diff --git a/biome.jsonc b/biome.jsonc index c6441913d0f..543c417956d 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -31,7 +31,6 @@ // TODO: these files are too big and complex, ignore them until their respective refactors "src/data/moves/move.ts", "src/data/abilities/ability.ts", - "src/field/pokemon.ts", // this file is just too big: "src/data/balance/tms.ts" @@ -91,6 +90,19 @@ "noUselessSwitchCase": "off", // Explicit > Implicit "noUselessConstructor": "warn", // TODO: Refactor and make this an error "noBannedTypes": "warn" // TODO: Refactor and make this an error + }, + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "types": { + "integer": { + "message": "This is an alias for 'number' that can provide false impressions of what values can actually be contained in this variable. Use 'number' instead.", + "use": "number" + } + } + } + } } } }, diff --git a/public/images/pokemon/332.png b/public/images/pokemon/332.png index a9979480673..44e374426b4 100644 Binary files a/public/images/pokemon/332.png and b/public/images/pokemon/332.png differ diff --git a/public/images/pokemon/646.png b/public/images/pokemon/646.png index 64b0af2d151..e54083bfc73 100644 Binary files a/public/images/pokemon/646.png and b/public/images/pokemon/646.png differ diff --git a/public/images/pokemon/back/332.png b/public/images/pokemon/back/332.png index a432b1af4b2..b251a35e468 100644 Binary files a/public/images/pokemon/back/332.png and b/public/images/pokemon/back/332.png differ diff --git a/public/images/pokemon/back/female/332.png b/public/images/pokemon/back/female/332.png index a432b1af4b2..b251a35e468 100644 Binary files a/public/images/pokemon/back/female/332.png and b/public/images/pokemon/back/female/332.png differ diff --git a/public/images/pokemon/female/332.png b/public/images/pokemon/female/332.png index 2100e4b9a10..c1f7c8ec3f0 100644 Binary files a/public/images/pokemon/female/332.png and b/public/images/pokemon/female/332.png differ diff --git a/public/images/pokemon/variant/332.json b/public/images/pokemon/variant/332.json index e9b487bca25..336ef4a22f3 100644 --- a/public/images/pokemon/variant/332.json +++ b/public/images/pokemon/variant/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "831a1f", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "c54b4b", - "215200": "710f2e", + "a5d670": "df5252", + "4aa552": "9f2f2c", "a5d674": "e16363", - "196b21": "891222", + "7aa953": "c54b4b", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "7aa1df", "525252": "123a5a", - "63b56b": "b2332f", - "a5d673": "df5252", "8c6b3a": "448bc3", - "4aa552": "9f2f2c" + "bdef84": "ec8c8c", + "63b56b": "b2332f", + "319452": "831a1f", + "196b21": "891222", + "4a7310": "982443" }, "2": { - "319452": "b08d72", - "4a7310": "4f3956", - "7ba563": "704e7e", - "bdef84": "a779ba", "8cbd63": "e3d7a6", - "215200": "583823", + "a5d670": "d7cda7", + "4aa552": "c5a77f", "a5d674": "8c669b", - "196b21": "78582c", + "7aa953": "704e7e", + "7ba563": "704e7e", + "215200": "583823", "f7ce00": "f2aacd", "525252": "a53b6f", - "63b56b": "cfc191", - "a5d673": "d7cda7", "8c6b3a": "df87bb", - "4aa552": "c5a77f" + "bdef84": "a779ba", + "63b56b": "cfc191", + "319452": "b08d72", + "196b21": "78582c", + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/332.json b/public/images/pokemon/variant/back/332.json index c13c07c34b4..fbfb3705202 100644 --- a/public/images/pokemon/variant/back/332.json +++ b/public/images/pokemon/variant/back/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "831a1f", + "7ba563": "b44040", + "215201": "630d28", + "215200": "710f2f", + "a5d674": "df5252", + "8cbd63": "c54b4b", + "63b56b": "b2332f", + "a5d670": "e16363", "319452": "831a1f", "4aa552": "9f2f2c", - "7ba563": "b44040", - "8cbd63": "c54b4b", - "215200": "710f2f", - "196b21": "831a1f", - "a5d674": "df5252", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "b2332f", - "215201": "630d28" + "4a7310": "982443" }, "2": { + "196b21": "b08d72", + "7ba563": "704e7e", + "215201": "583823", + "215200": "3f3249", + "a5d674": "d7cda7", + "8cbd63": "e3d7a6", + "63b56b": "cfc191", + "a5d670": "8c669b", "319452": "b08d72", "4aa552": "c5a77f", - "7ba563": "704e7e", - "8cbd63": "e3d7a6", - "215200": "3f3249", - "196b21": "b08d72", - "a5d674": "d7cda7", - "4a7310": "4f3956", - "a5d673": "8c669b", - "63b56b": "cfc191", - "215201": "583823" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/332.json b/public/images/pokemon/variant/back/female/332.json index 9ec50cb7e92..17f8d3c5f74 100644 --- a/public/images/pokemon/variant/back/female/332.json +++ b/public/images/pokemon/variant/back/female/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "780d4a", + "7ba563": "b44040", + "215201": "710f2e", + "215200": "710f2f", + "a5d674": "de5b6f", + "8cbd63": "bf3d64", + "63b56b": "9e2056", + "a5d670": "e16363", "319452": "780d4a", "4aa552": "8a1652", - "7ba563": "b44040", - "8cbd63": "bf3d64", - "215200": "710f2f", - "196b21": "780d4a", - "a5d674": "de5b6f", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "9e2056", - "215201": "710f2e" + "4a7310": "982443" }, "2": { + "196b21": "b59c72", + "7ba563": "805a9c", + "215201": "694d37", + "215200": "41334d", + "a5d674": "f6f7df", + "8cbd63": "ebe9ca", + "63b56b": "e3ddb8", + "a5d670": "a473ba", "319452": "b59c72", "4aa552": "c9b991", - "7ba563": "805a9c", - "8cbd63": "ebe9ca", - "215200": "41334d", - "196b21": "b59c72", - "a5d674": "f6f7df", - "4a7310": "4f3956", - "a5d673": "a473ba", - "63b56b": "e3ddb8", - "215201": "694d37" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/female/332.json b/public/images/pokemon/variant/female/332.json index c86429d13c4..7a1dc0f1457 100644 --- a/public/images/pokemon/variant/female/332.json +++ b/public/images/pokemon/variant/female/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "780d4a", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "bf3d64", - "215200": "710f2e", + "a5d670": "de5b6f", + "4aa552": "8a1652", "a5d674": "e16363", - "196b21": "7d1157", + "7aa953": "bf3d64", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "5bcfc3", "525252": "20668c", - "63b56b": "9e2056", - "a5d673": "de5b6f", "8c6b3a": "33a3b0", - "4aa552": "8a1652" + "bdef84": "ec8c8c", + "63b56b": "9e2056", + "319452": "780d4a", + "196b21": "7d1157", + "4a7310": "982443" }, "2": { - "319452": "b59c72", - "4a7310": "4f3956", - "7ba563": "805a9c", - "bdef84": "c193cf", "8cbd63": "f6f7df", - "215200": "694d37", + "a5d670": "ebe9ca", + "4aa552": "c9b991", "a5d674": "a473ba", - "196b21": "9c805f", + "7aa953": "805a9c", + "7ba563": "805a9c", + "215200": "694d37", "f7ce00": "f2aab6", "525252": "983364", - "63b56b": "e3ddb8", - "a5d673": "ebe9ca", "8c6b3a": "df879f", - "4aa552": "c9b991" + "bdef84": "c193cf", + "63b56b": "e3ddb8", + "319452": "b59c72", + "196b21": "9c805f", + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/locales b/public/locales index 42cd5cf577f..e9ccbadb6ea 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 42cd5cf577f475c22bc82d55e7ca358eb4f3184f +Subproject commit e9ccbadb6eaa3b797f3dec919745befda2ec74bd diff --git a/scripts/decrypt-save.js b/scripts/decrypt-save.js new file mode 100644 index 00000000000..a7239a40df6 --- /dev/null +++ b/scripts/decrypt-save.js @@ -0,0 +1,149 @@ +import pkg from "crypto-js"; +const { AES, enc } = pkg; +// biome-ignore lint: This is how you import fs from node +import * as fs from "node:fs"; + +const SAVE_KEY = "x0i2O7WRiANTqPmZ"; + +/** + * A map of condensed keynames to their associated full names + * NOTE: Update this if `src/system/game-data#systemShortKeys` ever changes! + */ +const systemShortKeys = { + seenAttr: "$sa", + caughtAttr: "$ca", + natureAttr: "$na", + seenCount: "$s", + caughtCount: "$c", + hatchedCount: "$hc", + ivs: "$i", + moveset: "$m", + eggMoves: "$em", + candyCount: "$x", + friendship: "$f", + abilityAttr: "$a", + passiveAttr: "$pa", + valueReduction: "$vr", + classicWinCount: "$wc", +}; + +/** + * Replace the shortened key names with their full names + * @param {string} dataStr - The string to convert + * @returns {string} The string with shortened keynames replaced with full names + */ +function convertSystemDataStr(dataStr) { + const fromKeys = Object.values(systemShortKeys); + const toKeys = Object.keys(systemShortKeys); + for (const k in fromKeys) { + dataStr = dataStr.replace(new RegExp(`${fromKeys[k].replace("$", "\\$")}`, "g"), toKeys[k]); + } + + return dataStr; +} + +/** + * Decrypt a save + * @param {string} path - The path to the encrypted save file + * @returns {string} The decrypted save data + */ +function decryptSave(path) { + // Check if the file exists + if (!fs.existsSync(path)) { + console.error(`File not found: ${path}`); + process.exit(1); + } + let fileData; + try { + fileData = fs.readFileSync(path, "utf8"); + } catch (e) { + switch (e.code) { + case "ENOENT": + console.error(`File not found: ${path}`); + break; + case "EACCES": + console.error(`Could not open ${path}: Permission denied`); + break; + case "EISDIR": + console.error(`Unable to read ${path} as it is a directory`); + break; + default: + console.error(`Error reading file: ${e.message}`); + } + process.exit(1); + } + return convertSystemDataStr(AES.decrypt(fileData, SAVE_KEY).toString(enc.Utf8)); +} + +/* Print the usage message and exits */ +function printUsage() { + console.log(` +Usage: node decrypt-save.js [save-file] + +Arguments: + file-path Path to the encrypted save file to decrypt. + save-file Path to where the decrypted data should be written. If not provided, the decrypted data will be printed to the console. + +Options: + -h, --help Show this help message and exit. + +Description: + This script decrypts an encrypted pokerogue save file +`); +} + +/** + * Write `data` to `filePath`, gracefully communicating errors that arise + * @param {string} filePath + * @param {string} data + */ +function writeToFile(filePath, data) { + try { + fs.writeFileSync(filePath, data); + } catch (e) { + switch (e.code) { + case "EACCES": + console.error(`Could not open ${filePath}: Permission denied`); + break; + case "EISDIR": + console.error(`Unable to write to ${filePath} as it is a directory`); + break; + default: + console.error(`Error writing file: ${e.message}`); + } + process.exit(1); + } +} + +function main() { + let args = process.argv.slice(2); + // Get options + const options = args.filter(arg => arg.startsWith("-")); + // get args + args = args.filter(arg => !arg.startsWith("-")); + + if (args.length === 0 || options.includes("-h") || options.includes("--help") || args.length > 2) { + printUsage(); + process.exit(0); + } + // If the user provided a second argument, check if the file exists already and refuse to write to it. + if (args.length === 2) { + const destPath = args[1]; + if (fs.existsSync(destPath)) { + console.error(`Refusing to overwrite ${destPath}`); + process.exit(1); + } + } + + // Otherwise, commence decryption. + const decrypt = decryptSave(args[0]); + + if (args.length === 1) { + process.stdout.write(decrypt); + process.exit(0); + } + + writeToFile(destPath, decrypt); +} + +main(); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index e51667f6a15..6c900cc2a30 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -7532,7 +7532,7 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { /** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */ getCondition(): MoveConditionFunc { - return (user, target, move) => target.getAbility().isSuppressable && !target.summonData.abilitySuppressed; + return (_user, target, _move) => !target.summonData.abilitySuppressed && (target.getAbility().isSuppressable || (target.hasPassive() && target.getPassiveAbility().isSuppressable)); } } diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 25798de3b4a..50b9c2da78c 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -3,7 +3,7 @@ import { transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedItem } from "#app/utils/common"; +import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -88,7 +88,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui const r = randSeedInt(SHINY_MAGIKARP_WEIGHT); - let validEventEncounters = timedEventManager + const validEventEncounters = timedEventManager .getEventEncounters() .filter( s => @@ -111,22 +111,26 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui if ( r === 0 || ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && - (validEventEncounters.length === 0)) + validEventEncounters.length === 0) ) { // If you roll 1%, give shiny Magikarp with random variant species = getPokemonSpecies(Species.MAGIKARP); pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true); - } - else if ( - (validEventEncounters.length > 0 && (r <= EVENT_THRESHOLD || - (isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE))) + } else if ( + validEventEncounters.length > 0 && + (r <= EVENT_THRESHOLD || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) ) { tries = 0; do { // If you roll 20%, give event encounter with 3 extra shiny rolls and its HA, if it has one const enc = randSeedItem(validEventEncounters); species = getPokemonSpecies(enc.species); - pokemon = new PlayerPokemon(species, 5, species.abilityHidden === Abilities.NONE ? undefined : 2, enc.formIndex); + pokemon = new PlayerPokemon( + species, + 5, + species.abilityHidden === Abilities.NONE ? undefined : 2, + enc.formIndex, + ); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); @@ -145,15 +149,13 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); - } - else { + } else { // If there's, and this would never happen, no eligible event encounters with a hidden ability, just do Magikarp species = getPokemonSpecies(Species.MAGIKARP); pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true); } } - } - else { + } else { pokemon = new PlayerPokemon(species, 5, 2, species.formIndex); } pokemon.generateAndPopulateMoveset(); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 2a471ee6132..d707363fa2e 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1698,8 +1698,8 @@ export function initSpecies() { new PokemonSpecies(Species.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), - new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), ), new PokemonSpecies(Species.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, Abilities.CUTE_CHARM, Abilities.MAGIC_GUARD, Abilities.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false), new PokemonSpecies(Species.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, Abilities.CUTE_CHARM, Abilities.COMPETITIVE, Abilities.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false), @@ -3122,7 +3122,7 @@ export function initSpecies() { ), new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown - new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 9.7, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), new PokemonSpecies(Species.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false, new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true), new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true), diff --git a/src/data/trainers/TrainerPartyTemplate.ts b/src/data/trainers/TrainerPartyTemplate.ts index ccc494218e9..86201589276 100644 --- a/src/data/trainers/TrainerPartyTemplate.ts +++ b/src/data/trainers/TrainerPartyTemplate.ts @@ -224,16 +224,16 @@ export const trainerPartyTemplates = { */ export function getEvilGruntPartyTemplate(): TrainerPartyTemplate { const waveIndex = globalScene.currentBattle?.waveIndex; - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_1){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_1) { return trainerPartyTemplates.TWO_AVG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_2){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_2) { return trainerPartyTemplates.THREE_AVG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_3){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_3) { return trainerPartyTemplates.TWO_AVG_ONE_STRONG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_ADMIN_1){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_ADMIN_1) { return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger } return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger @@ -251,7 +251,7 @@ export function getGymLeaderPartyTemplate() { switch (gameMode.modeId) { case GameModes.DAILY: if (currentBattle?.waveIndex <= 20) { - return trainerPartyTemplates.GYM_LEADER_2 + return trainerPartyTemplates.GYM_LEADER_2; } return trainerPartyTemplates.GYM_LEADER_3; case GameModes.CHALLENGE: // In the future, there may be a ChallengeType to call here. For now, use classic's. @@ -259,13 +259,15 @@ export function getGymLeaderPartyTemplate() { if (currentBattle?.waveIndex <= 20) { return trainerPartyTemplates.GYM_LEADER_1; // 1 avg 1 strong } - else if (currentBattle?.waveIndex <= 30) { + if (currentBattle?.waveIndex <= 30) { return trainerPartyTemplates.GYM_LEADER_2; // 1 avg 1 strong 1 stronger } - else if (currentBattle?.waveIndex <= 60) { // 50 and 60 + // 50 and 60 + if (currentBattle?.waveIndex <= 60) { return trainerPartyTemplates.GYM_LEADER_3; // 2 avg 1 strong 1 stronger } - else if (currentBattle?.waveIndex <= 90) { // 80 and 90 + // 80 and 90 + if (currentBattle?.waveIndex <= 90) { return trainerPartyTemplates.GYM_LEADER_4; // 3 avg 1 strong 1 stronger } // 110+ diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0ebd15394e8..53cfa7aaade 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,10 +5,9 @@ import { globalScene } from "#app/global-scene"; import type { Variant } from "#app/sprites/variant"; import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; import { variantData } from "#app/sprites/variant"; -import BattleInfo, { - PlayerBattleInfo, - EnemyBattleInfo, -} from "#app/ui/battle-info"; +import BattleInfo from "#app/ui/battle-info/battle-info"; +import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info"; +import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info"; import type Move from "#app/data/moves/move"; import { HighCritAttr, @@ -50,11 +49,26 @@ import { getPokemonSpecies, getPokemonSpeciesForm, } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { - getStarterValueFriendshipCap, - speciesStarterCosts, -} from "#app/data/balance/starters"; -import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor, randSeedIntRange } from "#app/utils/common"; + NumberHolder, + randSeedInt, + getIvsFromId, + BooleanHolder, + randSeedItem, + isNullOrUndefined, + getEnumValues, + toDmgValue, + fixedInt, + rgbaToInt, + rgbHexToRgba, + rgbToHsv, + deltaRgb, + isBetween, + type nil, + type Constructor, + randSeedIntRange, +} from "#app/utils/common"; import type { TypeDamageMultiplier } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; @@ -92,20 +106,13 @@ import { import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { Status, getRandomStatus } from "#app/data/status-effect"; -import type { - SpeciesFormEvolution, - SpeciesEvolutionCondition, -} from "#app/data/balance/pokemon-evolutions"; +import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution, } from "#app/data/balance/pokemon-evolutions"; -import { - reverseCompatibleTms, - tmSpecies, - tmPoolTiers, -} from "#app/data/balance/tms"; +import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, BattlerTagLapseType, @@ -128,11 +135,7 @@ import { type GrudgeTag, } from "../data/battler-tags"; import { WeatherType } from "#enums/weather-type"; -import { - ArenaTagSide, - NoCritTag, - WeakenMoveScreenTag, -} from "#app/data/arena-tag"; +import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { Ability } from "#app/data/abilities/ability-class"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; @@ -171,7 +174,8 @@ import { MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, - CheckTrappedAbAttr, InfiltratorAbAttr, + CheckTrappedAbAttr, + InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, @@ -196,25 +200,18 @@ import type { PartyOption } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; -import { - EVOLVE_MOVE, - RELEARN_MOVE, -} from "#app/data/balance/pokemon-level-moves"; +import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { achvs } from "#app/system/achv"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import { DexAttr } from "#app/system/game-data"; -import { - QuantizerCelebi, - argbFromRgba, - rgbaFromArgb, -} from "@material/material-color-utilities"; +import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { getNatureStatMultiplier } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, - SpeciesFormChangePostMoveTrigger + SpeciesFormChangePostMoveTrigger, } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; @@ -299,10 +296,10 @@ type damageParams = { simulated?: boolean; /** If defined, used in place of calculated effectiveness values */ effectiveness?: number; -} +}; /** Type for the parameters of {@linkcode Pokemon#getBaseDamage | getBaseDamage} */ -type getBaseDamageParams = Omit +type getBaseDamageParams = Omit; /** Type for the parameters of {@linkcode Pokemon#getAttackDamage | getAttackDamage} */ type getAttackDamageParams = Omit; @@ -414,7 +411,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.level = level; - this.abilityIndex = abilityIndex ?? this.generateAbilityIndex() + this.abilityIndex = abilityIndex ?? this.generateAbilityIndex(); if (formIndex !== undefined) { this.formIndex = formIndex; @@ -428,8 +425,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variant !== undefined) { this.variant = variant; } - this.exp = - dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); + this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; if (dataSource) { @@ -445,18 +441,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.nickname = dataSource.nickname; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? - this.friendship = - dataSource.friendship !== undefined - ? dataSource.friendship - : this.species.baseFriendship; + this.friendship = dataSource.friendship ?? this.species.baseFriendship; this.metLevel = dataSource.metLevel || 5; this.luck = dataSource.luck; this.metBiome = dataSource.metBiome; this.metSpecies = - dataSource.metSpecies ?? - (this.metBiome !== -1 - ? this.species.speciesId - : this.species.getRootSpeciesId(true)); + 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; @@ -476,9 +466,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.fusionTeraType = dataSource.fusionTeraType; this.usedTMs = dataSource.usedTMs ?? []; - this.customPokemonData = new CustomPokemonData( - dataSource.customPokemonData, - ); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); this.teraType = dataSource.teraType; this.isTerastallized = dataSource.isTerastallized; this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? []; @@ -491,12 +479,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.formIndex === undefined) { - this.formIndex = globalScene.getSpeciesFormIndex( - species, - this.gender, - this.nature, - this.isPlayer(), - ); + this.formIndex = globalScene.getSpeciesFormIndex(species, this.gender, this.nature, this.isPlayer()); } if (this.shiny === undefined) { @@ -515,19 +498,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.friendship = species.baseFriendship; this.metLevel = level; - this.metBiome = globalScene.currentBattle - ? globalScene.arena.biomeType - : -1; + this.metBiome = globalScene.currentBattle ? globalScene.arena.biomeType : -1; this.metSpecies = species.speciesId; - this.metWave = globalScene.currentBattle - ? globalScene.currentBattle.waveIndex - : -1; + this.metWave = globalScene.currentBattle ? globalScene.currentBattle.waveIndex : -1; this.pokerus = false; if (level > 1) { - const fused = new BooleanHolder( - globalScene.gameMode.isSplicedOnly, - ); + const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); } @@ -537,9 +514,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateFusionSpecies(); } } - this.luck = - (this.shiny ? this.variant + 1 : 0) + - (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.fusionLuck = this.luck; this.teraType = randSeedItem(this.getTypes(false, false, true)); @@ -559,15 +534,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!dataSource) { this.calculateStats(); } - } /** - * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). + * @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). */ - getNameToRender(useIllusion: boolean = true) { - const name: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.name : this.name; - const nickname: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.nickname : this.nickname; + getNameToRender(useIllusion = true) { + const name: string = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.name : this.name; + const nickname: string = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname; try { if (nickname) { return decodeURIComponent(escape(atob(nickname))); @@ -579,12 +555,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getPokeball(useIllusion = false){ - if(useIllusion){ - return this.summonData.illusion?.pokeball ?? this.pokeball - } else { - return this.pokeball + getPokeball(useIllusion = false) { + if (useIllusion) { + return this.summonData.illusion?.pokeball ?? this.pokeball; } + return this.pokeball; } init(): void { @@ -646,10 +621,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the pokemon is fainted */ public isFainted(checkStatus = false): boolean { - return ( - this.hp <= 0 && - (!checkStatus || this.status?.effect === StatusEffect.FAINT) - ); + return this.hp <= 0 && (!checkStatus || this.status?.effect === StatusEffect.FAINT); } /** @@ -667,11 +639,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public isAllowedInChallenge(): boolean { const challengeAllowed = new BooleanHolder(true); - applyChallenges( - ChallengeType.POKEMON_IN_BATTLE, - this, - challengeAllowed, - ); + applyChallenges(ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); return challengeAllowed.value; } @@ -692,12 +660,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let ret = 0n; ret |= this.gender !== Gender.FEMALE ? DexAttr.MALE : DexAttr.FEMALE; ret |= !this.shiny ? DexAttr.NON_SHINY : DexAttr.SHINY; - ret |= - this.variant >= 2 - ? DexAttr.VARIANT_3 - : this.variant === 1 - ? DexAttr.VARIANT_2 - : DexAttr.DEFAULT_VARIANT; + ret |= this.variant >= 2 ? DexAttr.VARIANT_3 : this.variant === 1 ? DexAttr.VARIANT_2 : DexAttr.DEFAULT_VARIANT; ret |= globalScene.gameData.getFormAttr(this.formIndex); return ret; } @@ -723,17 +686,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Generate `abilityIndex` based on species and hidden ability if not pre-defined. */ private generateAbilityIndex(): number { - // Roll for hidden ability chance, applying any ability charms for enemy mons - const hiddenAbilityChance = new NumberHolder( - BASE_HIDDEN_ABILITY_CHANCE, - ); + const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - hiddenAbilityChance, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } // If the roll succeeded and we have one, use HA; otherwise pick a random ability @@ -746,8 +702,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.species.ability2 !== this.species.ability1 ? randSeedInt(2) : 0; } - - /** * Generate an illusion of the last pokemon in the party, as other wild pokemon in the area. */ @@ -765,7 +719,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { shiny: this.shiny, variant: this.variant, fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant + fusionVariant: this.fusionVariant, }, species: speciesId, formIndex: pokemon.formIndex, @@ -773,7 +727,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { pokeball: pokemon.pokeball, fusionFormIndex: pokemon.fusionFormIndex, fusionSpecies: pokemon.fusionSpecies || undefined, - fusionGender: pokemon.fusionGender + fusionGender: pokemon.fusionGender, }; this.name = pokemon.name; @@ -788,7 +742,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.loadAssets(false, true).then(() => this.playAnim()); this.updateInfo(); } else { - const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, this.level); + const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies( + globalScene.currentBattle.waveIndex, + this.level, + ); this.summonData.illusion = { basePokemon: { @@ -797,12 +754,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { shiny: this.shiny, variant: this.variant, fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant + fusionVariant: this.fusionVariant, }, species: randomIllusion.speciesId, formIndex: randomIllusion.formIndex, gender: this.gender, - pokeball: this.pokeball + pokeball: this.pokeball, }; this.name = randomIllusion.name; @@ -814,15 +771,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { breakIllusion(): boolean { if (!this.summonData.illusion) { return false; - } else { - this.name = this.summonData.illusion.basePokemon.name; - this.nickname = this.summonData.illusion.basePokemon.nickname; - this.shiny = this.summonData.illusion.basePokemon.shiny; - this.variant = this.summonData.illusion.basePokemon.variant; - this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant; - this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny; - this.summonData.illusion = null; } + this.name = this.summonData.illusion.basePokemon.name; + this.nickname = this.summonData.illusion.basePokemon.nickname; + this.shiny = this.summonData.illusion.basePokemon.shiny; + this.variant = this.summonData.illusion.basePokemon.variant; + this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant; + this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny; + this.summonData.illusion = null; if (this.isOnField()) { globalScene.playSound("PRSFX- Transform"); } @@ -843,9 +799,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract getBattlerIndex(): BattlerIndex; /** -   * @param useIllusion - Whether we want the illusion or not. -   */ - async loadAssets(ignoreOverride = true, useIllusion: boolean = false): Promise { + * @param useIllusion - Whether we want the illusion or not. + */ + async loadAssets(ignoreOverride = true, useIllusion = false): Promise { /** Promises that are loading assets and can be run concurrently. */ const loadPromises: Promise[] = []; // Assets for moves @@ -858,7 +814,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(useIllusion) === Gender.FEMALE, formIndex, this.isShiny(useIllusion), - this.getVariant(useIllusion) + this.getVariant(useIllusion), ), ); @@ -869,15 +825,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } if (this.getFusionSpeciesForm()) { - const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; - const fusionShiny = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionShiny : this.fusionShiny; - const fusionVariant = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionVariant : this.fusionVariant; - loadPromises.push(this.getFusionSpeciesForm(false, useIllusion).loadAssets( - this.getFusionGender(false, useIllusion) === Gender.FEMALE, - fusionFormIndex, - fusionShiny, - fusionVariant - )); + const fusionFormIndex = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; + const fusionShiny = + !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.fusionShiny + : this.fusionShiny; + const fusionVariant = + !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.fusionVariant + : this.fusionVariant; + loadPromises.push( + this.getFusionSpeciesForm(false, useIllusion).loadAssets( + this.getFusionGender(false, useIllusion) === Gender.FEMALE, + fusionFormIndex, + fusionShiny, + fusionVariant, + ), + ); globalScene.loadPokemonAtlas( this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride), @@ -885,7 +850,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.isShiny(true)) { - loadPromises.push(populateVariantColors(this, false, ignoreOverride)) + loadPromises.push(populateVariantColors(this, false, ignoreOverride)); if (this.isPlayer()) { loadPromises.push(populateVariantColors(this, true, ignoreOverride)); } @@ -895,7 +860,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // This must be initiated before we queue loading, otherwise the load could have finished before // we reach the line of code that adds the listener, causing a deadlock. - const waitOnLoadPromise = new Promise(resolve => globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve)); + const waitOnLoadPromise = new Promise(resolve => + globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve), + ); if (!globalScene.load.isLoading()) { globalScene.load.start(); @@ -967,11 +934,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param useExpSprite should the experimental sprite be used * @param battleSpritePath the filename of the sprite */ - async populateVariantColorCache( - cacheKey: string, - useExpSprite: boolean, - battleSpritePath: string, - ) { + async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) { const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`; return globalScene .cachedFetch(spritePath) @@ -990,13 +953,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return res.json(); }) .catch(error => { - return this.fallbackVariantColor( - cacheKey, - spritePath, - useExpSprite, - battleSpritePath, - error, - ); + return this.fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); }) .then(c => { if (!isNullOrUndefined(c)) { @@ -1006,10 +963,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getFormKey(): string { - if ( - !this.species.forms.length || - this.species.forms.length <= this.formIndex - ) { + if (!this.species.forms.length || this.species.forms.length <= this.formIndex) { return ""; } return this.species.forms[this.formIndex].formKey; @@ -1019,10 +973,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.fusionSpecies) { return null; } - if ( - !this.fusionSpecies.forms.length || - this.fusionSpecies.forms.length <= this.fusionFormIndex - ) { + if (!this.fusionSpecies.forms.length || this.fusionSpecies.forms.length <= this.fusionFormIndex) { return ""; } return this.fusionSpecies.forms[this.fusionFormIndex].formKey; @@ -1034,10 +985,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string { - const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace( - /\_{2}/g, - "/", - ); + const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/"); return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; } @@ -1047,7 +995,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(ignoreOverride, true) === Gender.FEMALE, formIndex, this.shiny, - this.variant + this.variant, ); } @@ -1063,7 +1011,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { formIndex, this.shiny, this.variant, - back + back, ); } @@ -1072,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, this.summonData.illusion?.basePokemon.shiny ?? this.shiny, - this.summonData.illusion?.basePokemon.variant ?? this.variant + this.summonData.illusion?.basePokemon.variant ?? this.variant, ); } @@ -1086,7 +1034,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, this.fusionShiny, - this.fusionVariant + this.fusionVariant, ); } @@ -1102,7 +1050,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fusionFormIndex, this.fusionShiny, this.fusionVariant, - back + back, ); } @@ -1110,55 +1058,67 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return `pkmn__${this.getFusionBattleSpriteId(back, ignoreOverride)}`; } - getFusionBattleSpriteAtlasPath( - back?: boolean, - ignoreOverride?: boolean, - ): string { - return this.getFusionBattleSpriteId(back, ignoreOverride).replace( - /\_{2}/g, - "/", - ); + getFusionBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string { + return this.getFusionBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/"); } - getIconAtlasKey(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const formIndex = useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; - const variant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; + getIconAtlasKey(ignoreOverride = false, useIllusion = true): string { + // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.formIndex` is `0`?) + const formIndex = + useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion.formIndex : this.formIndex; + const variant = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.variant : this.variant; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( formIndex, this.isBaseShiny(useIllusion), - variant + variant, ); } - getFusionIconAtlasKey(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const fusionFormIndex = useIllusion && this.summonData.illusion?.fusionFormIndex ? this.summonData.illusion?.fusionFormIndex : this.fusionFormIndex; - const fusionVariant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.fusionVariant : this.fusionVariant; + getFusionIconAtlasKey(ignoreOverride = false, useIllusion = true): string { + // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.fusionFormIndex` is `0`?) + const fusionFormIndex = + useIllusion && this.summonData.illusion?.fusionFormIndex + ? this.summonData.illusion.fusionFormIndex + : this.fusionFormIndex; + const fusionVariant = + !useIllusion && this.summonData.illusion + ? this.summonData.illusion.basePokemon.fusionVariant + : this.fusionVariant; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( fusionFormIndex, this.isFusionShiny(), - fusionVariant + fusionVariant, ); } - getIconId(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const formIndex = useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; - const variant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; + getIconId(ignoreOverride?: boolean, useIllusion = true): string { + const formIndex = + useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; + const variant = + !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getGender(ignoreOverride, useIllusion) === Gender.FEMALE, formIndex, this.isBaseShiny(), - variant + variant, ); } - getFusionIconId(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const fusionFormIndex = useIllusion && this.summonData.illusion?.fusionFormIndex ? this.summonData.illusion?.fusionFormIndex : this.fusionFormIndex; - const fusionVariant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.fusionVariant : this.fusionVariant; + getFusionIconId(ignoreOverride?: boolean, useIllusion = true): string { + const fusionFormIndex = + useIllusion && this.summonData.illusion?.fusionFormIndex + ? this.summonData.illusion?.fusionFormIndex + : this.fusionFormIndex; + const fusionVariant = + !useIllusion && !!this.summonData.illusion + ? this.summonData.illusion?.basePokemon.fusionVariant + : this.fusionVariant; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getFusionGender(ignoreOverride, useIllusion) === Gender.FEMALE, fusionFormIndex, this.isFusionShiny(), - fusionVariant + fusionVariant, ); } @@ -1168,12 +1128,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * This overrides `useIllusion` if `true`. * @param useIllusion - `true` to use the speciesForm of the illusion; default `false`. */ - getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm { + getSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm { if (!ignoreOverride && this.summonData.speciesForm) { return this.summonData.speciesForm; } - const species: PokemonSpecies = useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; + const species: PokemonSpecies = + useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; if (species.forms && species.forms.length > 0) { @@ -1186,17 +1147,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not. */ - getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { - const fusionSpecies: PokemonSpecies = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; - const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion = false): PokemonSpeciesForm { + const fusionSpecies: PokemonSpecies = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; + const fusionFormIndex = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; if (!ignoreOverride && this.summonData.fusionSpeciesForm) { return this.summonData.fusionSpeciesForm; } - if ( - !fusionSpecies?.forms?.length || - fusionFormIndex >= fusionSpecies?.forms.length - ) { + if (!fusionSpecies?.forms?.length || fusionFormIndex >= fusionSpecies?.forms.length) { return fusionSpecies; } return fusionSpecies?.forms[fusionFormIndex]; @@ -1207,9 +1167,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getTintSprite(): Phaser.GameObjects.Sprite | null { - return !this.maskEnabled - ? (this.getAt(1) as Phaser.GameObjects.Sprite) - : this.maskSprite; + return !this.maskEnabled ? (this.getAt(1) as Phaser.GameObjects.Sprite) : this.maskSprite; } getSpriteScale(): number { @@ -1290,20 +1248,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param animConfig {@linkcode String} to pass to {@linkcode Phaser.GameObjects.Sprite.play} * @returns true if the sprite was able to be animated */ - tryPlaySprite( - sprite: Phaser.GameObjects.Sprite, - tintSprite: Phaser.GameObjects.Sprite, - key: string, - ): boolean { + tryPlaySprite(sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite, key: string): boolean { // Catch errors when trying to play an animation that doesn't exist try { sprite.play(key); tintSprite.play(key); } catch (error: unknown) { - console.error( - `Couldn't play animation for '${key}'!\nIs the image for this Pokemon missing?\n`, - error, - ); + console.error(`Couldn't play animation for '${key}'!\nIs the image for this Pokemon missing?\n`, error); return false; } @@ -1312,11 +1263,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } playAnim(): void { - this.tryPlaySprite( - this.getSprite(), - this.getTintSprite()!, - this.getBattleSpriteKey(), - ); // TODO: is the bag correct? + this.tryPlaySprite(this.getSprite(), this.getTintSprite()!, this.getBattleSpriteKey()); // TODO: is the bang correct? } getFieldPositionOffset(): [number, number] { @@ -1354,30 +1301,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" const currentPhase = globalScene.getCurrentPhase(); - if ( - currentPhase instanceof MoveEffectPhase && - currentPhase.getPokemon() === this - ) { + if (currentPhase instanceof MoveEffectPhase && currentPhase.getPokemon() === this) { return false; } return true; - } else { - return false; } + return false; } /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ destroySubstitute(): void { const substitute = this.getTag(SubstituteTag); - if (substitute && substitute.sprite) { + if (substitute?.sprite) { substitute.sprite.destroy(); } } - setFieldPosition( - fieldPosition: FieldPosition, - duration?: number, - ): Promise { + setFieldPosition(fieldPosition: FieldPosition, duration?: number): Promise { return new Promise(resolve => { if (fieldPosition === this.fieldPosition) { resolve(); @@ -1442,10 +1382,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns the numeric value of the desired {@linkcode Stat} */ getStat(stat: PermanentStat, bypassSummonData = true): number { - if ( - !bypassSummonData && - this.summonData.stats[stat] !== 0 - ) { + if (!bypassSummonData && this.summonData.stats[stat] !== 0) { return this.summonData.stats[stat]; } return this.stats[stat]; @@ -1519,9 +1456,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { if (critBoostTag instanceof DragonCheerTag) { - critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) - ? 2 - : 1; + critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; } else { critStage.value += 2; } @@ -1572,57 +1507,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const statValue = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { - globalScene.applyModifiers( - StatBoosterModifier, - this.isPlayer(), - this, - stat, - statValue, - ); + globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); } // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway const fieldApplied = new BooleanHolder(false); for (const pokemon of globalScene.getField(true)) { - applyFieldStatMultiplierAbAttrs( - FieldMultiplyStatAbAttr, - pokemon, - stat, - statValue, - this, - fieldApplied, - simulated, - ); + applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied, simulated); if (fieldApplied.value) { break; } } if (!ignoreAbility) { - applyStatMultiplierAbAttrs( - StatMultiplierAbAttr, - this, - stat, - statValue, - simulated, - ); + applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated); } const ally = this.getAlly(); if (!isNullOrUndefined(ally)) { - applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); + applyAllyStatMultiplierAbAttrs( + AllyStatMultiplierAbAttr, + ally, + stat, + statValue, + simulated, + this, + move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility, + ); } let ret = statValue.value * - this.getStatStageMultiplier( - stat, - opponent, - move, - ignoreOppAbility, - isCritical, - simulated, - ignoreHeldItems, - ); + this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems); switch (stat) { case Stat.ATK: @@ -1631,31 +1546,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case Stat.DEF: - if ( - this.isOfType(PokemonType.ICE) && - globalScene.arena.weather?.weatherType === WeatherType.SNOW - ) { + if (this.isOfType(PokemonType.ICE) && globalScene.arena.weather?.weatherType === WeatherType.SNOW) { ret *= 1.5; } break; case Stat.SPATK: break; case Stat.SPDEF: - if ( - this.isOfType(PokemonType.ROCK) && - globalScene.arena.weather?.weatherType === WeatherType.SANDSTORM - ) { + if (this.isOfType(PokemonType.ROCK) && globalScene.arena.weather?.weatherType === WeatherType.SANDSTORM) { ret *= 1.5; } break; - case Stat.SPD: + case Stat.SPD: { const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { ret *= 2; } - if ( - globalScene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side) - ) { + if (globalScene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { ret >>= 2; } @@ -1665,19 +1572,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.status && this.status.effect === StatusEffect.PARALYSIS) { ret >>= 1; } - if ( - this.getTag(BattlerTagType.UNBURDEN) && - this.hasAbility(Abilities.UNBURDEN) - ) { + if (this.getTag(BattlerTagType.UNBURDEN) && this.hasAbility(Abilities.UNBURDEN)) { ret *= 2; } break; + } } const highestStatBoost = this.findTag( - t => - t instanceof HighestStatBoostTag && - (t as HighestStatBoostTag).stat === stat, + t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat, ) as HighestStatBoostTag; if (highestStatBoost) { ret *= highestStatBoost.multiplier; @@ -1695,18 +1598,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const baseStats = this.calculateBaseStats(); // Using base stats, calculate and store stats one by one for (const s of PERMANENT_STATS) { - const statHolder = new NumberHolder( - Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01), - ); + const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - globalScene.applyModifier( - PokemonIncrementingStatModifier, - this.isPlayer(), - this, - s, - statHolder, - ); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1720,37 +1615,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } else { statHolder.value += 5; - const natureStatMultiplier = new NumberHolder( - getNatureStatMultiplier(this.getNature(), s), - ); - globalScene.applyModifier( - PokemonNatureWeightModifier, - this.isPlayer(), - this, - natureStatMultiplier, - ); + const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s)); + globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max( - Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"]( - statHolder.value * natureStatMultiplier.value, - ), + Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), 1, ); } - globalScene.applyModifier( - PokemonIncrementingStatModifier, - this.isPlayer(), - this, - s, - statHolder, - ); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } - statHolder.value = Phaser.Math.Clamp( - statHolder.value, - 1, - Number.MAX_SAFE_INTEGER, - ); + statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); this.setStat(s, statHolder.value); } @@ -1758,32 +1634,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); - applyChallenges( - ChallengeType.FLIP_STAT, - this, - baseStats, - ); + applyChallenges(ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice - globalScene.applyModifiers( - PokemonBaseStatTotalModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); // Old Gateau - globalScene.applyModifiers( - PokemonBaseStatFlatModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; - applyChallenges( - ChallengeType.FLIP_STAT, - this, - fusionBaseStats, - ); + applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats); for (const s of PERMANENT_STATS) { baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); @@ -1794,20 +1652,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } // Vitamins - globalScene.applyModifiers( - BaseStatModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); return baseStats; } getNature(): Nature { - return this.customPokemonData.nature !== -1 - ? this.customPokemonData.nature - : this.nature; + return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature; } setNature(nature: Nature): void { @@ -1842,9 +1693,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getHpRatio(precise = false): number { - return precise - ? this.hp / this.getMaxHp() - : Math.round((this.hp / this.getMaxHp()) * 100) / 100; + return precise ? this.hp / this.getMaxHp() : Math.round((this.hp / this.getMaxHp()) * 100) / 100; } generateGender(): void { @@ -1861,54 +1710,56 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + * @param useIllusion - Whether we want the fake or real gender (illusion ability). */ - getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + getGender(ignoreOverride?: boolean, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion) { return this.summonData.illusion.gender; - } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { + } + if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { return this.summonData.gender; } return this.gender; } /** - * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + * @param useIllusion - Whether we want the fake or real gender (illusion ability). */ - getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + getFusionGender(ignoreOverride?: boolean, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion?.fusionGender) { return this.summonData.illusion.fusionGender; - } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { + } + if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { return this.summonData.fusionGender; } return this.fusionGender; } /** - * @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability). + * @param useIllusion - Whether we want the fake or real shininess (illusion ability). */ - isShiny(useIllusion: boolean = false): boolean { + isShiny(useIllusion = false): boolean { if (!useIllusion && this.summonData.illusion) { - return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; - } else { - return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); + return !!( + this.summonData.illusion.basePokemon?.shiny || + (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) + ); } + return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); } - isBaseShiny(useIllusion: boolean = false){ + isBaseShiny(useIllusion = false) { if (!useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.basePokemon?.shiny; - } else { - return this.shiny; } + return this.shiny; } - isFusionShiny(useIllusion: boolean = false){ + isFusionShiny(useIllusion = false) { if (!useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.basePokemon?.fusionShiny; - } else { - return this.isFusion(useIllusion) && this.fusionShiny; } + return this.isFusion(useIllusion) && this.fusionShiny; } /** @@ -1916,34 +1767,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param useIllusion - Whether we want the fake or real shininess (illusion ability). * @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise */ - isDoubleShiny(useIllusion: boolean = false): boolean { + isDoubleShiny(useIllusion = false): boolean { if (!useIllusion && this.summonData.illusion?.basePokemon) { - return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny; - } else { - return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; + return ( + this.isFusion(false) && + this.summonData.illusion.basePokemon.shiny && + this.summonData.illusion.basePokemon.fusionShiny + ); } + return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; } /** - * @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability). + * @param useIllusion - Whether we want the fake or real variant (illusion ability). */ - getVariant(useIllusion: boolean = false): Variant { + getVariant(useIllusion = false): Variant { if (!useIllusion && this.summonData.illusion) { return !this.isFusion(false) - ? this.summonData.illusion.basePokemon!.variant - : Math.max(this.variant, this.fusionVariant) as Variant; - } else { - return !this.isFusion(true) - ? this.variant - : Math.max(this.variant, this.fusionVariant) as Variant; + ? this.summonData.illusion.basePokemon!.variant + : (Math.max(this.variant, this.fusionVariant) as Variant); } + return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant); } getBaseVariant(doubleShiny: boolean): Variant { if (doubleShiny) { return this.summonData.illusion?.basePokemon?.variant ?? this.variant; } - return this.getVariant(); } @@ -1951,7 +1801,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.luck + (this.isFusion() ? this.fusionLuck : 0); } - isFusion(useIllusion: boolean = false): boolean { + isFusion(useIllusion = false): boolean { if (useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.fusionSpecies; } @@ -1959,12 +1809,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). + * @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). */ - getName(useIllusion: boolean = false): string { - return (!useIllusion && this.summonData.illusion?.basePokemon) - ? this.summonData.illusion.basePokemon.name - : this.name; + getName(useIllusion = false): string { + return !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.name + : this.name; } /** @@ -1984,22 +1834,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ hasSpecies(species: Species, formKey?: string): boolean { if (isNullOrUndefined(formKey)) { - return ( - this.species.speciesId === species || - this.fusionSpecies?.speciesId === species - ); + return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; } - return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey); + return ( + (this.species.speciesId === species && this.getFormKey() === formKey) || + (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey) + ); } abstract isBoss(): boolean; getMoveset(ignoreOverride?: boolean): PokemonMove[] { - const ret = - !ignoreOverride && this.summonData.moveset - ? this.summonData.moveset - : this.moveset; + const ret = !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset; // Overrides moveset based on arrays specified in overrides.ts let overrideArray: Moves | Array = this.isPlayer() @@ -2014,10 +1861,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } overrideArray.forEach((move: Moves, index: number) => { const ppUsed = this.moveset[index]?.ppUsed ?? 0; - this.moveset[index] = new PokemonMove( - move, - Math.min(ppUsed, allMoves[move].pp), - ); + this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp)); }); } @@ -2034,9 +1878,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getUnlockedEggMoves(): Moves[] { const moves: Moves[] = []; const species = - this.metSpecies in speciesEggMoves - ? this.metSpecies - : this.getSpeciesForm(true).getRootSpeciesId(true); + this.metSpecies in speciesEggMoves ? this.metSpecies : this.getSpeciesForm(true).getRootSpeciesId(true); if (species in speciesEggMoves) { for (let i = 0; i < 4; i++) { if (globalScene.gameData.starterData[species].eggMoves & (1 << i)) { @@ -2058,17 +1900,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getLearnableLevelMoves(): Moves[] { let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); - if ( - this.metBiome === -1 && - !globalScene.gameMode.isFreshStartChallenge() && - !globalScene.gameMode.isDaily - ) { + if (this.metBiome === -1 && !globalScene.gameMode.isFreshStartChallenge() && !globalScene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } if (Array.isArray(this.usedTMs) && this.usedTMs.length > 0) { - levelMoves = this.usedTMs - .filter(m => !levelMoves.includes(m)) - .concat(levelMoves); + levelMoves = this.usedTMs.filter(m => !levelMoves.includes(m)).concat(levelMoves); } levelMoves = levelMoves.filter(lm => !this.moveset.some(m => m.moveId === lm)); return levelMoves; @@ -2084,9 +1920,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getTypes( includeTeraType = false, - forDefend: boolean = false, - ignoreOverride: boolean = false, - useIllusion: boolean = false + forDefend = false, + ignoreOverride = false, + useIllusion = false, ): PokemonType[] { const types: PokemonType[] = []; @@ -2101,7 +1937,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!types.length || !includeTeraType) { - if ( !ignoreOverride && this.summonData.types && @@ -2146,10 +1981,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { secondType = fusionType1; } - if ( - secondType === PokemonType.UNKNOWN && - isNullOrUndefined(fusionType2) - ) { + if (secondType === PokemonType.UNKNOWN && isNullOrUndefined(fusionType2)) { // If second pokemon was monotype and shared its primary type secondType = customTypes && @@ -2188,11 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // check type added to Pokemon from moves like Forest's Curse or Trick Or Treat - if ( - !ignoreOverride && - this.summonData.addedType && - !types.includes(this.summonData.addedType) - ) { + if (!ignoreOverride && this.summonData.addedType && !types.includes(this.summonData.addedType)) { types.push(this.summonData.addedType); } @@ -2212,15 +2040,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` * @returns `true` if the Pokemon's type matches */ - public isOfType( - type: PokemonType, - includeTeraType = true, - forDefend = false, - ignoreOverride = false, - ): boolean { - return this.getTypes(includeTeraType, forDefend, ignoreOverride).some( - t => t === type, - ); + public isOfType(type: PokemonType, includeTeraType = true, forDefend = false, ignoreOverride = false): boolean { + return this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type); } /** @@ -2242,27 +2063,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if ( - !isNullOrUndefined(this.fusionCustomPokemonData?.ability) && - this.fusionCustomPokemonData.ability !== -1 - ) { + if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { return allAbilities[this.fusionCustomPokemonData.ability]; } - return allAbilities[ - this.getFusionSpeciesForm(ignoreOverride).getAbility( - this.fusionAbilityIndex, - ) - ]; + return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } - if ( - !isNullOrUndefined(this.customPokemonData.ability) && - this.customPokemonData.ability !== -1 - ) { + if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { return allAbilities[this.customPokemonData.ability]; } - let abilityId = this.getSpeciesForm(ignoreOverride).getAbility( - this.abilityIndex, - ); + let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); if (abilityId === Abilities.NONE) { abilityId = this.species.ability1; } @@ -2283,10 +2092,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } - if ( - !isNullOrUndefined(this.customPokemonData.passive) && - this.customPokemonData.passive !== -1 - ) { + if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { return allAbilities[this.customPokemonData.passive]; } @@ -2310,9 +2116,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const abilityAttrs: T[] = []; if (!canApply || this.canApplyAbility()) { - abilityAttrs.push( - ...this.getAbility(ignoreOverride).getAttrs(attrType), - ); + abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType)); } if (!canApply || this.canApplyAbility(true)) { @@ -2362,11 +2166,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } if ( - ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || - Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && + ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && this.isPlayer()) || - ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || - Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && + ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && !this.isPlayer()) ) { return true; @@ -2403,35 +2205,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } const arena = globalScene?.arena; - if ( - arena.ignoreAbilities && - arena.ignoringEffectSource !== this.getBattlerIndex() && - ability.isIgnorable - ) { + if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) { return false; } - if ( - this.summonData.abilitySuppressed && - ability.isSuppressable - ) { + if (this.summonData.abilitySuppressed && ability.isSuppressable) { return false; } - const suppressAbilitiesTag = arena.getTag( - ArenaTagType.NEUTRALIZING_GAS, - ) as SuppressAbilitiesTag; + const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; const suppressOffField = ability.hasAttr(PreSummonAbAttr); - if ( - (this.isOnField() || suppressOffField) && - suppressAbilitiesTag && - !suppressAbilitiesTag.isBeingRemoved() - ) { - const thisAbilitySuppressing = ability.hasAttr( - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, - ); - const hasSuppressingAbility = this.hasAbilityWithAttr( - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, - false, - ); + if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) { + const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); + const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas // (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized) // If the ability itself is neutralizing gas, don't suppress it (handled through arena tag) @@ -2443,10 +2227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } } - return ( - (this.hp > 0 || ability.isBypassFaint) && - !ability.conditions.find(condition => !condition(this)) - ); + return (this.hp > 0 || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this)); } /** @@ -2458,22 +2239,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride Whether to ignore ability changing effects; default `false` * @returns `true` if the ability is present and active */ - public hasAbility( - ability: Abilities, - canApply = true, - ignoreOverride = false, - ): boolean { - if ( - this.getAbility(ignoreOverride).id === ability && - (!canApply || this.canApplyAbility()) - ) { + public hasAbility(ability: Abilities, canApply = true, ignoreOverride = false): boolean { + if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if ( - this.getPassiveAbility().id === ability && - this.hasPassive() && - (!canApply || this.canApplyAbility(true)) - ) { + if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return true; } return false; @@ -2489,22 +2259,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride Whether to ignore ability changing effects; default `false` * @returns `true` if an ability with the given {@linkcode AbAttr} is present and active */ - public hasAbilityWithAttr( - attrType: Constructor, - canApply = true, - ignoreOverride = false, - ): boolean { - if ( - (!canApply || this.canApplyAbility()) && - this.getAbility(ignoreOverride).hasAttr(attrType) - ) { + public hasAbilityWithAttr(attrType: Constructor, canApply = true, ignoreOverride = false): boolean { + if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { return true; } - if ( - this.hasPassive() && - (!canApply || this.canApplyAbility(true)) && - this.getPassiveAbility().hasAttr(attrType) - ) { + if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) { return true; } return false; @@ -2537,10 +2296,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return PokemonType.STELLAR; } if (this.hasSpecies(Species.OGERPON)) { - const ogerponForm = - this.species.speciesId === Species.OGERPON - ? this.formIndex - : this.fusionFormIndex; + const ogerponForm = this.species.speciesId === Species.OGERPON ? this.formIndex : this.fusionFormIndex; switch (ogerponForm) { case 0: case 4: @@ -2580,10 +2336,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param simulated - If `true`, applies abilities via simulated calls. * @returns `true` if the pokemon is trapped */ - public isTrapped( - trappedAbMessages: string[] = [], - simulated = true, - ): boolean { + public isTrapped(trappedAbMessages: string[] = [], simulated = true): boolean { const commandedTag = this.getTag(BattlerTagType.COMMANDED); if (commandedTag?.getSourcePokemon()?.isActive(true)) { return true; @@ -2598,22 +2351,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective * Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger */ - const opposingFieldUnfiltered = this.isPlayer() - ? globalScene.getEnemyField() - : globalScene.getPlayerField(); - const opposingField = opposingFieldUnfiltered.filter( - enemyPkm => enemyPkm.switchOutStatus === false, - ); + const opposingFieldUnfiltered = this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField(); + const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); for (const opponent of opposingField) { - applyCheckTrappedAbAttrs( - CheckTrappedAbAttr, - opponent, - trappedByAbility, - this, - trappedAbMessages, - simulated, - ); + applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated); } const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; @@ -2635,26 +2377,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveTypeHolder = new NumberHolder(move.type); applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); - applyPreAttackAbAttrs( - MoveTypeChangeAbAttr, - this, - null, - move, - simulated, - moveTypeHolder - ); + applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // then bypass the check for ion deluge and electrify - if (this.isTerastallized && (move.id === Moves.TERA_BLAST || move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) { + if ( + this.isTerastallized && + (move.id === Moves.TERA_BLAST || + (move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) + ) { return moveTypeHolder.value as PokemonType; } - globalScene.arena.applyTags( - ArenaTagType.ION_DELUGE, - simulated, - moveTypeHolder, - ); + globalScene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder); if (this.getTag(BattlerTagType.ELECTRIFIED)) { moveTypeHolder.value = PokemonType.ELECTRIC; } @@ -2679,7 +2414,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ignoreAbility = false, simulated = true, cancelled?: BooleanHolder, - useIllusion: boolean = false + useIllusion = false, ): TypeDamageMultiplier { if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) { return this.turnData?.moveEffectiveness; @@ -2691,28 +2426,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveType = source.getMoveType(move); const typeMultiplier = new NumberHolder( - move.category !== MoveCategory.STATUS || - move.hasAttr(RespectAttackTypeImmunityAttr) - ? this.getAttackTypeEffectiveness( - moveType, - source, - false, - simulated, - move, - useIllusion - ) - : 1); - - applyMoveAttrs( - VariableMoveTypeMultiplierAttr, - source, - this, - move, - typeMultiplier, + move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) + ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion) + : 1, ); - if ( - this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t)) - ) { + + applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); + if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) { typeMultiplier.value = 0; } @@ -2722,52 +2442,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const cancelledHolder = cancelled ?? new BooleanHolder(false); if (!ignoreAbility) { - applyPreDefendAbAttrs( - TypeImmunityAbAttr, - this, - source, - move, - cancelledHolder, - simulated, - typeMultiplier, - ); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); if (!cancelledHolder.value) { - applyPreDefendAbAttrs( - MoveImmunityAbAttr, - this, - source, - move, - cancelledHolder, - simulated, - typeMultiplier, - ); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); } if (!cancelledHolder.value) { - const defendingSidePlayField = this.isPlayer() - ? globalScene.getPlayerField() - : globalScene.getEnemyField(); + const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); defendingSidePlayField.forEach(p => - applyPreDefendAbAttrs( - FieldPriorityMoveImmunityAbAttr, - p, - source, - move, - cancelledHolder, - ), + applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder), ); } } - const immuneTags = this.findTags( - tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType, - ); + const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); for (const tag of immuneTags) { - if ( - move && - !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType) - ) { + if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { typeMultiplier.value = 0; break; } @@ -2775,27 +2466,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Apply Tera Shell's effect to attacks after all immunities are accounted for if (!ignoreAbility && move.category !== MoveCategory.STATUS) { - applyPreDefendAbAttrs( - FullHpResistTypeAbAttr, - this, - source, - move, - cancelledHolder, - simulated, - typeMultiplier, - ); + applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); } - if ( - move.category === MoveCategory.STATUS && - move.hitsSubstitute(source, this) - ) { + if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) { typeMultiplier.value = 0; } - return ( - !cancelledHolder.value ? typeMultiplier.value : 0 - ) as TypeDamageMultiplier; + return (!cancelledHolder.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; } /** @@ -2811,10 +2489,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getAttackTypeEffectiveness( moveType: PokemonType, source?: Pokemon, - ignoreStrongWinds: boolean = false, - simulated: boolean = true, + ignoreStrongWinds = false, + simulated = true, move?: Move, - useIllusion: boolean = false + useIllusion = false, ): TypeDamageMultiplier { if (moveType === PokemonType.STELLAR) { return this.isTerastallized ? 2 : 1; @@ -2824,10 +2502,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Handle flying v ground type immunity without removing flying type so effective types are still effective // Related to https://github.com/pagefaultgames/pokerogue/issues/524 - if ( - moveType === PokemonType.GROUND && - (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY)) - ) { + if (moveType === PokemonType.GROUND && (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))) { const flyingIndex = types.indexOf(PokemonType.FLYING); if (flyingIndex > -1) { types.splice(flyingIndex, 1); @@ -2836,37 +2511,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let multiplier = types .map(defType => { - const multiplier = new NumberHolder( - getTypeDamageMultiplier(moveType, defType), - ); - applyChallenges( - ChallengeType.TYPE_EFFECTIVENESS, - multiplier, - ); + const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType)); + applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); if (move) { - applyMoveAttrs( - VariableMoveTypeChartAttr, - null, - this, - move, - multiplier, - defType, - ); + applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); } if (source) { const ignoreImmunity = new BooleanHolder(false); - if ( - source.isActive(true) && - source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr) - ) { - applyAbAttrs( - IgnoreTypeImmunityAbAttr, - source, - ignoreImmunity, - simulated, - moveType, - defType, - ); + if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { + applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType); } if (ignoreImmunity.value) { if (multiplier.value === 0) { @@ -2874,9 +2527,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - const exposedTags = this.findTags( - tag => tag instanceof ExposedTag, - ) as ExposedTag[]; + const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) { if (multiplier.value === 0) { return 1; @@ -2887,13 +2538,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }) .reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; - const typeMultiplierAgainstFlying = new NumberHolder( - getTypeDamageMultiplier(moveType, PokemonType.FLYING), - ); - applyChallenges( - ChallengeType.TYPE_EFFECTIVENESS, - typeMultiplierAgainstFlying, - ); + const typeMultiplierAgainstFlying = new NumberHolder(getTypeDamageMultiplier(moveType, PokemonType.FLYING)); + applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying); // Handle strong winds lowering effectiveness of types super effective against pure flying if ( !ignoreStrongWinds && @@ -2922,27 +2568,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const enemyTypes = opponent.getTypes(true, true, false, true); /** Is this Pokemon faster than the opponent? */ const outspeed = - (this.isActive(true) - ? this.getEffectiveStat(Stat.SPD, opponent) - : this.getStat(Stat.SPD, false)) >= + (this.isActive(true) ? this.getEffectiveStat(Stat.SPD, opponent) : this.getStat(Stat.SPD, false)) >= opponent.getEffectiveStat(Stat.SPD, this); /** * Based on how effective this Pokemon's types are offensively against the opponent's types. * This score is increased by 25 percent if this Pokemon is faster than the opponent. */ - let atkScore = opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1); + let atkScore = + opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1); /** * Based on how effectively this Pokemon defends against the opponent's types. * This score cannot be higher than 4. */ - let defScore = - 1 / - Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25); + let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25); if (types.length > 1) { atkScore *= opponent.getAttackTypeEffectiveness(types[1], this); } if (enemyTypes.length > 1) { - defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25)); + defScore *= + 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25); } /** * Based on this Pokemon's HP ratio compared to that of the opponent. @@ -2963,38 +2607,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ( !e.item && this.level >= e.level && - (isNullOrUndefined(e.preFormKey) || - this.getFormKey() === e.preFormKey) + (isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey) ) { - if ( - e.condition === null || - (e.condition as SpeciesEvolutionCondition).predicate(this) - ) { + if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) { return e; } } } } - if ( - this.isFusion() && - this.fusionSpecies && - pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId) - ) { - const fusionEvolutions = pokemonEvolutions[ - this.fusionSpecies.speciesId - ].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e)); + if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) { + const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map( + e => new FusionSpeciesFormEvolution(this.species.speciesId, e), + ); for (const fe of fusionEvolutions) { if ( !fe.item && this.level >= fe.level && - (isNullOrUndefined(fe.preFormKey) || - this.getFusionFormKey() === fe.preFormKey) + (isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey) ) { - if ( - fe.condition === null || - (fe.condition as SpeciesEvolutionCondition).predicate(this) - ) { + if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) { return fe; } } @@ -3024,10 +2656,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!startingLevel) { startingLevel = this.level; } - if ( - learnSituation === LearnMoveSituation.EVOLUTION_FUSED && - this.fusionSpecies - ) { + if (learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies) { // For fusion evolutions, get ONLY the moves of the component mon that evolved levelMoves = this.getFusionSpeciesForm(true) .getLevelMoves() @@ -3047,10 +2676,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); for (let e = 0; e < evolutionChain.length; e++) { // TODO: Might need to pass specific form index in simulated evolution chain - const speciesLevelMoves = getPokemonSpeciesForm( - evolutionChain[e][0], - this.formIndex, - ).getLevelMoves(); + const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves(); if (includeRelearnerMoves) { levelMoves.push(...speciesLevelMoves); } else { @@ -3058,9 +2684,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ...speciesLevelMoves.filter( lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || - ((!e || lm[0] > 1) && - (e === evolutionChain.length - 1 || - lm[0] <= evolutionChain[e + 1][1])), + ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1])), ), ); } @@ -3075,19 +2699,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lm[0] > 0, ); } - if ( - this.fusionSpecies && - learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE - ) { + if (this.fusionSpecies && learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE) { // For fusion evolutions, get ONLY the moves of the component mon that evolved if (simulateEvolutionChain) { - const fusionEvolutionChain = - this.fusionSpecies.getSimulatedEvolutionChain( - this.level, - this.hasTrainer(), - this.isBoss(), - this.isPlayer(), - ); + const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain( + this.level, + this.hasTrainer(), + this.isBoss(), + this.isPlayer(), + ); for (let e = 0; e < fusionEvolutionChain.length; e++) { // TODO: Might need to pass specific form index in simulated evolution chain const speciesLevelMoves = getPokemonSpeciesForm( @@ -3097,9 +2717,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (includeRelearnerMoves) { levelMoves.push( ...speciesLevelMoves.filter( - lm => - (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || - lm[0] !== EVOLVE_MOVE, + lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || lm[0] !== EVOLVE_MOVE, ), ); } else { @@ -3108,8 +2726,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && - (e === fusionEvolutionChain.length - 1 || - lm[0] <= fusionEvolutionChain[e + 1][1])), + (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1])), ), ); } @@ -3128,9 +2745,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } } - levelMoves.sort((lma: [number, number], lmb: [number, number]) => - lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0, - ); + levelMoves.sort((lma: [number, number], lmb: [number, number]) => (lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0)); /** * Filter out moves not within the correct level range(s) @@ -3142,10 +2757,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isRelearner = level < startingLevel; const allowedEvolutionMove = level === 0 && includeEvolutionMoves; - return ( - !(level > this.level) && - (includeRelearnerMoves || !isRelearner || allowedEvolutionMove) - ); + return !(level > this.level) && (includeRelearnerMoves || !isRelearner || allowedEvolutionMove); }); /** @@ -3211,10 +2823,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ trySetShiny(thresholdOverride?: number): boolean { // Shiny Pokemon should not spawn in the end biome in endless - if ( - globalScene.gameMode.isEndless && - globalScene.arena.biomeType === Biome.END - ) { + if (globalScene.gameMode.isEndless && globalScene.arena.biomeType === Biome.END) { return false; } @@ -3234,11 +2843,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!this.hasTrainer()) { - globalScene.applyModifiers( - ShinyRateBoosterModifier, - true, - shinyThreshold, - ); + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); } } else { shinyThreshold.value = thresholdOverride; @@ -3263,10 +2868,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set as a shiny, `false` otherwise */ - public trySetShinySeed( - thresholdOverride?: number, - applyModifiersToOverride?: boolean, - ): boolean { + public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.shiny) { const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined || applyModifiersToOverride) { @@ -3276,13 +2878,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyModifiers( - ShinyRateBoosterModifier, - true, - shinyThreshold, - ); - } - else { + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + } else { shinyThreshold.value = thresholdOverride; } @@ -3292,8 +2889,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.shiny) { this.variant = this.variant ?? 0; this.variant = Math.max(this.generateShinyVariant(), this.variant) as Variant; // Don't set a variant lower than the current one - this.luck = - this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.initShinySparkle(); } @@ -3319,8 +2915,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Checks if there is no variant data for both the index or index with form if ( !this.shiny || - (!variantData.hasOwnProperty(variantDataIndex) && - !variantData.hasOwnProperty(this.species.speciesId)) + (!variantData.hasOwnProperty(variantDataIndex) && !variantData.hasOwnProperty(this.species.speciesId)) ) { return 0; } @@ -3350,10 +2945,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise */ - public tryRerollHiddenAbilitySeed( - thresholdOverride?: number, - applyModifiersToOverride?: boolean, - ): boolean { + public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.species.abilityHidden) { return false; } @@ -3363,11 +2955,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { haThreshold.value = thresholdOverride; } if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - haThreshold, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); } } else { haThreshold.value = thresholdOverride; @@ -3381,15 +2969,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } public generateFusionSpecies(forStarter?: boolean): void { - const hiddenAbilityChance = new NumberHolder( - BASE_HIDDEN_ABILITY_CHANCE, - ); + const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - hiddenAbilityChance, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -3412,30 +2994,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let fusionOverride: PokemonSpecies | undefined = undefined; - if ( - forStarter && - this instanceof PlayerPokemon && - Overrides.STARTER_FUSION_SPECIES_OVERRIDE - ) { - fusionOverride = getPokemonSpecies( - Overrides.STARTER_FUSION_SPECIES_OVERRIDE, - ); - } else if ( - this instanceof EnemyPokemon && - Overrides.OPP_FUSION_SPECIES_OVERRIDE - ) { + if (forStarter && this instanceof PlayerPokemon && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) { + fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE); + } else if (this instanceof EnemyPokemon && Overrides.OPP_FUSION_SPECIES_OVERRIDE) { fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE); } this.fusionSpecies = fusionOverride ?? - globalScene.randomSpecies( - globalScene.currentBattle?.waveIndex || 0, - this.level, - false, - filter, - true, - ); + globalScene.randomSpecies(globalScene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionAbilityIndex = this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 @@ -3487,10 +3054,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let movePool: [Moves, number][] = []; const allLevelMoves = this.getLevelMoves(1, true, true); if (!allLevelMoves) { - console.warn( - "Error encountered trying to generate moveset for:", - this.species.name, - ); + console.warn("Error encountered trying to generate moveset for:", this.species.name); return; } @@ -3505,13 +3069,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { weight = 50; } // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves. - if (weight === 1 && allMoves[levelMove[1]].power >= 80 || weight === RELEARN_MOVE && this.hasTrainer()) { + if ((weight === 1 && allMoves[levelMove[1]].power >= 80) || (weight === RELEARN_MOVE && this.hasTrainer())) { weight = 40; } - if ( - !movePool.some(m => m[0] === levelMove[1]) && - !allMoves[levelMove[1]].name.endsWith(" (N)") - ) { + if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) { movePool.push([levelMove[1], weight]); } } @@ -3532,30 +3093,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { compatible = true; break; } - } else if ( - p === this.species.speciesId || - (this.fusionSpecies && p === this.fusionSpecies.speciesId) - ) { + } else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) { compatible = true; break; } } - if ( - compatible && - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) { movePool.push([moveId, 4]); - } else if ( - tmPoolTiers[moveId] === ModifierTier.GREAT && - this.level >= 30 - ) { + } else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) { movePool.push([moveId, 8]); - } else if ( - tmPoolTiers[moveId] === ModifierTier.ULTRA && - this.level >= 50 - ) { + } else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) { movePool.push([moveId, 14]); } } @@ -3565,10 +3113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.level >= 60) { for (let i = 0; i < 3; i++) { const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i]; - if ( - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { movePool.push([moveId, 40]); } } @@ -3584,17 +3129,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.fusionSpecies) { for (let i = 0; i < 3; i++) { - const moveId = - speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i]; - if ( - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i]; + if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { movePool.push([moveId, 40]); } } - const moveId = - speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3]; + const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3]; // No rare egg moves before e4 if ( this.level >= 170 && @@ -3613,35 +3153,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr) && !allMoves[m[0]].hasAttr(HpSplitAttr)); } // No one gets Memento or Final Gambit - movePool = movePool.filter( - m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit), - ); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit)); if (this.hasTrainer()) { // Trainers never get OHKO moves movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr)); // Half the weight of self KO moves - movePool = movePool.map(m => [ - m[0], - m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1), - ]); + movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]); // Trainers get a weight bump to stat buffing moves movePool = movePool.map(m => [ m[0], - m[1] * - (allMoves[m[0]] - .getAttrs(StatStageChangeAttr) - .some(a => a.stages > 1 && a.selfTarget) - ? 1.25 - : 1), + m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), ]); // Trainers get a weight decrease to multiturn moves movePool = movePool.map(m => [ m[0], - m[1] * - (!!allMoves[m[0]].isChargingMove() || - !!allMoves[m[0]].hasAttr(RechargeAttr) - ? 0.7 - : 1), + m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1), ]); } @@ -3649,10 +3175,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Caps max power at 90 to avoid something like hyper beam ruining the stats. // This is a pretty soft weighting factor, although it is scaled with the weight multiplier. const maxPower = Math.min( - movePool.reduce( - (v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), - 40, - ), + movePool.reduce((v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), 40), 90, ); movePool = movePool.map(m => [ @@ -3660,10 +3183,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 - : Math.max( - Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), - 0.5, - )), + : Math.max(Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), 0.5)), ]); // Weight damaging moves against the lower stat. This uses a non-linear relationship. @@ -3671,16 +3191,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // One third weight at ~1.58x higher, one quarter weight at ~1.73x higher, one fifth at ~1.87x, and one tenth at ~2.35x higher. const atk = this.getStat(Stat.ATK); const spAtk = this.getStat(Stat.SPATK); - const worseCategory: MoveCategory = - atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; - const statRatio = - worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; + const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; + const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; movePool = movePool.map(m => [ m[0], - m[1] * - (allMoves[m[0]].category === worseCategory - ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) - : 1), + m[1] * (allMoves[m[0]].category === worseCategory ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) : 1), ]); /** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */ @@ -3688,16 +3203,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isBoss()) { weightMultiplier += 0.4; } - const baseWeights: [Moves, number][] = movePool.map(m => [ - m[0], - Math.ceil(Math.pow(m[1], weightMultiplier) * 100), - ]); + const baseWeights: [Moves, number][] = movePool.map(m => [m[0], Math.ceil(Math.pow(m[1], weightMultiplier) * 100)]); // All Pokemon force a STAB move first const stabMovePool = baseWeights.filter( - m => - allMoves[m[0]].category !== MoveCategory.STATUS && - this.isOfType(allMoves[m[0]].type), + m => allMoves[m[0]].category !== MoveCategory.STATUS && this.isOfType(allMoves[m[0]].type), ); if (stabMovePool.length) { @@ -3710,41 +3220,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.moveset.push(new PokemonMove(stabMovePool[index][0])); } - while ( - baseWeights.length > this.moveset.length && - this.moveset.length < 4 - ) { + while (baseWeights.length > this.moveset.length && this.moveset.length < 4) { if (this.hasTrainer()) { // Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier. // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights get 20x if STAB. // Status moves remain unchanged on weight, this encourages 1-2 movePool = baseWeights - .filter(m => !this.moveset.some( - mo => - m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed - )) + .filter( + m => + !this.moveset.some( + mo => + m[0] === mo.moveId || + (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + ), + ) .map(m => { let ret: number; if ( this.moveset.some( - mo => - mo.getMove().category !== MoveCategory.STATUS && - mo.getMove().type === allMoves[m[0]].type, + mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[m[0]].type, ) ) { ret = Math.ceil(Math.sqrt(m[1])); } else if (allMoves[m[0]].category !== MoveCategory.STATUS) { ret = Math.ceil( (m[1] / - Math.max( - Math.pow( - 4, - this.moveset.filter(mo => (mo.getMove().power ?? 0) > 1) - .length, - ) / 8, - 0.5, - )) * + Math.max(Math.pow(4, this.moveset.filter(mo => (mo.getMove().power ?? 0) > 1).length) / 8, 0.5)) * (this.isOfType(allMoves[m[0]].type) ? 20 : 1), ); } else { @@ -3754,11 +3255,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); } else { // Non-trainer pokemon just use normal weights - movePool = baseWeights.filter(m => !this.moveset.some( - mo => - m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed - )); + movePool = baseWeights.filter( + m => + !this.moveset.some( + mo => + m[0] === mo.moveId || + (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + ), + ); } const totalWeight = movePool.reduce((v, m) => v + m[1], 0); let rand = randSeedInt(totalWeight); @@ -3775,18 +3279,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { !globalScene.currentBattle?.isBattleMysteryEncounter() || !globalScene.currentBattle?.mysteryEncounter ) { - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeMoveLearnedTrigger, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); } } public trySelectMove(moveIndex: number, ignorePp?: boolean): boolean { - const move = - this.getMoveset().length > moveIndex - ? this.getMoveset()[moveIndex] - : null; + const move = this.getMoveset().length > moveIndex ? this.getMoveset()[moveIndex] : null; return move?.isUsable(this, ignorePp) ?? false; } @@ -3795,11 +3293,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const otherBattleInfo = globalScene.fieldUI .getAll() .slice(0, 4) - .filter( - ui => - ui instanceof BattleInfo && - (ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer(), - ) + .filter(ui => ui instanceof BattleInfo && (ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer()) .find(() => true); if (!otherBattleInfo || !this.getFieldIndex()) { globalScene.fieldUI.sendToBack(this.battleInfo); @@ -3807,10 +3301,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { globalScene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo); } - this.battleInfo.setX( - this.battleInfo.x + - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198), - ); + this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setVisible(true); if (this.isPlayer()) { this.battleInfo.expMaskRect.x += 150; @@ -3826,7 +3317,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hideInfo(): Promise { return new Promise(resolve => { - if (this.battleInfo && this.battleInfo.visible) { + if (this.battleInfo?.visible) { globalScene.tweens.add({ targets: [this.battleInfo, this.battleInfo.expMaskRect], x: this.isPlayer() ? "+=150" : `-=${!this.isBoss() ? 150 : 246}`, @@ -3837,10 +3328,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.battleInfo.expMaskRect.x -= 150; } this.battleInfo.setVisible(false); - this.battleInfo.setX( - this.battleInfo.x - - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198), - ); + this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); resolve(); }, }); @@ -3862,22 +3350,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.battleInfo.updateInfo(this, instant); } - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - this.battleInfo.updateEffectiveness(effectiveness); - } - toggleStats(visible: boolean): void { this.battleInfo.toggleStats(visible); } - toggleFlyout(visible: boolean): void { - this.battleInfo.toggleFlyout(visible); - } - /** * Adds experience to this PlayerPokemon, subject to wave based level caps. * @param exp The amount of experience to add @@ -3887,25 +3363,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const maxExpLevel = globalScene.getMaxExpLevel(ignoreLevelCap); const initialExp = this.exp; this.exp += exp; - while ( - this.level < maxExpLevel && - this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate) - ) { + while (this.level < maxExpLevel && this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate)) { this.level++; } if (this.level >= maxExpLevel) { - console.log( - initialExp, - this.exp, - getLevelTotalExp(this.level, this.species.growthRate), - ); - this.exp = Math.max( - getLevelTotalExp(this.level, this.species.growthRate), - initialExp, - ); + console.log(initialExp, this.exp, getLevelTotalExp(this.level, this.species.growthRate)); + this.exp = Math.max(getLevelTotalExp(this.level, this.species.growthRate), initialExp); } - this.levelExp = - this.exp - getLevelTotalExp(this.level, this.species.growthRate); + this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); } /** @@ -3919,7 +3384,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getOpponent(targetIndex: number): Pokemon | null { const ret = this.getOpponents()[targetIndex]; - if (ret.summonData) { // TODO: why does this check for summonData and can we remove it? + // TODO: why does this check for summonData and can we remove it? + if (ret.summonData) { return ret; } return null; @@ -3931,11 +3397,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param onField - whether to also check if the pokemon is currently on the field (defaults to true) */ getOpponents(onField = true): Pokemon[] { - return ( - (this.isPlayer() - ? globalScene.getEnemyField() - : globalScene.getPlayerField()) as Pokemon[] - ).filter(p => p.isActive(onField)); + return ((this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField()) as Pokemon[]).filter(p => + p.isActive(onField), + ); } getOpponentDescriptor(): string { @@ -3943,17 +3407,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (opponents.length === 1) { return opponents[0].name; } - return this.isPlayer() - ? i18next.t("arenaTag:opposingTeam") - : i18next.t("arenaTag:yourTeam"); + return this.isPlayer() ? i18next.t("arenaTag:opposingTeam") : i18next.t("arenaTag:yourTeam"); } getAlly(): Pokemon | undefined { - return ( - this.isPlayer() - ? globalScene.getPlayerField() - : globalScene.getEnemyField() - )[this.getFieldIndex() ? 0 : 1]; + return (this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; } /** @@ -3962,9 +3420,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns An array of Pokémon on the allied field. */ getAlliedField(): Pokemon[] { - return this instanceof PlayerPokemon - ? globalScene.getPlayerField() - : globalScene.getEnemyField(); + return this instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); } /** @@ -4007,37 +3463,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!ignoreOppAbility) { - applyAbAttrs( - IgnoreOpponentStatStagesAbAttr, - opponent, - null, - simulated, - stat, - ignoreStatStage, - ); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); } if (move) { - applyMoveAttrs( - IgnoreOpponentStatStagesAttr, - this, - opponent, - move, - ignoreStatStage, - ); + applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage); } } if (!ignoreStatStage.value) { - const statStageMultiplier = new NumberHolder( - Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value), - ); + const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); if (!ignoreHeldItems) { - globalScene.applyModifiers( - TempStatStageBoosterModifier, - this.isPlayer(), - stat, - statStageMultiplier, - ); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); } return Math.min(statStageMultiplier.value, 4); } @@ -4061,47 +3497,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const userAccStage = new NumberHolder(this.getStatStage(Stat.ACC)); - const targetEvaStage = new NumberHolder( - target.getStatStage(Stat.EVA), - ); + const targetEvaStage = new NumberHolder(target.getStatStage(Stat.EVA)); const ignoreAccStatStage = new BooleanHolder(false); const ignoreEvaStatStage = new BooleanHolder(false); - applyAbAttrs( - IgnoreOpponentStatStagesAbAttr, - target, - null, - false, - Stat.ACC, - ignoreAccStatStage, - ); - applyAbAttrs( - IgnoreOpponentStatStagesAbAttr, - this, - null, - false, - Stat.EVA, - ignoreEvaStatStage, - ); - applyMoveAttrs( - IgnoreOpponentStatStagesAttr, - this, - target, - sourceMove, - ignoreEvaStatStage, - ); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); + applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage); - globalScene.applyModifiers( - TempStatStageBoosterModifier, - this.isPlayer(), - Stat.ACC, - userAccStage, - ); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); - userAccStage.value = ignoreAccStatStage.value - ? 0 - : Math.min(userAccStage.value, 6); + userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6); targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value; if (target.findTag(t => t instanceof ExposedTag)) { @@ -4116,22 +3523,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6)); } - applyStatMultiplierAbAttrs( - StatMultiplierAbAttr, - this, - Stat.ACC, - accuracyMultiplier, - false, - sourceMove, - ); + applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove); const evasionMultiplier = new NumberHolder(1); - applyStatMultiplierAbAttrs( - StatMultiplierAbAttr, - target, - Stat.EVA, - evasionMultiplier, - ); + applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); const ally = this.getAlly(); if (!isNullOrUndefined(ally)) { @@ -4157,8 +3552,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @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( - { + getBaseDamage({ source, move, moveCategory, @@ -4167,8 +3561,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ignoreAllyAbility = false, ignoreSourceAllyAbility = false, isCritical = false, - simulated = true}: getBaseDamageParams - ): number { + simulated = true, + }: getBaseDamageParams): number { const isPhysical = moveCategory === MoveCategory.PHYSICAL; /** A base damage multiplier based on the source's level */ @@ -4217,25 +3611,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * 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; + 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, - ); + console.log("base damage", baseDamage, move.name, power, sourceAtk.value, targetDef.value); } return baseDamage; } - /** Determine the STAB multiplier for a move used against this pokemon. * * @param source - The attacking {@linkcode Pokemon} @@ -4259,31 +3644,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { stabMultiplier.value += 0.5; } - applyMoveAttrs( - CombinedPledgeStabBoostAttr, - source, - this, - move, - stabMultiplier, - ); + applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); if (!ignoreSourceAbility) { applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); } - if ( - source.isTerastallized && - sourceTeraType === moveType && - moveType !== PokemonType.STELLAR - ) { + if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) { stabMultiplier.value += 0.5; } if ( source.isTerastallized && source.getTeraType() === PokemonType.STELLAR && - (!source.stellarTypesBoosted.includes(moveType) || - source.hasSpecies(Species.TERAPAGOS)) + (!source.stellarTypesBoosted.includes(moveType) || source.hasSpecies(Species.TERAPAGOS)) ) { stabMultiplier.value += matchesSourceType ? 0.5 : 0.2; } @@ -4304,31 +3678,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param effectiveness If defined, used in place of calculated effectiveness values * @returns The {@linkcode DamageCalculationResult} */ - getAttackDamage( - { - source, - move, - ignoreAbility = false, - ignoreSourceAbility = false, - ignoreAllyAbility = false, - ignoreSourceAllyAbility = false, - isCritical = false, - simulated = true, - effectiveness}: getAttackDamageParams, - ): DamageCalculationResult { + getAttackDamage({ + source, + move, + ignoreAbility = false, + ignoreSourceAbility = false, + ignoreAllyAbility = false, + ignoreSourceAllyAbility = false, + isCritical = false, + simulated = true, + effectiveness, + }: getAttackDamageParams): DamageCalculationResult { const damage = new NumberHolder(0); - const defendingSide = this.isPlayer() - ? ArenaTagSide.PLAYER - : ArenaTagSide.ENEMY; + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const variableCategory = new NumberHolder(move.category); - applyMoveAttrs( - VariableMoveCategoryAttr, - source, - this, - move, - variableCategory, - ); + applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); const moveCategory = variableCategory.value as MoveCategory; /** The move's type after type-changing effects are applied */ @@ -4344,13 +3709,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * Note that the source's abilities are not ignored here */ - const typeMultiplier = effectiveness ?? this.getMoveEffectiveness( - source, - move, - ignoreAbility, - simulated, - cancelled, - ); + const typeMultiplier = + effectiveness ?? this.getMoveEffectiveness(source, move, ignoreAbility, simulated, cancelled); const isPhysical = moveCategory === MoveCategory.PHYSICAL; @@ -4358,21 +3718,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const arenaAttackTypeMultiplier = new NumberHolder( globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()), ); - applyMoveAttrs( - IgnoreWeatherTypeDebuffAttr, - source, - this, - move, - arenaAttackTypeMultiplier, - ); + applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0; if (cancelled.value || isTypeImmune) { return { cancelled: cancelled.value, - result: - move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT, + result: move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT, damage: 0, }; } @@ -4390,9 +3743,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { null, multiLensMultiplier, ); - fixedDamage.value = toDmgValue( - fixedDamage.value * multiLensMultiplier.value, - ); + fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { cancelled: false, @@ -4469,10 +3820,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * A multiplier for random damage spread in the range [0.85, 1] * This is always 1 for simulated calls. */ - const randomMultiplier = simulated - ? 1 - : this.randBattleSeedIntRange(85, 100) / 100; - + const randomMultiplier = simulated ? 1 : this.randBattleSeedIntRange(85, 100) / 100; /** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */ const stabMultiplier = this.calculateStabMultiplier(source, move, ignoreSourceAbility, simulated); @@ -4487,12 +3835,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) { const burnDamageReductionCancelled = new BooleanHolder(false); if (!ignoreSourceAbility) { - applyAbAttrs( - BypassBurnDamageReductionAbAttr, - source, - burnDamageReductionCancelled, - simulated, - ); + applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, simulated); } if (!burnDamageReductionCancelled.value) { burnMultiplier = 0.5; @@ -4505,13 +3848,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Critical hits should bypass screens if (!isCritical) { globalScene.arena.applyTagsForSide( - WeakenMoveScreenTag, - defendingSide, - simulated, - source, - moveCategory, - screenMultiplier, - ); + WeakenMoveScreenTag, + defendingSide, + simulated, + source, + moveCategory, + screenMultiplier, + ); } /** @@ -4556,14 +3899,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Doubles damage if the attacker has Tinted Lens and is using a resisted move */ if (!ignoreSourceAbility) { - applyPreAttackAbAttrs( - DamageBoostAbAttr, - source, - this, - move, - simulated, - damage, - ); + applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, simulated, damage); } /** Apply the enemy's Damage and Resistance tokens */ @@ -4576,28 +3912,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ if (!ignoreAbility) { - applyPreDefendAbAttrs( - ReceivedMoveDamageMultiplierAbAttr, - this, - source, - move, - cancelled, - simulated, - damage, - ); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage); const ally = this.getAlly(); /** Additionally apply friend guard damage reduction if ally has it. */ if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { - applyPreDefendAbAttrs( - AlliedFieldDamageReductionAbAttr, - ally, - source, - move, - cancelled, - simulated, - damage, - ); + applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, ally, source, move, cancelled, simulated, damage); } } @@ -4605,15 +3925,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); if (this.isFullHp() && !ignoreAbility) { - applyPreDefendAbAttrs( - PreDefendFullHpEndureAbAttr, - this, - source, - move, - cancelled, - false, - damage, - ); + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); } // debug message for when damage is applied (i.e. not simulated) @@ -4642,8 +3954,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param move - The {@linkcode Move} being used * @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`) * @returns whether the move critically hits the pokemon - */ - getCriticalHitResult(source: Pokemon, move: Move, simulated: boolean = true): boolean { + */ + getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) { @@ -4657,16 +3969,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical); applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); if (!isCritical.value) { - const critChance = [24, 8, 2, 1][ - Math.max(0, Math.min(this.getCritStage(source, move), 3)) - ]; + const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); } applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical); return isCritical.value; - } /** @@ -4677,12 +3986,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhas flag on whether to add FaintPhase if pokemon after applying damage faints * @returns integer representing damage dealt */ - damage( - damage: number, - _ignoreSegments = false, - preventEndure = false, - ignoreFaintPhase = false, - ): number { + damage(damage: number, _ignoreSegments = false, preventEndure = false, ignoreFaintPhase = false): number { if (this.isFainted()) { return 0; } @@ -4698,12 +4002,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - globalScene.applyModifiers( - SurviveDamageModifier, - this.isPlayer(), - this, - surviveDamage, - ); + globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); } if (surviveDamage.value) { damage = this.hp - 1; @@ -4721,9 +4020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() ) */ globalScene.setPhaseQueueSplice(); - globalScene.unshiftPhase( - new FaintPhase(this.getBattlerIndex(), preventEndure), - ); + globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); } @@ -4742,39 +4039,29 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage() * @returns integer of damage done */ - damageAndUpdate(damage: number, + damageAndUpdate( + damage: number, { result = HitResult.EFFECTIVE, isCritical = false, ignoreSegments = false, ignoreFaintPhase = false, source = undefined, - }: - { - result?: DamageResult, - isCritical?: boolean, - ignoreSegments?: boolean, - ignoreFaintPhase?: boolean, - source?: Pokemon, - } = {} + }: { + result?: DamageResult; + isCritical?: boolean; + ignoreSegments?: boolean; + ignoreFaintPhase?: boolean; + source?: Pokemon; + } = {}, ): number { - const isIndirectDamage = [ HitResult.INDIRECT, HitResult.INDIRECT_KO ].includes(result); - const damagePhase = new DamageAnimPhase( - this.getBattlerIndex(), - damage, - result as DamageResult, - isCritical - ); + const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result); + const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, isCritical); globalScene.unshiftPhase(damagePhase); if (this.switchOutStatus && source) { damage = 0; } - damage = this.damage( - damage, - ignoreSegments, - isIndirectDamage, - ignoreFaintPhase, - ); + damage = this.damage(damage, ignoreSegments, isIndirectDamage, ignoreFaintPhase); // Ensure the battle-info bar's HP is updated, though only if the battle info is visible // TODO: When battle-info UI is refactored, make this only update the HP bar if (this.battleInfo.visible) { @@ -4787,15 +4074,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr */ if (!source || source.turnData.hitCount <= 1) { - applyPostDamageAbAttrs( - PostDamageAbAttr, - this, - damage, - this.hasPassive(), - false, - [], - source, - ); + applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); } return damage; } @@ -4811,13 +4090,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isMax(): boolean { - const maxForms = [ SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX ] as string[]; - return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)); + const maxForms = [ + SpeciesFormKey.GIGANTAMAX, + SpeciesFormKey.GIGANTAMAX_RAPID, + SpeciesFormKey.GIGANTAMAX_SINGLE, + SpeciesFormKey.ETERNAMAX, + ] as string[]; + return ( + maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)) + ); } isMega(): boolean { - const megaForms = [ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL ] as string[]; - return megaForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!)); + const megaForms = [ + SpeciesFormKey.MEGA, + SpeciesFormKey.MEGA_X, + SpeciesFormKey.MEGA_Y, + SpeciesFormKey.PRIMAL, + ] as string[]; + return ( + megaForms.includes(this.getFormKey()) || + (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!)) + ); } canAddTag(tagType: BattlerTagType): boolean { @@ -4828,35 +4122,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const stubTag = new BattlerTag(tagType, 0, 0); const cancelled = new BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs( - BattlerTagImmunityAbAttr, - this, - stubTag, - cancelled, - true, - ); + applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true); const userField = this.getAlliedField(); userField.forEach(pokemon => - applyPreApplyBattlerTagAbAttrs( - UserFieldBattlerTagImmunityAbAttr, - pokemon, - stubTag, - cancelled, - true, - this, - ), + applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true, this), ); return !cancelled.value; } - addTag( - tagType: BattlerTagType, - turnCount = 0, - sourceMove?: Moves, - sourceId?: number, - ): boolean { + addTag(tagType: BattlerTagType, turnCount = 0, sourceMove?: Moves, sourceId?: number): boolean { const existingTag = this.getTag(tagType); if (existingTag) { existingTag.onOverlap(this); @@ -4866,25 +4142,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct? const cancelled = new BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs( - BattlerTagImmunityAbAttr, - this, - newTag, - cancelled, - ); + applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); if (cancelled.value) { return false; } for (const pokemon of this.getAlliedField()) { - applyPreApplyBattlerTagAbAttrs( - UserFieldBattlerTagImmunityAbAttr, - pokemon, - newTag, - cancelled, - false, - this - ); + applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled, false, this); if (cancelled.value) { return false; } @@ -4932,14 +4196,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const tags = this.summonData.tags; const tag = tags.find(t => t.tagType === tagType); if (!tag) { - return false + return false; } if (!tag.lapse(this, BattlerTagLapseType.CUSTOM)) { tag.onRemove(this); tags.splice(tags.indexOf(tag), 1); } - return true + return true; } /** @@ -4953,8 +4217,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { .filter( t => lapseType === BattlerTagLapseType.FAINT || - (t.lapseTypes.some(lType => lType === lapseType) && - !t.lapse(this, lapseType)), + (t.lapseTypes.some(lType => lType === lapseType) && !t.lapse(this, lapseType)), ) .forEach(t => { t.onRemove(this); @@ -4998,7 +4261,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (t.sourceId === sourceId) { t.sourceId = newSourceId; } - }) + }); } /** @@ -5058,21 +4321,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @see {@linkcode MoveRestrictionBattlerTag} */ - isMoveTargetRestricted( - moveId: Moves, - user: Pokemon, - target: Pokemon, - ): boolean { - for (const tag of this.findTags( - t => t instanceof MoveRestrictionBattlerTag, - )) { - if ( - (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted( - moveId, - user, - target, - ) - ) { + isMoveTargetRestricted(moveId: Moves, user: Pokemon, target: Pokemon): boolean { + for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { + if ((tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return (tag as MoveRestrictionBattlerTag) !== null; } } @@ -5087,26 +4338,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param target - {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status * @returns The first tag on this Pokemon that restricts the move, or `null` if the move is not restricted. */ - getRestrictingTag( - moveId: Moves, - user?: Pokemon, - target?: Pokemon, - ): MoveRestrictionBattlerTag | null { - for (const tag of this.findTags( - t => t instanceof MoveRestrictionBattlerTag, - )) { + getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null { + for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) { return tag as MoveRestrictionBattlerTag; } - if ( - user && - target && - (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted( - moveId, - user, - target, - ) - ) { + if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return tag as MoveRestrictionBattlerTag; } } @@ -5136,9 +4373,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getLastXMoves(moveCount = 1): TurnMove[] { const moveHistory = this.getMoveHistory(); if (moveCount >= 0) { - return moveHistory - .slice(Math.max(moveHistory.length - moveCount, 0)) - .reverse(); + return moveHistory.slice(Math.max(moveHistory.length - moveCount, 0)).reverse(); } return moveHistory.slice(0).reverse(); } @@ -5196,39 +4431,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.loadAssets().then(() => { this.calculateStats(); globalScene.updateModifiers(this.isPlayer(), true); - Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then( - () => resolve(), - ); + Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); }); }); } - cry( - soundConfig?: Phaser.Types.Sound.SoundConfig, - sceneOverride?: BattleScene, - ): AnySound { + cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound { const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed? const cry = this.getSpeciesForm(undefined, true).cry(soundConfig); let duration = cry.totalDuration * 1000; - if ( - this.fusionSpecies && - this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true) - ) { + if (this.fusionSpecies && this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true)) { let fusionCry = this.getFusionSpeciesForm(undefined, true).cry(soundConfig, true); duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => { try { - SoundFade.fadeOut( - scene, - cry, - fixedInt(Math.ceil(duration * 0.2)), - ); + SoundFade.fadeOut(scene, cry, fixedInt(Math.ceil(duration * 0.2))); fusionCry = this.getFusionSpeciesForm(undefined, true).cry( - Object.assign( - { seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, - soundConfig, - ), + Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig), ); SoundFade.fadeIn( scene, @@ -5248,10 +4468,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // biome-ignore lint: there are a ton of issues.. faintCry(callback: Function): void { - if ( - this.fusionSpecies && - this.getSpeciesForm() !== this.getFusionSpeciesForm() - ) { + if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) { return this.fusionFaintCry(callback); } @@ -5271,32 +4488,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer: Phaser.Time.TimerEvent | null = - globalScene.time.addEvent({ - delay: fixedInt(delay), - repeat: -1, - callback: () => { - frameThreshold = sprite.anims.msPerFrame / rate; - frameProgress += delay; - while (frameProgress > frameThreshold) { - if (sprite.anims.duration) { - sprite.anims.nextFrame(); - tintSprite?.anims.nextFrame(); - } - frameProgress -= frameThreshold; + let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ + delay: fixedInt(delay), + repeat: -1, + callback: () => { + frameThreshold = sprite.anims.msPerFrame / rate; + frameProgress += delay; + while (frameProgress > frameThreshold) { + if (sprite.anims.duration) { + sprite.anims.nextFrame(); + tintSprite?.anims.nextFrame(); } - if (cry && !cry.pendingRemove) { - rate *= 0.99; - cry.setRate(rate); - } else { - faintCryTimer?.destroy(); - faintCryTimer = null; - if (callback) { - callback(); - } + frameProgress -= frameThreshold; + } + if (cry && !cry.pendingRemove) { + rate *= 0.99; + cry.setRate(rate); + } else { + faintCryTimer?.destroy(); + faintCryTimer = null; + if (callback) { + callback(); } - }, - }); + } + }, + }); // Failsafe globalScene.time.delayedCall(fixedInt(3000), () => { @@ -5356,61 +4572,53 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer: Phaser.Time.TimerEvent | null = - globalScene.time.addEvent({ - delay: fixedInt(delay), - repeat: -1, - callback: () => { - ++i; - frameThreshold = sprite.anims.msPerFrame / rate; - frameProgress += delay; - while (frameProgress > frameThreshold) { - if (sprite.anims.duration) { - sprite.anims.nextFrame(); - tintSprite?.anims.nextFrame(); - } - frameProgress -= frameThreshold; + let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ + delay: fixedInt(delay), + repeat: -1, + callback: () => { + ++i; + frameThreshold = sprite.anims.msPerFrame / rate; + frameProgress += delay; + while (frameProgress > frameThreshold) { + if (sprite.anims.duration) { + sprite.anims.nextFrame(); + tintSprite?.anims.nextFrame(); } - if (i === transitionIndex && fusionCryKey) { - SoundFade.fadeOut( - globalScene, - cry, - fixedInt(Math.ceil((duration / rate) * 0.2)), - ); - fusionCry = globalScene.playSound( - fusionCryKey, - Object.assign({ - seek: Math.max(fusionCry.totalDuration * 0.4, 0), - rate: rate, - }), - ); - SoundFade.fadeIn( - globalScene, - fusionCry, - fixedInt(Math.ceil((duration / rate) * 0.2)), - globalScene.masterVolume * globalScene.fieldVolume, - 0, - ); + frameProgress -= frameThreshold; + } + if (i === transitionIndex && fusionCryKey) { + SoundFade.fadeOut(globalScene, cry, fixedInt(Math.ceil((duration / rate) * 0.2))); + fusionCry = globalScene.playSound( + fusionCryKey, + Object.assign({ + seek: Math.max(fusionCry.totalDuration * 0.4, 0), + rate: rate, + }), + ); + SoundFade.fadeIn( + globalScene, + fusionCry, + fixedInt(Math.ceil((duration / rate) * 0.2)), + globalScene.masterVolume * globalScene.fieldVolume, + 0, + ); + } + rate *= 0.99; + if (cry && !cry.pendingRemove) { + cry.setRate(rate); + } + if (fusionCry && !fusionCry.pendingRemove) { + fusionCry.setRate(rate); + } + if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) { + faintCryTimer?.destroy(); + faintCryTimer = null; + if (callback) { + callback(); } - rate *= 0.99; - if (cry && !cry.pendingRemove) { - cry.setRate(rate); - } - if (fusionCry && !fusionCry.pendingRemove) { - fusionCry.setRate(rate); - } - if ( - (!cry || cry.pendingRemove) && - (!fusionCry || fusionCry.pendingRemove) - ) { - faintCryTimer?.destroy(); - faintCryTimer = null; - if (callback) { - callback(); - } - } - }, - }); + } + }, + }); // Failsafe globalScene.time.delayedCall(fixedInt(3000), () => { @@ -5433,8 +4641,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isOppositeGender(pokemon: Pokemon): boolean { return ( this.gender !== Gender.GENDERLESS && - pokemon.gender === - (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE) + pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE) ); } @@ -5442,11 +4649,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!effect || quiet) { return; } - const message = effect && this.status?.effect === effect - ? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)) - : i18next.t("abilityTriggers:moveImmunity", { - pokemonNameWithAffix: getPokemonNameWithAffix(this), - }); + const message = + effect && this.status?.effect === effect + ? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)) + : i18next.t("abilityTriggers:moveImmunity", { + pokemonNameWithAffix: getPokemonNameWithAffix(this), + }); globalScene.queueMessage(message); } @@ -5471,11 +4679,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.queueImmuneMessage(quiet, effect); return false; } - if ( - this.isGrounded() && - !ignoreField && - globalScene.arena.terrain?.terrainType === TerrainType.MISTY - ) { + if (this.isGrounded() && !ignoreField && globalScene.arena.terrain?.terrainType === TerrainType.MISTY) { this.queueImmuneMessage(quiet, effect); return false; } @@ -5485,7 +4689,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { switch (effect) { case StatusEffect.POISON: - case StatusEffect.TOXIC: + case StatusEffect.TOXIC: { // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity const poisonImmunity = types.map(defType => { // Check if the Pokemon is not immune to Poison/Toxic @@ -5496,20 +4700,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity const cancelImmunity = new BooleanHolder(false); if (sourcePokemon) { - applyAbAttrs( - IgnoreTypeStatusEffectImmunityAbAttr, - sourcePokemon, - cancelImmunity, - false, - effect, - defType, - ); + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); if (cancelImmunity.value) { return false; } } - return true; + return true; }); if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) { @@ -5519,6 +4716,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } break; + } case StatusEffect.PARALYSIS: if (this.isOfType(PokemonType.ELECTRIC)) { this.queueImmuneMessage(quiet, effect); @@ -5526,10 +4724,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case StatusEffect.SLEEP: - if ( - this.isGrounded() && - globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC - ) { + if (this.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { this.queueImmuneMessage(quiet, effect); return false; } @@ -5539,9 +4734,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.isOfType(PokemonType.ICE) || (!ignoreField && globalScene?.arena?.weather?.weatherType && - [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes( - globalScene.arena.weather.weatherType, - )) + [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(globalScene.arena.weather.weatherType)) ) { this.queueImmuneMessage(quiet, effect); return false; @@ -5556,13 +4749,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const cancelled = new BooleanHolder(false); - applyPreSetStatusAbAttrs( - StatusEffectImmunityAbAttr, - this, - effect, - cancelled, - quiet, - ); + applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet); if (cancelled.value) { return false; } @@ -5573,8 +4760,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { pokemon, effect, cancelled, - quiet, this, sourcePokemon, - ) + quiet, + this, + sourcePokemon, + ); if (cancelled.value) { break; } @@ -5584,15 +4773,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } - if ( - sourcePokemon && - sourcePokemon !== this && - this.isSafeguarded(sourcePokemon) - ) { - if(!quiet){ - globalScene.queueMessage( - i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) - })); + if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { + if (!quiet) { + globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) })); } return false; } @@ -5633,13 +4816,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.resetStatus(false); } globalScene.unshiftPhase( - new ObtainStatusEffectPhase( - this.getBattlerIndex(), - effect, - turnsRemaining, - sourceText, - sourcePokemon, - ), + new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), ); return true; } @@ -5725,9 +4902,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise. */ isSafeguarded(attacker: Pokemon): boolean { - const defendingSide = this.isPlayer() - ? ArenaTagSide.PLAYER - : ArenaTagSide.ENEMY; + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { const bypassed = new BooleanHolder(false); if (attacker) { @@ -5745,18 +4920,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fieldSetup(resetSummonData?: boolean): void { this.setSwitchOutStatus(false); if (globalScene) { - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangePostMoveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangePostMoveTrigger, true); } // If this Pokemon has a Substitute when loading in, play an animation to add its sprite if (this.getTag(SubstituteTag)) { - globalScene.triggerPokemonBattleAnim( - this, - PokemonAnimType.SUBSTITUTE_ADD, - ); + globalScene.triggerPokemonBattleAnim(this, PokemonAnimType.SUBSTITUTE_ADD); this.getTag(SubstituteTag)!.sourceInFocus = false; } @@ -5786,7 +4954,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.summonData = new PokemonSummonData(); this.tempSummonData = new PokemonTempSummonData(); - this.summonData.illusion = illusion + this.summonData.illusion = illusion; this.updateInfo(); } @@ -5794,7 +4962,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Reset a {@linkcode Pokemon}'s per-battle {@linkcode PokemonBattleData | battleData}, * as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave. * Should be called once per arena transition (new biome/trainer battle/Mystery Encounter). - */ + */ resetBattleAndWaveData(): void { this.battleData = new PokemonBattleData(); this.resetWaveData(); @@ -5815,10 +4983,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.stellarTypesBoosted = []; if (wasTerastallized) { this.updateSpritePipelineData(); - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeLapseTeraTrigger, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeLapseTeraTrigger); } } @@ -5836,18 +5001,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { try { this.getSprite().play(this.getBattleSpriteKey()); } catch (err: unknown) { - console.error( - `Failed to play animation for ${this.getBattleSpriteKey()}`, - err, - ); + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); } try { this.getTintSprite()?.play(this.getBattleSpriteKey()); } catch (err: unknown) { - console.error( - `Failed to play animation for ${this.getBattleSpriteKey()}`, - err, - ); + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); } } @@ -5898,9 +5057,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.x * this.parentContainer.scale + this.parentContainer.x, this.y * this.parentContainer.scale + this.parentContainer.y, ); - this.maskSprite?.setScale( - this.getSpriteScale() * this.parentContainer.scale, - ); + this.maskSprite?.setScale(this.getSpriteScale() * this.parentContainer.scale); this.maskEnabled = true; } } @@ -5926,12 +5083,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[ - `spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = []; - s.pipelineData[ - `fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = []; + s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; + s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; }); return; } @@ -5946,12 +5099,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant, ); const backSpriteKey = speciesForm - .getSpriteKey( - this.getGender(ignoreOveride) === Gender.FEMALE, - speciesForm.formIndex, - this.shiny, - this.variant, - ) + .getSpriteKey(this.getGender(ignoreOveride) === Gender.FEMALE, speciesForm.formIndex, this.shiny, this.variant) .replace("pkmn__", "pkmn__back__"); const fusionSpriteKey = fusionSpeciesForm.getSpriteKey( this.getFusionGender(ignoreOveride) === Gender.FEMALE, @@ -5994,40 +5142,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const spriteColors: number[][] = []; const pixelData: Uint8ClampedArray[] = []; - [canvas, backCanvas, fusionCanvas, fusionBackCanvas].forEach( - (canv: HTMLCanvasElement, c: number) => { - const context = canv.getContext("2d"); - const frame = [ - sourceFrame, - sourceBackFrame, - fusionFrame, - fusionBackFrame, - ][c]; - canv.width = frame.width; - canv.height = frame.height; + [canvas, backCanvas, fusionCanvas, fusionBackCanvas].forEach((canv: HTMLCanvasElement, c: number) => { + const context = canv.getContext("2d"); + const frame = [sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame][c]; + canv.width = frame.width; + canv.height = frame.height; - if (context) { - context.drawImage( - [sourceImage, sourceBackImage, fusionImage, fusionBackImage][c], - frame.cutX, - frame.cutY, - frame.width, - frame.height, - 0, - 0, - frame.width, - frame.height, - ); - const imageData = context.getImageData( - frame.cutX, - frame.cutY, - frame.width, - frame.height, - ); - pixelData.push(imageData.data); - } - }, - ); + if (context) { + context.drawImage( + [sourceImage, sourceBackImage, fusionImage, fusionBackImage][c], + frame.cutX, + frame.cutY, + frame.width, + frame.height, + 0, + 0, + frame.width, + frame.height, + ); + const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height); + pixelData.push(imageData.data); + } + }); for (let f = 0; f < 2; f++) { const variantColors = variantColorCache[!f ? spriteKey : backSpriteKey]; @@ -6036,9 +5172,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { Object.keys(variantColors[this.variant]).forEach(k => { variantColorSet.set( rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), - Array.from( - Object.values(rgbHexToRgba(variantColors[this.variant][k])), - ), + Array.from(Object.values(rgbHexToRgba(variantColors[this.variant][k]))), ); }); } @@ -6068,9 +5202,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const pixelColors: number[] = []; for (let f = 0; f < 2; f++) { for (let i = 0; i < pixelData[f].length; i += 4) { - const total = pixelData[f] - .slice(i, i + 3) - .reduce((total: number, value: number) => total + value, 0); + const total = pixelData[f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -6087,29 +5219,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fusionPixelColors: number[] = []; for (let f = 0; f < 2; f++) { - const variantColors = - variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; + const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; const variantColorSet = new Map(); - if ( - this.fusionShiny && - variantColors && - variantColors[this.fusionVariant] - ) { + if (this.fusionShiny && variantColors && variantColors[this.fusionVariant]) { for (const k of Object.keys(variantColors[this.fusionVariant])) { variantColorSet.set( rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), - Array.from( - Object.values( - rgbHexToRgba(variantColors[this.fusionVariant][k]), - ), - ), + Array.from(Object.values(rgbHexToRgba(variantColors[this.fusionVariant][k]))), ); } } for (let i = 0; i < pixelData[2 + f].length; i += 4) { - const total = pixelData[2 + f] - .slice(i, i + 3) - .reduce((total: number, value: number) => total + value, 0); + const total = pixelData[2 + f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -6157,101 +5278,94 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { paletteColors = paletteColors!; // erroneously tell TS compiler that paletteColors is defined! fusionPaletteColors = fusionPaletteColors!; // mischievously misinform TS compiler that fusionPaletteColors is defined! - const [palette, fusionPalette] = [paletteColors, fusionPaletteColors].map( - paletteColors => { - let keys = Array.from(paletteColors.keys()).sort( - (a: number, b: number) => - paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, - ); - let rgbaColors: Map; - let hsvColors: Map; + const [palette, fusionPalette] = [paletteColors, fusionPaletteColors].map(paletteColors => { + let keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => + paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + ); + let rgbaColors: Map; + let hsvColors: Map; - const mappedColors = new Map(); + const mappedColors = new Map(); - do { - mappedColors.clear(); + do { + mappedColors.clear(); - rgbaColors = keys.reduce((map: Map, k: number) => { - map.set(k, Object.values(rgbaFromArgb(k))); - return map; - }, new Map()); - hsvColors = Array.from(rgbaColors.keys()).reduce( - (map: Map, k: number) => { - const rgb = rgbaColors.get(k)!.slice(0, 3); - map.set(k, rgbToHsv(rgb[0], rgb[1], rgb[2])); - return map; - }, - new Map(), - ); + rgbaColors = keys.reduce((map: Map, k: number) => { + map.set(k, Object.values(rgbaFromArgb(k))); + return map; + }, new Map()); + hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map, k: number) => { + const rgb = rgbaColors.get(k)!.slice(0, 3); + map.set(k, rgbToHsv(rgb[0], rgb[1], rgb[2])); + return map; + }, new Map()); - for (let c = keys.length - 1; c >= 0; c--) { - const hsv = hsvColors.get(keys[c])!; - for (let c2 = 0; c2 < c; c2++) { - const hsv2 = hsvColors.get(keys[c2])!; - const diff = Math.abs(hsv[0] - hsv2[0]); - if (diff < 30 || diff >= 330) { - if (mappedColors.has(keys[c])) { - mappedColors.get(keys[c])!.push(keys[c2]); - } else { - mappedColors.set(keys[c], [keys[c2]]); - } - break; + for (let c = keys.length - 1; c >= 0; c--) { + const hsv = hsvColors.get(keys[c])!; + for (let c2 = 0; c2 < c; c2++) { + const hsv2 = hsvColors.get(keys[c2])!; + const diff = Math.abs(hsv[0] - hsv2[0]); + if (diff < 30 || diff >= 330) { + if (mappedColors.has(keys[c])) { + mappedColors.get(keys[c])!.push(keys[c2]); + } else { + mappedColors.set(keys[c], [keys[c2]]); } + break; + } + } + } + + mappedColors.forEach((values: number[], key: number) => { + const keyColor = rgbaColors.get(key)!; + const valueColors = values.map(v => rgbaColors.get(v)!); + const color = keyColor.slice(0); + let count = paletteColors.get(key)!; + for (const value of values) { + const valueCount = paletteColors.get(value); + if (!valueCount) { + continue; + } + count += valueCount; + } + + for (let c = 0; c < 3; c++) { + color[c] *= paletteColors.get(key)! / count; + values.forEach((value: number, i: number) => { + if (paletteColors.has(value)) { + const valueCount = paletteColors.get(value)!; + color[c] += valueColors[i][c] * (valueCount / count); + } + }); + color[c] = Math.round(color[c]); + } + + paletteColors.delete(key); + for (const value of values) { + paletteColors.delete(value); + if (mappedColors.has(value)) { + mappedColors.delete(value); } } - mappedColors.forEach((values: number[], key: number) => { - const keyColor = rgbaColors.get(key)!; - const valueColors = values.map(v => rgbaColors.get(v)!); - const color = keyColor.slice(0); - let count = paletteColors.get(key)!; - for (const value of values) { - const valueCount = paletteColors.get(value); - if (!valueCount) { - continue; - } - count += valueCount; - } - - for (let c = 0; c < 3; c++) { - color[c] *= paletteColors.get(key)! / count; - values.forEach((value: number, i: number) => { - if (paletteColors.has(value)) { - const valueCount = paletteColors.get(value)!; - color[c] += valueColors[i][c] * (valueCount / count); - } - }); - color[c] = Math.round(color[c]); - } - - paletteColors.delete(key); - for (const value of values) { - paletteColors.delete(value); - if (mappedColors.has(value)) { - mappedColors.delete(value); - } - } - - paletteColors.set( - argbFromRgba({ - r: color[0], - g: color[1], - b: color[2], - a: color[3], - }), - count, - ); - }); - - keys = Array.from(paletteColors.keys()).sort( - (a: number, b: number) => - paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + paletteColors.set( + argbFromRgba({ + r: color[0], + g: color[1], + b: color[2], + a: color[3], + }), + count, ); - } while (mappedColors.size); + }); - return keys.map(c => Object.values(rgbaFromArgb(c))); - }, - ); + keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => + paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + ); + } while (mappedColors.size); + + return keys.map(c => Object.values(rgbaFromArgb(c))); + }); const paletteDeltas: number[][] = []; @@ -6274,10 +5388,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const ratio = easeFunc(delta / 255); const color = [0, 0, 0, fusionSpriteColors[sc][3]]; for (let c = 0; c < 3; c++) { - color[c] = Math.round( - fusionSpriteColors[sc][c] * ratio + - fusionPalette[paletteIndex][c] * (1 - ratio), - ); + color[c] = Math.round(fusionSpriteColors[sc][c] * ratio + fusionPalette[paletteIndex][c] * (1 - ratio)); } fusionSpriteColors[sc] = color; } @@ -6286,12 +5397,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[ - `spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = spriteColors; - s.pipelineData[ - `fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = fusionSpriteColors; + s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = spriteColors; + s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = + fusionSpriteColors; }); canvas.remove(); @@ -6311,9 +5419,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ randBattleSeedInt(range: number, min = 0): number { - return globalScene.currentBattle - ? globalScene.randBattleSeedInt(range, min) - : randSeedInt(range, min); + return globalScene.currentBattle ? globalScene.randBattleSeedInt(range, min) : randSeedInt(range, min); } /** @@ -6323,9 +5429,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns a random integer between {@linkcode min} and {@linkcode max} inclusive */ randBattleSeedIntRange(min: number, max: number): number { - return globalScene.currentBattle - ? globalScene.randBattleSeedInt(max - min + 1, min) - : randSeedIntRange(min, max); + return globalScene.currentBattle ? globalScene.randBattleSeedInt(max - min + 1, min) : randSeedIntRange(min, max); } /** @@ -6353,11 +5457,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Trigger abilities that activate upon leaving the field applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this); this.setSwitchOutStatus(true); - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeActiveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); globalScene.field.remove(this, destroy); } @@ -6379,10 +5479,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hasSameAbilityInRootForm(abilityIndex: number): boolean { const currentAbilityIndex = this.abilityIndex; const rootForm = getPokemonSpecies(this.species.getRootSpeciesId()); - return ( - rootForm.getAbility(abilityIndex) === - rootForm.getAbility(currentAbilityIndex) - ); + return rootForm.getAbility(abilityIndex) === rootForm.getAbility(currentAbilityIndex); } /** @@ -6411,23 +5508,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. * @returns `true` if the item was removed successfully, `false` otherwise. */ - public loseHeldItem( - heldItem: PokemonHeldItemModifier, - forBattle = true, - ): boolean { + public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean { if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { return false; } - heldItem.stackCount--; - if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, !this.isPlayer()); - } - if (forBattle) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); - } + heldItem.stackCount--; + if (heldItem.stackCount <= 0) { + globalScene.removeModifier(heldItem, !this.isPlayer()); + } + if (forBattle) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); + } - return true; + return true; } /** @@ -6436,17 +5530,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param berryType The type of berry being eaten. * @param updateHarvest Whether to track the berry for harvest; default `true`. */ - public recordEatenBerry(berryType: BerryType, updateHarvest: boolean = true) { + public recordEatenBerry(berryType: BerryType, updateHarvest = true) { this.battleData.hasEatenBerry = true; if (updateHarvest) { // Only track for harvest if we actually consumed the berry - this.battleData.berriesEaten.push(berryType) + this.battleData.berriesEaten.push(berryType); } this.turnData.berriesEaten.push(berryType); } } export class PlayerPokemon extends Pokemon { + protected battleInfo: PlayerBattleInfo; public compatibleTms: Moves[]; constructor( @@ -6461,20 +5556,7 @@ export class PlayerPokemon extends Pokemon { nature?: Nature, dataSource?: Pokemon | PokemonData, ) { - super( - 106, - 148, - species, - level, - abilityIndex, - formIndex, - gender, - shiny, - variant, - ivs, - nature, - dataSource, - ); + super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); @@ -6537,17 +5619,13 @@ export class PlayerPokemon extends Pokemon { if (Array.isArray(p)) { const [pkm, form] = p; if ( - (pkm === this.species.speciesId || - (this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) && + (pkm === this.species.speciesId || (this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) && form === this.getFormKey() ) { compatible = true; break; } - } else if ( - p === this.species.speciesId || - (this.fusionSpecies && p === this.fusionSpecies.speciesId) - ) { + } else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) { compatible = true; break; } @@ -6565,8 +5643,7 @@ export class PlayerPokemon extends Pokemon { if ( !this.getSpeciesForm().validateStarterMoveset( moveset, - globalScene.gameData.starterData[this.species.getRootSpeciesId()] - .eggMoves, + globalScene.gameData.starterData[this.species.getRootSpeciesId()].eggMoves, ) ) { return false; @@ -6592,18 +5669,10 @@ export class PlayerPokemon extends Pokemon { UiMode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), - (slotIndex: number, option: PartyOption) => { - if ( - slotIndex >= globalScene.currentBattle.getBattlerCount() && - slotIndex < 6 - ) { + (slotIndex: number, _option: PartyOption) => { + if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { globalScene.prependToPhase( - new SwitchSummonPhase( - switchType, - this.getFieldIndex(), - slotIndex, - false, - ), + new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase, ); } @@ -6617,23 +5686,13 @@ export class PlayerPokemon extends Pokemon { addFriendship(friendship: number): void { if (friendship > 0) { const starterSpeciesId = this.species.getRootSpeciesId(); - const fusionStarterSpeciesId = - this.isFusion() && this.fusionSpecies - ? this.fusionSpecies.getRootSpeciesId() - : 0; + const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0; const starterData = [ globalScene.gameData.starterData[starterSpeciesId], - fusionStarterSpeciesId - ? globalScene.gameData.starterData[fusionStarterSpeciesId] - : null, + fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); const amount = new NumberHolder(friendship); - globalScene.applyModifier( - PokemonFriendshipBoosterModifier, - true, - this, - amount, - ); + globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? timedEventManager.getClassicFriendshipMultiplier() : 1; @@ -6642,11 +5701,7 @@ export class PlayerPokemon extends Pokemon { ? 1.5 // Divide candy gain for fusions by 1.5 during events : 2 // 2 for fusions outside events : 1; // 1 for non-fused mons - const starterAmount = new NumberHolder( - Math.floor( - (amount.value * candyFriendshipMultiplier) / fusionReduction, - ), - ); + const starterAmount = new NumberHolder(Math.floor((amount.value * candyFriendshipMultiplier) / fusionReduction)); // Add friendship to this PlayerPokemon this.friendship = Math.min(this.friendship + amount.value, 255); @@ -6655,14 +5710,9 @@ export class PlayerPokemon extends Pokemon { } // Add to candy progress for this mon's starter species and its fused species (if it has one) starterData.forEach((sd: StarterDataEntry, i: number) => { - const speciesId = !i - ? starterSpeciesId - : (fusionStarterSpeciesId as Species); + const speciesId = !i ? starterSpeciesId : (fusionStarterSpeciesId as Species); sd.friendship = (sd.friendship || 0) + starterAmount.value; - if ( - sd.friendship >= - getStarterValueFriendshipCap(speciesStarterCosts[speciesId]) - ) { + if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) { globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); sd.friendship = 0; } @@ -6673,9 +5723,7 @@ export class PlayerPokemon extends Pokemon { } } - getPossibleEvolution( - evolution: SpeciesFormEvolution | null, - ): Promise { + getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise { if (!evolution) { return new Promise(resolve => resolve(this)); } @@ -6690,9 +5738,7 @@ export class PlayerPokemon extends Pokemon { this.fusionFormIndex = evolution.evoFormKey !== null ? Math.max( - evolutionSpecies.forms.findIndex( - f => f.formKey === evolution.evoFormKey, - ), + evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0, ) : this.fusionFormIndex; @@ -6714,9 +5760,7 @@ export class PlayerPokemon extends Pokemon { const formIndex = evolution.evoFormKey !== null && !isFusion ? Math.max( - evolutionSpecies.forms.findIndex( - f => f.formKey === evolution.evoFormKey, - ), + evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0, ) : this.formIndex; @@ -6737,10 +5781,7 @@ export class PlayerPokemon extends Pokemon { }); } - evolve( - evolution: SpeciesFormEvolution | null, - preEvolution: PokemonSpeciesForm, - ): Promise { + evolve(evolution: SpeciesFormEvolution | null, preEvolution: PokemonSpeciesForm): Promise { if (!evolution) { return new Promise(resolve => resolve()); } @@ -6756,10 +5797,9 @@ export class PlayerPokemon extends Pokemon { } if (evolution.preFormKey !== null) { const formIndex = Math.max( - (!isFusion || !this.fusionSpecies - ? this.species - : this.fusionSpecies - ).forms.findIndex(f => f.formKey === evolution.evoFormKey), + (!isFusion || !this.fusionSpecies ? this.species : this.fusionSpecies).forms.findIndex( + f => f.formKey === evolution.evoFormKey, + ), 0, ); if (!isFusion) { @@ -6774,18 +5814,12 @@ export class PlayerPokemon extends Pokemon { const preEvoAbilityCount = preEvolution.getAbilityCount(); if ([0, 1, 2].includes(this.abilityIndex)) { // Handles cases where a Pokemon with 3 abilities evolves into a Pokemon with 2 abilities (ie: Eevee -> any Eeveelution) - if ( - this.abilityIndex === 2 && - preEvoAbilityCount === 3 && - abilityCount === 2 - ) { + if (this.abilityIndex === 2 && preEvoAbilityCount === 3 && abilityCount === 2) { this.abilityIndex = 1; } } else { // Prevent pokemon with an illegal ability value from breaking things - console.warn( - "this.abilityIndex is somehow an illegal value, please report this", - ); + console.warn("this.abilityIndex is somehow an illegal value, please report this"); console.warn(this.abilityIndex); this.abilityIndex = 0; } @@ -6794,17 +5828,11 @@ export class PlayerPokemon extends Pokemon { const abilityCount = this.getFusionSpeciesForm().getAbilityCount(); const preEvoAbilityCount = preEvolution.getAbilityCount(); if ([0, 1, 2].includes(this.fusionAbilityIndex)) { - if ( - this.fusionAbilityIndex === 2 && - preEvoAbilityCount === 3 && - abilityCount === 2 - ) { + if (this.fusionAbilityIndex === 2 && preEvoAbilityCount === 3 && abilityCount === 2) { this.fusionAbilityIndex = 1; } } else { - console.warn( - "this.fusionAbilityIndex is somehow an illegal value, please report this", - ); + console.warn("this.fusionAbilityIndex is somehow an illegal value, please report this"); console.warn(this.fusionAbilityIndex); this.fusionAbilityIndex = 0; } @@ -6818,22 +5846,15 @@ export class PlayerPokemon extends Pokemon { }); }; if (preEvolution.speciesId === Species.GIMMIGHOUL) { - const evotracker = - this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? - null; + const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; if (evotracker) { globalScene.removeModifier(evotracker); } } if (!globalScene.gameMode.isDaily || this.metBiome > -1) { - globalScene.gameData.updateSpeciesDexIvs( - this.species.speciesId, - this.ivs, - ); + globalScene.gameData.updateSpeciesDexIvs(this.species.speciesId, this.ivs); globalScene.gameData.setPokemonSeen(this, false); - globalScene.gameData - .setPokemonCaught(this, false) - .then(() => updateAndResolve()); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -6844,10 +5865,7 @@ export class PlayerPokemon extends Pokemon { const isFusion = evolution instanceof FusionSpeciesFormEvolution; const evoSpecies = !isFusion ? this.species : this.fusionSpecies; - if ( - evoSpecies?.speciesId === Species.NINCADA && - evolution.speciesId === Species.NINJASK - ) { + if (evoSpecies?.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) { const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; if (newEvolution.condition?.predicate(this)) { @@ -6883,12 +5901,7 @@ export class PlayerPokemon extends Pokemon { newPokemon.evoCounter = this.evoCounter; globalScene.getPlayerParty().push(newPokemon); - newPokemon.evolve( - !isFusion - ? newEvolution - : new FusionSpeciesFormEvolution(this.id, newEvolution), - evoSpecies, - ); + newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); const modifiers = globalScene.findModifiers( m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, true, @@ -6949,9 +5962,7 @@ export class PlayerPokemon extends Pokemon { }; if (!globalScene.gameMode.isDaily || this.metBiome > -1) { globalScene.gameData.setPokemonSeen(this, false); - globalScene.gameData - .setPokemonCaught(this, false) - .then(() => updateAndResolve()); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -6986,8 +5997,7 @@ export class PlayerPokemon extends Pokemon { // Store the average HP% that each Pokemon has const maxHp = this.getMaxHp(); - const newHpPercent = - (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2; + const newHpPercent = (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2; this.generateName(); this.calculateStats(); @@ -7018,15 +6028,7 @@ export class PlayerPokemon extends Pokemon { true, ) as PokemonHeldItemModifier[]; for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItemModifier( - modifier, - this, - false, - modifier.getStackCount(), - true, - true, - false, - ); + globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); } globalScene.updateModifiers(true, true); globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); @@ -7034,11 +6036,7 @@ export class PlayerPokemon extends Pokemon { const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); pokemon .getMoveset(true) - .map((m: PokemonMove) => - globalScene.unshiftPhase( - new LearnMovePhase(newPartyMemberIndex, m.getMove().id), - ), - ); + .map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); pokemon.destroy(); this.updateFusionPalette(); } @@ -7064,6 +6062,7 @@ export class PlayerPokemon extends Pokemon { } export class EnemyPokemon extends Pokemon { + protected battleInfo: EnemyBattleInfo; public trainerSlot: TrainerSlot; public aiType: AiType; public bossSegments: number; @@ -7140,17 +6139,13 @@ export class EnemyPokemon extends Pokemon { } } - this.luck = - (this.shiny ? this.variant + 1 : 0) + - (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); let prevolution: Species; let speciesId = species.speciesId; while ((prevolution = pokemonPrevolutions[speciesId])) { const evolution = pokemonEvolutions[prevolution].find( - pe => - pe.speciesId === speciesId && - (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), + pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), ); if (evolution?.condition?.enforceFunc) { evolution.condition.enforceFunc(this); @@ -7168,8 +6163,7 @@ export class EnemyPokemon extends Pokemon { } } - this.aiType = - boss || this.hasTrainer() ? AiType.SMART : AiType.SMART_RANDOM; + this.aiType = boss || this.hasTrainer() ? AiType.SMART : AiType.SMART_RANDOM; } initBattleInfo(): void { @@ -7193,12 +6187,7 @@ export class EnemyPokemon extends Pokemon { if (boss) { this.bossSegments = bossSegments || - globalScene.getEncounterBossSegments( - globalScene.currentBattle.waveIndex, - this.level, - this.species, - true, - ); + globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true); this.bossSegmentIndex = this.bossSegments - 1; } else { this.bossSegments = 0; @@ -7245,23 +6234,21 @@ export class EnemyPokemon extends Pokemon { * the Pokemon the move will target. * @returns this Pokemon's next move in the format {move, moveTargets} */ - // TODO: Refactor this and split it up + // TODO: split this up and move it elsewhere getNextMove(): TurnMove { // If this Pokemon has a move already queued, return it. - for (const queuedMove of this.getMoveQueue()) { + const moveQueue = this.getMoveQueue(); + for (const [i, queuedMove] of moveQueue.entries()) { const moveIndex = this.getMoveset().findIndex(m => m.moveId === queuedMove.move); // If the queued move was called indirectly, ignore all PP and usability checks. // Otherwise, ensure that the move being used is actually usable // TODO: Virtual moves shouldn't use the move queue if ( isVirtual(queuedMove.useType) || - (moveIndex > -1 && - this.getMoveset()[moveIndex].isUsable( - this, - isIgnorePP(queuedMove.useType)) - ) + (moveIndex > -1 && this.getMoveset()[moveIndex].isUsable(this, isIgnorePP(queuedMove.useType))) ) { - return queuedMove; + moveQueue.splice(0, i) // TODO: This may be redundant and definitely should not be done here + return queuedMove; } } @@ -7287,11 +6274,13 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: // No enemy should spawn with this AI type in-game + // No enemy should spawn with this AI type in-game + case AiType.RANDOM: { const moveId = movePool[globalScene.randBattleSeedInt(movePool.length)].moveId; - return { move: moveId, targets: this.getNextTargets(moveId), useType: MoveUseType.NORMAL }; + return { move: moveId, targets: this.getNextTargets(moveId), useType: MoveUseType.NORMAL }; + } case AiType.SMART_RANDOM: - case AiType.SMART: + case AiType.SMART: { /** * Search this Pokemon's move pool for moves that will KO an opposing target. * If there are any moves that can KO an opponent (i.e. a player Pokemon), @@ -7312,20 +6301,14 @@ export class EnemyPokemon extends Pokemon { .targets.map(ind => fieldPokemon[ind]) .filter(p => this.isPlayer() !== p.isPlayer()); // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus - const isCritical = - move.hasAttr(CritOnlyAttr) || - !!this.getTag(BattlerTagType.ALWAYS_CRIT); + const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); return ( move.category !== MoveCategory.STATUS && moveTargets.some(p => { const doesNotFail = move.applyConditions(this, p, move) || - [ - Moves.SUCKER_PUNCH, - Moves.UPPER_HAND, - Moves.THUNDERCLAP, - ].includes(move.id); + [Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id); return ( doesNotFail && p.getAttackDamage({ @@ -7336,8 +6319,7 @@ export class EnemyPokemon extends Pokemon { ignoreAllyAbility: !p.getAlly()?.waveData.abilityRevealed, ignoreSourceAllyAbility: false, isCritical, - } - ).damage >= p.hp + }).damage >= p.hp ); }) ); @@ -7353,7 +6335,7 @@ export class EnemyPokemon extends Pokemon { * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. */ const moveScores = movePool.map(() => 0); - const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ])); + const moveTargets = Object.fromEntries(movePool.map(m => [m.moveId, this.getNextTargets(m.moveId)])); for (const m in movePool) { const pokemonMove = movePool[m]; const move = pokemonMove.getMove(); @@ -7374,8 +6356,7 @@ export class EnemyPokemon extends Pokemon { */ let targetScore = move.getUserBenefitScore(this, target, move) + - move.getTargetBenefitScore(this, target, move) * - (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); if (Number.isNaN(targetScore)) { console.error(`Move ${move.name} returned score of NaN`); targetScore = 0; @@ -7385,27 +6366,23 @@ export class EnemyPokemon extends Pokemon { * target score to -20 */ if ( - (move.name.endsWith(" (N)") || - !move.applyConditions(this, target, move)) && - ![ - Moves.SUCKER_PUNCH, - Moves.UPPER_HAND, - Moves.THUNDERCLAP, - ].includes(move.id) + (move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && + ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id) ) { targetScore = -20; } else if (move instanceof AttackMove) { /** * Attack moves are given extra multipliers to their base benefit score based on * the move's type effectiveness against the target and whether the move is a STAB move. - */ + */ const effectiveness = target.getMoveEffectiveness( this, move, !target.waveData.abilityRevealed, undefined, undefined, - true); + true, + ); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; @@ -7444,18 +6421,14 @@ export class EnemyPokemon extends Pokemon { let r = 0; if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while ( - r < sortedMovePool.length - 1 && - globalScene.randBattleSeedInt(8) >= 5 - ) { + while (r < sortedMovePool.length - 1 && globalScene.randBattleSeedInt(8) >= 5) { r++; } } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. while ( r < sortedMovePool.length - 1 && - moveScores[movePool.indexOf(sortedMovePool[r + 1])] / - moveScores[movePool.indexOf(sortedMovePool[r])] >= + moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])] >= 0 && globalScene.randBattleSeedInt(100) < Math.round( @@ -7469,6 +6442,14 @@ export class EnemyPokemon extends Pokemon { } console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId], useType: MoveUseType.NORMAL }; + console.log( + movePool.map(m => m.getName()), + moveScores, + r, + sortedMovePool.map(m => m.getName()), + ); + return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId], useType: MoveUseType.NORMAL }; + } } } @@ -7487,9 +6468,7 @@ export class EnemyPokemon extends Pokemon { */ getNextTargets(moveId: Moves): BattlerIndex[] { const moveTargets = getMoveTargets(this, moveId); - const targets = globalScene - .getField(true) - .filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + const targets = globalScene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); // If the move is multi-target, return all targets' indexes if (moveTargets.multiple) { return targets.map(p => p.getBattlerIndex()); @@ -7503,8 +6482,7 @@ export class EnemyPokemon extends Pokemon { */ const benefitScores = targets.map(p => [ p.getBattlerIndex(), - move.getTargetBenefitScore(this, p, move) * - (p.isPlayer() === this.isPlayer() ? 1 : -1), + move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1), ]); const sortedBenefitScores = benefitScores.slice(0); @@ -7535,9 +6513,7 @@ export class EnemyPokemon extends Pokemon { } // Remove any targets whose weights are less than half the max of the target weights from consideration - const benefitCutoffIndex = targetWeights.findIndex( - s => s < targetWeights[0] / 2, - ); + const benefitCutoffIndex = targetWeights.findIndex(s => s < targetWeights[0] / 2); if (benefitCutoffIndex > -1) { targetWeights = targetWeights.slice(0, benefitCutoffIndex); } @@ -7596,12 +6572,7 @@ export class EnemyPokemon extends Pokemon { return 0; } - damage( - damage: number, - ignoreSegments = false, - preventEndure = false, - ignoreFaintPhase = false, - ): number { + damage(damage: number, ignoreSegments = false, preventEndure = false, ignoreFaintPhase = false): number { if (this.isFainted()) { return 0; } @@ -7620,16 +6591,13 @@ export class EnemyPokemon extends Pokemon { while ( segmentsBypassed < this.bossSegmentIndex && this.canBypassBossSegments(segmentsBypassed + 1) && - damage - hpRemainder >= - Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1)) + damage - hpRemainder >= Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1)) ) { segmentsBypassed++; //console.log('damage', damage, 'segment', segmentsBypassed + 1, 'segment size', segmentSize, 'damage needed', Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1))); } - damage = toDmgValue( - this.hp - hpThreshold + segmentSize * segmentsBypassed, - ); + damage = toDmgValue(this.hp - hpThreshold + segmentSize * segmentsBypassed); clearedBossSegmentIndex = s - segmentsBypassed; } break; @@ -7644,12 +6612,7 @@ export class EnemyPokemon extends Pokemon { } } - const ret = super.damage( - damage, - ignoreSegments, - preventEndure, - ignoreFaintPhase, - ); + const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); if (this.isBoss()) { if (ignoreSegments) { @@ -7683,17 +6646,10 @@ export class EnemyPokemon extends Pokemon { * @param segmentIndex index of the segment to get down to (0 = no shield left, 1 = 1 shield left, etc.) */ handleBossSegmentCleared(segmentIndex: number): void { - while ( - this.bossSegmentIndex > 0 && - segmentIndex - 1 < this.bossSegmentIndex - ) { + while (this.bossSegmentIndex > 0 && segmentIndex - 1 < this.bossSegmentIndex) { // Filter out already maxed out stat stages and weigh the rest based on existing stats - const leftoverStats = EFFECTIVE_STATS.filter( - (s: EffectiveStat) => this.getStatStage(s) < 6, - ); - const statWeights = leftoverStats.map((s: EffectiveStat) => - this.getStat(s, false), - ); + const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6); + const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false)); let boostedStat: EffectiveStat; const statThresholds: number[] = []; @@ -7725,14 +6681,7 @@ export class EnemyPokemon extends Pokemon { } globalScene.unshiftPhase( - new StatStageChangePhase( - this.getBattlerIndex(), - true, - [boostedStat!], - stages, - true, - true, - ), + new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), ); this.bossSegmentIndex--; } @@ -7786,15 +6735,24 @@ export class EnemyPokemon extends Pokemon { newPokemon.setVisible(false); ret = newPokemon; - globalScene.triggerPokemonFormChange( - newPokemon, - SpeciesFormChangeActiveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); } return ret; } + + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.battleInfo.updateEffectiveness(effectiveness); + } + + toggleFlyout(visible: boolean): void { + this.battleInfo.toggleFlyout(visible); + } } /** @@ -7830,7 +6788,7 @@ interface IllusionData { /** The fusionGender of the illusion if it's a fusion */ fusionGender?: Gender; /** The level of the illusion (not used currently) */ - level?: number + level?: number; } export interface TurnMove { @@ -7884,7 +6842,7 @@ export class PokemonSummonData { /** Data pertaining to this pokemon's illusion. */ public illusion: IllusionData | null = null; - public illusionBroken: boolean = false; + public illusionBroken = false; /** Array containing all berries eaten in the last turn; used by {@linkcode Abilities.CUD_CHEW} */ public berriesEatenLast: BerryType[] = []; @@ -7921,15 +6879,15 @@ export class PokemonSummonData { } } - // TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added +// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added export class PokemonTempSummonData { /** * The number of turns this pokemon has spent without switching out. * Only currently used for positioning the battle cursor. */ - turnCount: number = 1; + turnCount = 1; - /** + /** * The number of turns this pokemon has spent in the active position since the start of the wave * without switching out. * Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file. @@ -7938,7 +6896,6 @@ export class PokemonTempSummonData { * {@linkcode Moves.FAKE_OUT | Fake Out} and {@linkcode Moves.FIRST_IMPRESSION | First Impression}). */ waveTurnCount = 1; - } /** @@ -7949,7 +6906,7 @@ export class PokemonBattleData { /** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode Moves.RAGE_FIST} */ public hitCount = 0; /** Whether this Pokemon has eaten a berry this battle; used for {@linkcode Moves.BELCH} */ - public hasEatenBerry: boolean = false; + public hasEatenBerry = false; /** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode Abilities.HARVEST} */ public berriesEaten: BerryType[] = []; @@ -7973,7 +6930,7 @@ export class PokemonWaveData { * A set of all the abilities this {@linkcode Pokemon} has used in this wave. * Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness. */ - public abilitiesApplied: Set = new Set; + public abilitiesApplied: Set = new Set(); /** Whether the pokemon's ability has been revealed or not */ public abilityRevealed = false; } @@ -8013,8 +6970,8 @@ export class PokemonTurnData { * All berries eaten by this pokemon in this turn. * Saved into {@linkcode PokemonSummonData | SummonData} by {@linkcode Abilities.CUD_CHEW} on turn end. * @see {@linkcode PokemonSummonData.berriesEatenLast} - */ - public berriesEaten: BerryType[] = [] + */ + public berriesEaten: BerryType[] = []; } export enum AiType { @@ -8090,12 +7047,7 @@ export class PokemonMove { */ public maxPpOverride?: number; - constructor( - moveId: Moves, - ppUsed = 0, - ppUp = 0, - maxPpOverride?: number, - ) { + constructor(moveId: Moves, ppUsed = 0, ppUp = 0, maxPpOverride?: number) { this.moveId = moveId; this.ppUsed = ppUsed; this.ppUp = ppUp; @@ -8106,22 +7058,14 @@ export class PokemonMove { * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. * - * @param {Pokemon} pokemon {@linkcode Pokemon} that would be using this move - * @param {boolean} ignorePp If `true`, skips the PP check - * @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) + * @param pokemon - {@linkcode Pokemon} that would be using this move + * @param ignorePp - If `true`, skips the PP check + * @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. */ - isUsable( - pokemon: Pokemon, - ignorePp = false, - ignoreRestrictionTags = false, - ): boolean { + isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { // TODO: Add Sky Drop's 1 turn stall - if ( - this.moveId && - !ignoreRestrictionTags && - pokemon.isMoveRestricted(this.moveId, pokemon) - ) { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } @@ -8129,9 +7073,7 @@ export class PokemonMove { return false; } - return ( - ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1 - ); + return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; } getMove(): Move { @@ -8142,15 +7084,12 @@ export class PokemonMove { * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} * @param count Amount of PP to use */ - usePp(count: number = 1) { + usePp(count = 1) { this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); } getMovePp(): number { - return ( - this.maxPpOverride || - this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5) - ); + return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5); } getPpRatio(): number { @@ -8167,11 +7106,6 @@ export class PokemonMove { * @returns A valid {@linkcode PokemonMove} object */ static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove( - source.moveId, - source.ppUsed, - source.ppUp, - source.maxPpOverride, - ); + return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.maxPpOverride); } } diff --git a/src/game-mode.ts b/src/game-mode.ts index ec7171b0024..97398d2b0a9 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -7,7 +7,7 @@ import type PokemonSpecies from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species"; import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; -import { randSeedInt, randSeedItem } from "#app/utils/common"; +import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; @@ -124,16 +124,20 @@ export class GameMode implements GameModeConfig { /** * @returns either: - * - random biome for Daily mode * - override from overrides.ts + * - random biome for Daily mode * - Town */ getStartingBiome(): Biome { + if (!isNullOrUndefined(Overrides.STARTING_BIOME_OVERRIDE)) { + return Overrides.STARTING_BIOME_OVERRIDE; + } + switch (this.modeId) { case GameModes.DAILY: return getDailyStartingBiome(); default: - return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; + return Biome.TOWN; } } diff --git a/src/main.ts b/src/main.ts index 7db663d14c7..38bfcbe5636 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ window.addEventListener("unhandledrejection", event => { const setPositionRelative = function (guideObject: Phaser.GameObjects.GameObject, x: number, y: number) { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; diff --git a/src/overrides.ts b/src/overrides.ts index 5bbd29b355f..95c758d7c43 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -73,7 +73,7 @@ class DefaultOverrides { */ readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null; readonly STARTING_WAVE_OVERRIDE: number = 0; - readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; + readonly STARTING_BIOME_OVERRIDE: Biome | null = null; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; /** Multiplies XP gained by this value including 0. Set to null to ignore the override. */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 598d9109abc..428acaf9ed4 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -24,7 +24,7 @@ export class RevivalBlessingPhase extends BattlePhase { UiMode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.user.getFieldIndex(), - (slotIndex: integer, _option: PartyOption) => { + (slotIndex: number, _option: PartyOption) => { if (slotIndex >= 0 && slotIndex < 6) { const pokemon = globalScene.getPlayerParty()[slotIndex]; if (!pokemon || !pokemon.isFainted()) { diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index c61eb73118f..6bdbb66be14 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -125,6 +125,12 @@ export class SwitchSummonPhase extends SummonPhase { const switchedInPokemon: Pokemon | undefined = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); + // Defensive programming: Overcome the bug where the summon data has somehow not been reset + // prior to switching in a new Pokemon. + // Force the switch to occur and load the assets for the new pokemon, ignoring override. + switchedInPokemon.resetSummonData(); + switchedInPokemon.loadAssets(true); + applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); if (!switchedInPokemon) { @@ -132,6 +138,7 @@ export class SwitchSummonPhase extends SummonPhase { return; } + if (this.switchType === SwitchType.BATON_PASS) { // If switching via baton pass, update opposing tags coming from the prior pokemon (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) => diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index f3665768cec..26fbcff75bd 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -20,37 +20,37 @@ declare module "phaser" { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Sprite { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Image { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface NineSlice { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Text { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Rectangle { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 0c13cdb9512..e4f11e1c93c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -161,7 +161,7 @@ export class UiInputs { buttonInfo(pressed = true): void { if (globalScene.showMovesetFlyout) { - for (const p of globalScene.getField().filter(p => p?.isActive(true))) { + for (const p of globalScene.getEnemyField().filter(p => p?.isActive(true))) { p.toggleFlyout(pressed); } } diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index e590bebcf5a..f8ef5fc1ec4 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,4 +1,4 @@ -import type { default as Pokemon } from "../field/pokemon"; +import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; import { fixedInt } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; @@ -126,7 +126,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { * Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event * @param pokemon {@linkcode Pokemon} to link to this flyout */ - initInfo(pokemon: Pokemon) { + initInfo(pokemon: EnemyPokemon) { this.pokemon = pokemon; this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts deleted file mode 100644 index 2822e8364ec..00000000000 --- a/src/ui/battle-info.ts +++ /dev/null @@ -1,986 +0,0 @@ -import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; -import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; -import { getLocalizedSpriteKey, fixedInt } from "#app/utils/common"; -import { addTextObject, TextStyle } from "./text"; -import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; -import { StatusEffect } from "#enums/status-effect"; -import { globalScene } from "#app/global-scene"; -import { getTypeRgb } from "#app/data/type"; -import { PokemonType } from "#enums/pokemon-type"; -import { getVariantTint } from "#app/sprites/variant"; -import { Stat } from "#enums/stat"; -import BattleFlyout from "./battle-flyout"; -import { WindowVariant, addWindow } from "./ui-theme"; -import i18next from "i18next"; -import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; - -export default class BattleInfo extends Phaser.GameObjects.Container { - public static readonly EXP_GAINS_DURATION_BASE = 1650; - - private baseY: number; - - private player: boolean; - private mini: boolean; - private boss: boolean; - private bossSegments: number; - private offset: boolean; - private lastName: string | null; - private lastTeraType: PokemonType; - private lastStatus: StatusEffect; - private lastHp: number; - private lastMaxHp: number; - private lastHpFrame: string | null; - private lastExp: number; - private lastLevelExp: number; - private lastLevel: number; - private lastLevelCapped: boolean; - private lastStats: string; - - private box: Phaser.GameObjects.Sprite; - private nameText: Phaser.GameObjects.Text; - private genderText: Phaser.GameObjects.Text; - private ownedIcon: Phaser.GameObjects.Sprite; - private championRibbon: Phaser.GameObjects.Sprite; - private teraIcon: Phaser.GameObjects.Sprite; - private shinyIcon: Phaser.GameObjects.Sprite; - private fusionShinyIcon: Phaser.GameObjects.Sprite; - private splicedIcon: Phaser.GameObjects.Sprite; - private statusIndicator: Phaser.GameObjects.Sprite; - private levelContainer: Phaser.GameObjects.Container; - private hpBar: Phaser.GameObjects.Image; - private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[]; - private levelNumbersContainer: Phaser.GameObjects.Container; - private hpNumbersContainer: Phaser.GameObjects.Container; - private type1Icon: Phaser.GameObjects.Sprite; - private type2Icon: Phaser.GameObjects.Sprite; - private type3Icon: Phaser.GameObjects.Sprite; - private expBar: Phaser.GameObjects.Image; - - // #region Type effectiveness hint objects - private effectivenessContainer: Phaser.GameObjects.Container; - private effectivenessWindow: Phaser.GameObjects.NineSlice; - private effectivenessText: Phaser.GameObjects.Text; - private currentEffectiveness?: string; - // #endregion - - public expMaskRect: Phaser.GameObjects.Graphics; - - private statsContainer: Phaser.GameObjects.Container; - private statsBox: Phaser.GameObjects.Sprite; - private statValuesContainer: Phaser.GameObjects.Container; - private statNumbers: Phaser.GameObjects.Sprite[]; - - public flyoutMenu?: BattleFlyout; - - private statOrder: Stat[]; - private readonly statOrderPlayer = [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - private readonly statOrderEnemy = [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - - constructor(x: number, y: number, player: boolean) { - super(globalScene, x, y); - this.baseY = y; - this.player = player; - this.mini = !player; - this.boss = false; - this.offset = false; - this.lastName = null; - this.lastTeraType = PokemonType.UNKNOWN; - this.lastStatus = StatusEffect.NONE; - this.lastHp = -1; - this.lastMaxHp = -1; - this.lastHpFrame = null; - this.lastExp = -1; - this.lastLevelExp = -1; - this.lastLevel = -1; - - // Initially invisible and shown via Pokemon.showInfo - this.setVisible(false); - - this.box = globalScene.add.sprite(0, 0, this.getTextureName()); - this.box.setName("box"); - this.box.setOrigin(1, 0.5); - this.add(this.box); - - this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO); - this.nameText.setName("text_name"); - this.nameText.setOrigin(0, 0); - this.add(this.nameText); - - this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO); - this.genderText.setName("text_gender"); - this.genderText.setOrigin(0, 0); - this.genderText.setPositionRelative(this.nameText, 0, 2); - this.add(this.genderText); - - if (!this.player) { - this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned"); - this.ownedIcon.setName("icon_owned"); - this.ownedIcon.setVisible(false); - this.ownedIcon.setOrigin(0, 0); - this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75); - this.add(this.ownedIcon); - - this.championRibbon = globalScene.add.sprite(0, 0, "champion_ribbon"); - this.championRibbon.setName("icon_champion_ribbon"); - this.championRibbon.setVisible(false); - this.championRibbon.setOrigin(0, 0); - this.championRibbon.setPositionRelative(this.nameText, 8, 11.75); - this.add(this.championRibbon); - } - - this.teraIcon = globalScene.add.sprite(0, 0, "icon_tera"); - this.teraIcon.setName("icon_tera"); - this.teraIcon.setVisible(false); - this.teraIcon.setOrigin(0, 0); - this.teraIcon.setScale(0.5); - this.teraIcon.setPositionRelative(this.nameText, 0, 2); - this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.teraIcon); - - this.shinyIcon = globalScene.add.sprite(0, 0, "shiny_star"); - this.shinyIcon.setName("icon_shiny"); - this.shinyIcon.setVisible(false); - this.shinyIcon.setOrigin(0, 0); - this.shinyIcon.setScale(0.5); - this.shinyIcon.setPositionRelative(this.nameText, 0, 2); - this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.shinyIcon); - - this.fusionShinyIcon = globalScene.add.sprite(0, 0, "shiny_star_2"); - this.fusionShinyIcon.setName("icon_fusion_shiny"); - this.fusionShinyIcon.setVisible(false); - this.fusionShinyIcon.setOrigin(0, 0); - this.fusionShinyIcon.setScale(0.5); - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.add(this.fusionShinyIcon); - - this.splicedIcon = globalScene.add.sprite(0, 0, "icon_spliced"); - this.splicedIcon.setName("icon_spliced"); - this.splicedIcon.setVisible(false); - this.splicedIcon.setOrigin(0, 0); - this.splicedIcon.setScale(0.5); - this.splicedIcon.setPositionRelative(this.nameText, 0, 2); - this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.splicedIcon); - - this.statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses")); - this.statusIndicator.setName("icon_status"); - this.statusIndicator.setVisible(false); - this.statusIndicator.setOrigin(0, 0); - this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5); - this.add(this.statusIndicator); - - this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5); - this.levelContainer.setName("container_level"); - this.add(this.levelContainer); - - const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); - this.levelContainer.add(levelOverlay); - - this.hpBar = globalScene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp"); - this.hpBar.setName("hp_bar"); - this.hpBar.setOrigin(0); - this.add(this.hpBar); - - this.hpBarSegmentDividers = []; - - this.levelNumbersContainer = globalScene.add.container(9.5, globalScene.uiTheme ? 0 : -0.5); - this.levelNumbersContainer.setName("container_level"); - this.levelContainer.add(this.levelNumbersContainer); - - if (this.player) { - this.hpNumbersContainer = globalScene.add.container(-15, 10); - this.hpNumbersContainer.setName("container_hp"); - this.add(this.hpNumbersContainer); - - const expBar = globalScene.add.image(-98, 18, "overlay_exp"); - expBar.setName("overlay_exp"); - expBar.setOrigin(0); - this.add(expBar); - - const expMaskRect = globalScene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xffffff); - expMaskRect.beginPath(); - expMaskRect.fillRect(127, 126, 85, 2); - - const expMask = expMaskRect.createGeometryMask(); - - expBar.setMask(expMask); - - this.expBar = expBar; - this.expMaskRect = expMaskRect; - } - - this.statsContainer = globalScene.add.container(0, 0); - this.statsContainer.setName("container_stats"); - this.statsContainer.setAlpha(0); - this.add(this.statsContainer); - - this.statsBox = globalScene.add.sprite(0, 0, `${this.getTextureName()}_stats`); - this.statsBox.setName("box_stats"); - this.statsBox.setOrigin(1, 0.5); - this.statsContainer.add(this.statsBox); - - const statLabels: Phaser.GameObjects.Sprite[] = []; - this.statNumbers = []; - - this.statValuesContainer = globalScene.add.container(0, 0); - this.statsContainer.add(this.statValuesContainer); - - // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy - // since the player won't have HP to show, it doesn't need to change from the current version - const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5; - const paddingX = this.player ? 4 : 2; - const statOverflow = this.player ? 1 : 0; - this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order - - this.statOrder.map((s, i) => { - // we do a check for i > statOverflow to see when the stat labels go onto the next column - // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 - // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 - const statX = - i > statOverflow - ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX - : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 - - const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis - let statY: number; // this will be the y-axis placement for the labels - if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) { - statY = baseY + 5; - } else { - statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us - } - - const statLabel = globalScene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]); - statLabel.setName("icon_stat_label_" + i.toString()); - statLabel.setOrigin(0, 0); - statLabels.push(statLabel); - this.statValuesContainer.add(statLabel); - - const statNumber = globalScene.add.sprite( - statX + statLabel.width, - statY, - "pbinfo_stat_numbers", - this.statOrder[i] !== Stat.HP ? "3" : "empty", - ); - statNumber.setName("icon_stat_number_" + i.toString()); - statNumber.setOrigin(0, 0); - this.statNumbers.push(statNumber); - this.statValuesContainer.add(statNumber); - - if (this.statOrder[i] === Stat.HP) { - statLabel.setVisible(false); - statNumber.setVisible(false); - } - }); - - if (!this.player) { - this.flyoutMenu = new BattleFlyout(this.player); - this.add(this.flyoutMenu); - - this.moveBelow(this.flyoutMenu, this.box); - } - - this.type1Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type1`, - ); - this.type1Icon.setName("icon_type_1"); - this.type1Icon.setOrigin(0, 0); - this.add(this.type1Icon); - - this.type2Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -1 : -2.5, - `pbinfo_${player ? "player" : "enemy"}_type2`, - ); - this.type2Icon.setName("icon_type_2"); - this.type2Icon.setOrigin(0, 0); - this.add(this.type2Icon); - - this.type3Icon = globalScene.add.sprite( - player ? -154 : 0, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type`, - ); - this.type3Icon.setName("icon_type_3"); - this.type3Icon.setOrigin(0, 0); - this.add(this.type3Icon); - - if (!this.player) { - this.effectivenessContainer = globalScene.add.container(0, 0); - this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4); - this.effectivenessContainer.setVisible(false); - this.add(this.effectivenessContainer); - - this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); - this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); - - this.effectivenessContainer.add(this.effectivenessWindow); - this.effectivenessContainer.add(this.effectivenessText); - } - } - - getStatsValueContainer(): Phaser.GameObjects.Container { - return this.statValuesContainer; - } - - initInfo(pokemon: Pokemon) { - this.updateNameText(pokemon); - const nameTextWidth = this.nameText.displayWidth; - - this.name = pokemon.getNameToRender(); - this.box.name = pokemon.getNameToRender(); - - this.flyoutMenu?.initInfo(pokemon); - - this.genderText.setText(getGenderSymbol(pokemon.gender)); - this.genderText.setColor(getGenderColor(pokemon.gender)); - this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0); - - this.lastTeraType = pokemon.getTeraType(); - - this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); - this.teraIcon.setVisible(pokemon.isTerastallized); - this.teraIcon.on("pointerover", () => { - if (pokemon.isTerastallized) { - globalScene.ui.showTooltip( - "", - i18next.t("fightUiHandler:teraHover", { - type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), - }), - ); - } - }); - this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - - const isFusion = pokemon.isFusion(true); - - this.splicedIcon.setPositionRelative( - this.nameText, - nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 2.5, - ); - this.splicedIcon.setVisible(isFusion); - if (this.splicedIcon.visible) { - this.splicedIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, - ), - ); - this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - - this.shinyIcon.setPositionRelative( - this.nameText, - nameTextWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`); - this.shinyIcon.setVisible(pokemon.isShiny()); - this.shinyIcon.setTint(getVariantTint(baseVariant)); - if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - - if (!this.player) { - if (this.nameText.visible) { - this.nameText.on("pointerover", () => - globalScene.ui.showTooltip( - "", - i18next.t("battleInfo:generation", { - generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), - }), - ), - ); - this.nameText.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; - this.ownedIcon.setVisible(!!dexEntry.caughtAttr); - const opponentPokemonDexAttr = pokemon.getDexAttr(); - if (globalScene.gameMode.isClassic) { - if ( - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 - ) { - this.championRibbon.setVisible(true); - } - } - - // Check if Player owns all genders and forms of the Pokemon - const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; - - const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - - // Check if the player owns ability for the root form - const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); - - if (missingDexAttrs || !playerOwnsThisAbility) { - this.ownedIcon.setTint(0x808080); - } - - if (this.boss) { - this.updateBossSegmentDividers(pokemon as EnemyPokemon); - } - } - - this.hpBar.setScale(pokemon.getHpRatio(true), 1); - this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - this.hpBar.setFrame(this.lastHpFrame); - if (this.player) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - } - this.lastHp = pokemon.hp; - this.lastMaxHp = pokemon.getMaxHp(); - - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - - this.shinyIcon.setVisible(pokemon.isShiny()); - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - if (this.player) { - this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - - this.statValuesContainer.setPosition(8, 7); - } - - const stats = this.statOrder.map(() => 0); - - this.lastStats = stats.join(""); - this.updateStats(stats); - } - - getTextureName(): string { - return `pbinfo_${this.player ? "player" : "enemy"}${!this.player && this.boss ? "_boss" : this.mini ? "_mini" : ""}`; - } - - setMini(mini: boolean): void { - if (this.mini === mini) { - return; - } - - this.mini = mini; - - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - - if (this.player) { - this.y -= 12 * (mini ? 1 : -1); - this.baseY = this.y; - } - - const offsetElements = [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.statusIndicator, - this.levelContainer, - ]; - offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); - - [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { - el.x += 4 * (mini ? 1 : -1); - el.y += -8 * (mini ? 1 : -1); - }); - - this.statValuesContainer.x += 2 * (mini ? 1 : -1); - this.statValuesContainer.y += -7 * (mini ? 1 : -1); - - const toggledElements = [this.hpNumbersContainer, this.expBar]; - toggledElements.forEach(el => el.setVisible(!mini)); - } - - toggleStats(visible: boolean): void { - globalScene.tweens.add({ - targets: this.statsContainer, - duration: fixedInt(125), - ease: "Sine.easeInOut", - alpha: visible ? 1 : 0, - }); - } - - updateBossSegments(pokemon: EnemyPokemon): void { - const boss = !!pokemon.bossSegments; - - if (boss !== this.boss) { - this.boss = boss; - - [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.ownedIcon, - this.championRibbon, - this.statusIndicator, - this.statValuesContainer, - ].map(e => (e.x += 48 * (boss ? -1 : 1))); - this.hpBar.x += 38 * (boss ? -1 : 1); - this.hpBar.y += 2 * (this.boss ? -1 : 1); - this.levelContainer.x += 2 * (boss ? -1 : 1); - this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - } - - this.bossSegments = boss ? pokemon.bossSegments : 0; - this.updateBossSegmentDividers(pokemon); - } - - updateBossSegmentDividers(pokemon: EnemyPokemon): void { - while (this.hpBarSegmentDividers.length) { - this.hpBarSegmentDividers.pop()?.destroy(); - } - - if (this.boss && this.bossSegments > 1) { - const uiTheme = globalScene.uiTheme; - const maxHp = pokemon.getMaxHp(); - for (let s = 1; s < this.bossSegments; s++) { - const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; - const divider = globalScene.add.rectangle( - 0, - 0, - 1, - this.hpBar.height - (uiTheme ? 0 : 1), - pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, - ); - divider.setOrigin(0.5, 0); - divider.setName("hpBar_divider_" + s.toString()); - this.add(divider); - this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); - - divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); - this.hpBarSegmentDividers.push(divider); - } - } - } - - setOffset(offset: boolean): void { - if (this.offset === offset) { - return; - } - - this.offset = offset; - - this.x += 10 * (this.offset === this.player ? 1 : -1); - this.y += 27 * (this.offset ? 1 : -1); - this.baseY = this.y; - } - - updateInfo(pokemon: Pokemon, instant?: boolean): Promise { - return new Promise(resolve => { - if (!globalScene) { - return resolve(); - } - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - - this.genderText.setText(getGenderSymbol(gender)); - this.genderText.setColor(getGenderColor(gender)); - - const nameUpdated = this.lastName !== pokemon.getNameToRender(); - - if (nameUpdated) { - this.updateNameText(pokemon); - this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); - } - - const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN; - const teraTypeUpdated = this.lastTeraType !== teraType; - - if (teraTypeUpdated) { - this.teraIcon.setVisible(teraType !== PokemonType.UNKNOWN); - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.teraIcon.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(teraType))); - this.lastTeraType = teraType; - } - - const isFusion = pokemon.isFusion(true); - - if (nameUpdated || teraTypeUpdated) { - this.splicedIcon.setVisible(isFusion); - - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.splicedIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 1.5, - ); - this.shinyIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - } - - if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { - this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; - - if (this.lastStatus !== StatusEffect.NONE) { - this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); - } - - const offsetX = !this.player ? (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0) : 0; - this.statusIndicator.setPositionRelative(this.nameText, offsetX, 11.5); - - this.statusIndicator.setVisible(!!this.lastStatus); - } - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - const updateHpFrame = () => { - const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - if (hpFrame !== this.lastHpFrame) { - this.hpBar.setFrame(hpFrame); - this.lastHpFrame = hpFrame; - } - }; - - const updatePokemonHp = () => { - let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; - const speed = globalScene.hpBarSpeed; - if (speed) { - duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); - } - globalScene.tweens.add({ - targets: this.hpBar, - ease: "Sine.easeOut", - scaleX: pokemon.getHpRatio(true), - duration: duration, - onUpdate: () => { - if (this.player && this.lastHp !== pokemon.hp) { - const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); - this.setHpNumbers(tweenHp, pokemon.getMaxHp()); - this.lastHp = tweenHp; - } - - updateHpFrame(); - }, - onComplete: () => { - updateHpFrame(); - // If, after tweening, the hp is different from the original (due to rounding), force the hp number display - // to update to the correct value. - if (this.player && this.lastHp !== pokemon.hp) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - this.lastHp = pokemon.hp; - } - resolve(); - }, - }); - if (!this.player) { - this.lastHp = pokemon.hp; - } - this.lastMaxHp = pokemon.getMaxHp(); - }; - - if (this.player) { - const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); - - if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { - const originalResolve = resolve; - const durationMultipler = Math.max( - Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")( - 1 - Math.min(pokemon.level - this.lastLevel, 10) / 10, - ), - 0.1, - ); - resolve = () => this.updatePokemonExp(pokemon, false, durationMultipler).then(() => originalResolve()); - } else if (isLevelCapped !== this.lastLevelCapped) { - this.setLevel(pokemon.level); - } - - this.lastLevelCapped = isLevelCapped; - } - - if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { - return updatePokemonHp(); - } - if (!this.player && this.lastLevel !== pokemon.level) { - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - } - - const stats = pokemon.getStatStages(); - const statsStr = stats.join(""); - - if (this.lastStats !== statsStr) { - this.updateStats(stats); - this.lastStats = statsStr; - } - - this.shinyIcon.setVisible(pokemon.isShiny(true)); - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - this.shinyIcon.setTint(getVariantTint(baseVariant)); - - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - - resolve(); - }); - } - - updateNameText(pokemon: Pokemon): void { - let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); - let nameTextWidth: number; - - const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); - nameTextWidth = nameSizeTest.displayWidth; - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - while ( - nameTextWidth > - (this.player || !this.boss ? 60 : 98) - - ((gender !== Gender.GENDERLESS ? 6 : 0) + - (pokemon.fusionSpecies ? 8 : 0) + - (pokemon.isShiny() ? 8 : 0) + - (Math.min(pokemon.level.toString().length, 3) - 3) * 8) - ) { - displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; - nameSizeTest.setText(displayName); - nameTextWidth = nameSizeTest.displayWidth; - } - - nameSizeTest.destroy(); - - this.nameText.setText(displayName); - this.lastName = pokemon.getNameToRender(); - - if (this.nameText.visible) { - this.nameText.setInteractive( - new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), - Phaser.Geom.Rectangle.Contains, - ); - } - } - - updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { - return new Promise(resolve => { - const levelUp = this.lastLevel < pokemon.level; - const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); - const levelExp = levelUp ? relLevelExp : pokemon.levelExp; - let ratio = relLevelExp ? levelExp / relLevelExp : 0; - if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { - if (levelUp) { - ratio = 1; - } else { - ratio = 0; - } - instant = true; - } - const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( - 1 - Math.max(this.lastLevel - 100, 0) / 150, - ); - let duration = - this.visible && !instant - ? ((levelExp - this.lastLevelExp) / relLevelExp) * - BattleInfo.EXP_GAINS_DURATION_BASE * - durationMultiplier * - levelDurationMultiplier - : 0; - const speed = globalScene.expGainsSpeed; - if (speed && speed >= ExpGainsSpeed.DEFAULT) { - duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); - } - if (ratio === 1) { - this.lastLevelExp = 0; - this.lastLevel++; - } else { - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - } - if (duration) { - globalScene.playSound("se/exp"); - } - globalScene.tweens.add({ - targets: this.expMaskRect, - ease: "Sine.easeIn", - x: ratio * 510, - duration: duration, - onComplete: () => { - if (!globalScene) { - return resolve(); - } - if (duration) { - globalScene.sound.stopByKey("se/exp"); - } - if (ratio === 1) { - globalScene.playSound("se/level_up"); - this.setLevel(this.lastLevel); - globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { - this.expMaskRect.x = 0; - this.updateInfo(pokemon, instant).then(() => resolve()); - }); - return; - } - resolve(); - }, - }); - }); - } - - setLevel(level: number): void { - const isCapped = level >= globalScene.getMaxExpLevel(); - this.levelNumbersContainer.removeAll(true); - const levelStr = level.toString(); - for (let i = 0; i < levelStr.length; i++) { - this.levelNumbersContainer.add( - globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i]), - ); - } - this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); - } - - setHpNumbers(hp: number, maxHp: number): void { - if (!this.player || !globalScene) { - return; - } - this.hpNumbersContainer.removeAll(true); - const hpStr = hp.toString(); - const maxHpStr = maxHp.toString(); - let offset = 0; - for (let i = maxHpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); - } - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); - for (let i = hpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); - } - } - - updateStats(stats: number[]): void { - this.statOrder.map((s, i) => { - if (s !== Stat.HP) { - this.statNumbers[i].setFrame(stats[s - 1].toString()); - } - }); - } - - /** - * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary - */ - toggleFlyout(visible: boolean): void { - this.flyoutMenu?.toggleFlyout(visible); - - if (visible) { - this.effectivenessContainer?.setVisible(false); - } else { - this.updateEffectiveness(this.currentEffectiveness); - } - } - - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - if (this.player) { - return; - } - this.currentEffectiveness = effectiveness; - - if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) { - this.effectivenessContainer.setVisible(false); - return; - } - - this.effectivenessText.setText(effectiveness); - this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; - this.effectivenessContainer.setVisible(true); - } - - getBaseY(): number { - return this.baseY; - } - - resetY(): void { - this.y = this.baseY; - } -} - -export class PlayerBattleInfo extends BattleInfo { - constructor() { - super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true); - } -} - -export class EnemyBattleInfo extends BattleInfo { - constructor() { - super(140, -141, false); - } - - setMini(_mini: boolean): void {} // Always mini -} diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts new file mode 100644 index 00000000000..7e6cc12bd3d --- /dev/null +++ b/src/ui/battle-info/battle-info.ts @@ -0,0 +1,688 @@ +import type { default as Pokemon } from "../../field/pokemon"; +import { getLocalizedSpriteKey, fixedInt, getShinyDescriptor } from "#app/utils/common"; +import { addTextObject, TextStyle } from "../text"; +import { getGenderSymbol, getGenderColor, Gender } from "../../data/gender"; +import { StatusEffect } from "#enums/status-effect"; +import { globalScene } from "#app/global-scene"; +import { getTypeRgb } from "#app/data/type"; +import { PokemonType } from "#enums/pokemon-type"; +import { getVariantTint } from "#app/sprites/variant"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; + +/** + * Parameters influencing the position of elements within the battle info container + */ +export type BattleInfoParamList = { + /** X offset for the name text*/ + nameTextX: number; + /** Y offset for the name text */ + nameTextY: number; + /** X offset for the level container */ + levelContainerX: number; + /** Y offset for the level container */ + levelContainerY: number; + /** X offset for the hp bar */ + hpBarX: number; + /** Y offset for the hp bar */ + hpBarY: number; + /** Parameters for the stat box container */ + statBox: { + /** The starting offset from the left of the label for the entries in the stat box */ + xOffset: number; + /** The X padding between each number column */ + paddingX: number; + /** The index of the stat entries at which paddingX is used instead of startingX */ + statOverflow: number; + }; +}; + +export default abstract class BattleInfo extends Phaser.GameObjects.Container { + public static readonly EXP_GAINS_DURATION_BASE = 1650; + + protected baseY: number; + protected baseLvContainerX: number; + + protected player: boolean; + protected mini: boolean; + protected boss: boolean; + protected bossSegments: number; + protected offset: boolean; + protected lastName: string | null; + protected lastTeraType: PokemonType; + protected lastStatus: StatusEffect; + protected lastHp: number; + protected lastMaxHp: number; + protected lastHpFrame: string | null; + protected lastExp: number; + protected lastLevelExp: number; + protected lastLevel: number; + protected lastLevelCapped: boolean; + protected lastStats: string; + + protected box: Phaser.GameObjects.Sprite; + protected nameText: Phaser.GameObjects.Text; + protected genderText: Phaser.GameObjects.Text; + protected teraIcon: Phaser.GameObjects.Sprite; + protected shinyIcon: Phaser.GameObjects.Sprite; + protected fusionShinyIcon: Phaser.GameObjects.Sprite; + protected splicedIcon: Phaser.GameObjects.Sprite; + protected statusIndicator: Phaser.GameObjects.Sprite; + protected levelContainer: Phaser.GameObjects.Container; + protected hpBar: Phaser.GameObjects.Image; + protected levelNumbersContainer: Phaser.GameObjects.Container; + protected type1Icon: Phaser.GameObjects.Sprite; + protected type2Icon: Phaser.GameObjects.Sprite; + protected type3Icon: Phaser.GameObjects.Sprite; + protected expBar: Phaser.GameObjects.Image; + + public expMaskRect: Phaser.GameObjects.Graphics; + + protected statsContainer: Phaser.GameObjects.Container; + protected statsBox: Phaser.GameObjects.Sprite; + protected statValuesContainer: Phaser.GameObjects.Container; + protected statNumbers: Phaser.GameObjects.Sprite[]; + + get statOrder(): Stat[] { + return []; + } + + /** Helper method used by the constructor to create the tera and shiny icons next to the name */ + private constructIcons() { + const hitArea = new Phaser.Geom.Rectangle(0, 0, 12, 15); + const hitCallback = Phaser.Geom.Rectangle.Contains; + + this.teraIcon = globalScene.add + .sprite(0, 0, "icon_tera") + .setName("icon_tera") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.shinyIcon = globalScene.add + .sprite(0, 0, "shiny_star") + .setName("icon_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.fusionShinyIcon = globalScene.add + .sprite(0, 0, "shiny_star_2") + .setName("icon_fusion_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .copyPosition(this.shinyIcon); + + this.splicedIcon = globalScene.add + .sprite(0, 0, "icon_spliced") + .setName("icon_spliced") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.add([this.teraIcon, this.shinyIcon, this.fusionShinyIcon, this.splicedIcon]); + } + + /** + * Submethod of the constructor that creates and adds the stats container to the battle info + */ + protected constructStatContainer({ xOffset, paddingX, statOverflow }: BattleInfoParamList["statBox"]): void { + this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0); + this.add(this.statsContainer); + + this.statsBox = globalScene.add + .sprite(0, 0, `${this.getTextureName()}_stats`) + .setName("box_stats") + .setOrigin(1, 0.5); + this.statsContainer.add(this.statsBox); + + const statLabels: Phaser.GameObjects.Sprite[] = []; + this.statNumbers = []; + + this.statValuesContainer = globalScene.add.container(); + this.statsContainer.add(this.statValuesContainer); + + const startingX = -this.statsBox.width + xOffset; + + // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy + // since the player won't have HP to show, it doesn't need to change from the current version + + for (const [i, s] of this.statOrder.entries()) { + const isHp = s === Stat.HP; + // we do a check for i > statOverflow to see when the stat labels go onto the next column + // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 + // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 + const statX = + i > statOverflow + ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX + : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 + + let statY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis + if (isHp || s === Stat.SPD) { + statY += 5; + } else if (this.player === !!(i % 2)) { + // we compare i % 2 against this.player to tell us where to place the label + // because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players + // this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us + statY += 10; + } + + const statLabel = globalScene.add + .sprite(statX, statY, "pbinfo_stat", Stat[s]) + .setName("icon_stat_label_" + i.toString()) + .setOrigin(0); + statLabels.push(statLabel); + this.statValuesContainer.add(statLabel); + + const statNumber = globalScene.add + .sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", !isHp ? "3" : "empty") + .setName("icon_stat_number_" + i.toString()) + .setOrigin(0); + this.statNumbers.push(statNumber); + this.statValuesContainer.add(statNumber); + + if (isHp) { + statLabel.setVisible(false); + statNumber.setVisible(false); + } + } + } + + /** + * Submethod of the constructor that creates and adds the pokemon type icons to the battle info + */ + protected abstract constructTypeIcons(): void; + + /** + * @param x - The x position of the battle info container + * @param y - The y position of the battle info container + * @param player - Whether this battle info belongs to a player or an enemy + * @param posParams - The parameters influencing the position of elements within the battle info container + */ + constructor(x: number, y: number, player: boolean, posParams: BattleInfoParamList) { + super(globalScene, x, y); + this.baseY = y; + this.player = player; + this.mini = !player; + this.boss = false; + this.offset = false; + this.lastName = null; + this.lastTeraType = PokemonType.UNKNOWN; + this.lastStatus = StatusEffect.NONE; + this.lastHp = -1; + this.lastMaxHp = -1; + this.lastHpFrame = null; + this.lastExp = -1; + this.lastLevelExp = -1; + this.lastLevel = -1; + this.baseLvContainerX = posParams.levelContainerX; + + // Initially invisible and shown via Pokemon.showInfo + this.setVisible(false); + + this.box = globalScene.add.sprite(0, 0, this.getTextureName()).setName("box").setOrigin(1, 0.5); + this.add(this.box); + + this.nameText = addTextObject(posParams.nameTextX, posParams.nameTextY, "", TextStyle.BATTLE_INFO) + .setName("text_name") + .setOrigin(0); + this.add(this.nameText); + + this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO) + .setName("text_gender") + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 2); + this.add(this.genderText); + + this.constructIcons(); + + this.statusIndicator = globalScene.add + .sprite(0, 0, getLocalizedSpriteKey("statuses")) + .setName("icon_status") + .setVisible(false) + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 11.5); + this.add(this.statusIndicator); + + this.levelContainer = globalScene.add + .container(posParams.levelContainerX, posParams.levelContainerY) + .setName("container_level"); + this.add(this.levelContainer); + + const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); + this.levelContainer.add(levelOverlay); + + this.hpBar = globalScene.add.image(posParams.hpBarX, posParams.hpBarY, "overlay_hp").setName("hp_bar").setOrigin(0); + this.add(this.hpBar); + + this.levelNumbersContainer = globalScene.add + .container(9.5, globalScene.uiTheme ? 0 : -0.5) + .setName("container_level"); + this.levelContainer.add(this.levelNumbersContainer); + + this.constructStatContainer(posParams.statBox); + + this.constructTypeIcons(); + } + + getStatsValueContainer(): Phaser.GameObjects.Container { + return this.statValuesContainer; + } + + //#region Initialization methods + + initSplicedIcon(pokemon: Pokemon, baseWidth: number) { + this.splicedIcon.setPositionRelative( + this.nameText, + baseWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 2.5, + ); + this.splicedIcon.setVisible(pokemon.isFusion(true)); + if (!this.splicedIcon.visible) { + return; + } + this.splicedIcon + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + /** + * Called by {@linkcode initInfo} to initialize the shiny icon + * @param pokemon - The pokemon object attached to this battle info + * @param baseXOffset - The x offset to use for the shiny icon + * @param doubleShiny - Whether the pokemon is shiny and its fusion species is also shiny + */ + protected initShinyIcon(pokemon: Pokemon, xOffset: number, doubleShiny: boolean) { + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + + this.shinyIcon.setPositionRelative( + this.nameText, + xOffset + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + this.shinyIcon + .setTexture(`shiny_star${doubleShiny ? "_1" : ""}`) + .setVisible(pokemon.isShiny()) + .setTint(getVariantTint(baseVariant)); + + if (!this.shinyIcon.visible) { + return; + } + + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + + this.shinyIcon + .on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor)) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + initInfo(pokemon: Pokemon) { + this.updateNameText(pokemon); + const nameTextWidth = this.nameText.displayWidth; + + this.name = pokemon.getNameToRender(); + this.box.name = pokemon.getNameToRender(); + + this.genderText + .setText(getGenderSymbol(pokemon.gender)) + .setColor(getGenderColor(pokemon.gender)) + .setPositionRelative(this.nameText, nameTextWidth, 0); + + this.lastTeraType = pokemon.getTeraType(); + + this.teraIcon + .setVisible(pokemon.isTerastallized) + .on("pointerover", () => { + if (pokemon.isTerastallized) { + globalScene.ui.showTooltip( + "", + i18next.t("fightUiHandler:teraHover", { + type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), + }), + ); + } + }) + .on("pointerout", () => globalScene.ui.hideTooltip()) + .setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); + + const isFusion = pokemon.isFusion(true); + this.initSplicedIcon(pokemon, nameTextWidth); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + this.initShinyIcon(pokemon, nameTextWidth, doubleShiny); + + this.fusionShinyIcon.setVisible(doubleShiny).copyPosition(this.shinyIcon); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + this.hpBar.setScale(pokemon.getHpRatio(true), 1); + this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + this.hpBar.setFrame(this.lastHpFrame); + this.lastHp = pokemon.hp; + this.lastMaxHp = pokemon.getMaxHp(); + + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + + this.shinyIcon.setVisible(pokemon.isShiny()); + + this.setTypes(pokemon.getTypes(true, false, undefined, true)); + + const stats = this.statOrder.map(() => 0); + + this.lastStats = stats.join(""); + this.updateStats(stats); + } + //#endregion + + /** + * Return the texture name of the battle info box + */ + abstract getTextureName(): string; + + setMini(_mini: boolean): void {} + + toggleStats(visible: boolean): void { + globalScene.tweens.add({ + targets: this.statsContainer, + duration: fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0, + }); + } + + setOffset(offset: boolean): void { + if (this.offset === offset) { + return; + } + + this.offset = offset; + + this.x += 10 * (this.offset === this.player ? 1 : -1); + this.y += 27 * (this.offset ? 1 : -1); + this.baseY = this.y; + } + + //#region Update methods and helpers + + /** + * Update the status icon to match the pokemon's current status + * @param pokemon - The pokemon object attached to this battle info + * @param xOffset - The offset from the name text + */ + updateStatusIcon(pokemon: Pokemon, xOffset = 0) { + if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { + this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; + + if (this.lastStatus !== StatusEffect.NONE) { + this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); + } + + this.statusIndicator.setVisible(!!this.lastStatus).setPositionRelative(this.nameText, xOffset, 11.5); + } + } + + /** Update the pokemon name inside the container */ + protected updateName(name: string): boolean { + if (this.lastName === name) { + return false; + } + this.nameText.setText(name).setPositionRelative(this.box, -this.nameText.displayWidth, 0); + this.lastName = name; + + return true; + } + + protected updateTeraType(ty: PokemonType): boolean { + if (this.lastTeraType === ty) { + return false; + } + + this.teraIcon + .setVisible(ty !== PokemonType.UNKNOWN) + .setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(ty))) + .setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.lastTeraType = ty; + + return true; + } + + /** + * Update the type icons to match the pokemon's types + */ + setTypes(types: PokemonType[]): void { + this.type1Icon + .setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`) + .setFrame(PokemonType[types[0]].toLowerCase()); + this.type2Icon.setVisible(types.length > 1); + this.type3Icon.setVisible(types.length > 2); + if (types.length > 1) { + this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); + } + if (types.length > 2) { + this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); + } + } + + /** + * Called by {@linkcode updateInfo} to update the position of the tera, spliced, and shiny icons + * @param isFusion - Whether the pokemon is a fusion or not + */ + protected updateIconDisplay(isFusion: boolean): void { + this.teraIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.splicedIcon + .setVisible(isFusion) + .setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 1.5, + ); + this.shinyIcon.setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + } + + //#region Hp Bar Display handling + /** + * Called every time the hp frame is updated by the tween + * @param pokemon - The pokemon object attached to this battle info + */ + protected updateHpFrame(): void { + const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + if (hpFrame !== this.lastHpFrame) { + this.hpBar.setFrame(hpFrame); + this.lastHpFrame = hpFrame; + } + } + + /** + * Called by every frame in the hp animation tween created in {@linkcode updatePokemonHp} + * @param _pokemon - The pokemon the battle-info bar belongs to + */ + protected onHpTweenUpdate(_pokemon: Pokemon): void { + this.updateHpFrame(); + } + + /** Update the pokemonHp bar */ + protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike) => void, instant?: boolean): void { + let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; + const speed = globalScene.hpBarSpeed; + if (speed) { + duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); + } + globalScene.tweens.add({ + targets: this.hpBar, + ease: "Sine.easeOut", + scaleX: pokemon.getHpRatio(true), + duration: duration, + onUpdate: () => { + this.onHpTweenUpdate(pokemon); + }, + onComplete: () => { + this.updateHpFrame(); + resolve(); + }, + }); + this.lastMaxHp = pokemon.getMaxHp(); + } + + //#endregion + + async updateInfo(pokemon: Pokemon, instant?: boolean): Promise { + let resolve: (r: void | PromiseLike) => void = () => {}; + const promise = new Promise(r => (resolve = r)); + if (!globalScene) { + return resolve(); + } + + const gender: Gender = pokemon.summonData?.illusion?.gender ?? pokemon.gender; + + this.genderText.setText(getGenderSymbol(gender)).setColor(getGenderColor(gender)); + + const nameUpdated = this.updateName(pokemon.getNameToRender()); + + const teraTypeUpdated = this.updateTeraType(pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN); + + const isFusion = pokemon.isFusion(true); + + if (nameUpdated || teraTypeUpdated) { + this.updateIconDisplay(isFusion); + } + + this.updateStatusIcon(pokemon); + + if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { + return this.updatePokemonHp(pokemon, resolve, instant); + } + if (!this.player && this.lastLevel !== pokemon.level) { + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + } + + const stats = pokemon.getStatStages(); + const statsStr = stats.join(""); + + if (this.lastStats !== statsStr) { + this.updateStats(stats); + this.lastStats = statsStr; + } + + this.shinyIcon.setVisible(pokemon.isShiny(true)); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + this.shinyIcon.setTint(getVariantTint(baseVariant)); + + this.fusionShinyIcon.setVisible(doubleShiny).setPosition(this.shinyIcon.x, this.shinyIcon.y); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + resolve(); + await promise; + } + //#endregion + + updateNameText(pokemon: Pokemon): void { + let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); + let nameTextWidth: number; + + const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); + nameTextWidth = nameSizeTest.displayWidth; + + const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; + while ( + nameTextWidth > + (this.player || !this.boss ? 60 : 98) - + ((gender !== Gender.GENDERLESS ? 6 : 0) + + (pokemon.fusionSpecies ? 8 : 0) + + (pokemon.isShiny() ? 8 : 0) + + (Math.min(pokemon.level.toString().length, 3) - 3) * 8) + ) { + displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; + nameSizeTest.setText(displayName); + nameTextWidth = nameSizeTest.displayWidth; + } + + nameSizeTest.destroy(); + + this.nameText.setText(displayName); + this.lastName = pokemon.getNameToRender(); + + if (this.nameText.visible) { + this.nameText.setInteractive( + new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), + Phaser.Geom.Rectangle.Contains, + ); + } + } + + /** + * Set the level numbers container to display the provided level + * + * @remarks + * The numbers in the pokemon's level uses images for each number rather than a text object with a special font. + * This method sets the images for each digit of the level number and then positions the level container based + * on the number of digits. + * + * @param level - The level to display + * @param textureKey - The texture key for the level numbers + */ + setLevel(level: number, textureKey: "numbers" | "numbers_red" = "numbers"): void { + this.levelNumbersContainer.removeAll(true); + const levelStr = level.toString(); + for (let i = 0; i < levelStr.length; i++) { + this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, textureKey, levelStr[i])); + } + this.levelContainer.setX(this.baseLvContainerX - 8 * Math.max(levelStr.length - 3, 0)); + } + + updateStats(stats: number[]): void { + for (const [i, s] of this.statOrder.entries()) { + if (s !== Stat.HP) { + this.statNumbers[i].setFrame(stats[s - 1].toString()); + } + } + } + + getBaseY(): number { + return this.baseY; + } + + resetY(): void { + this.y = this.baseY; + } +} diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts new file mode 100644 index 00000000000..7c16f01ac38 --- /dev/null +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -0,0 +1,235 @@ +import { globalScene } from "#app/global-scene"; +import BattleFlyout from "../battle-flyout"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { addWindow, WindowVariant } from "#app/ui/ui-theme"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type { GameObjects } from "phaser"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class EnemyBattleInfo extends BattleInfo { + protected player: false = false; + protected championRibbon: Phaser.GameObjects.Sprite; + protected ownedIcon: Phaser.GameObjects.Sprite; + protected flyoutMenu: BattleFlyout; + + protected hpBarSegmentDividers: GameObjects.Rectangle[] = []; + + // #region Type effectiveness hint objects + protected effectivenessContainer: Phaser.GameObjects.Container; + protected effectivenessWindow: Phaser.GameObjects.NineSlice; + protected effectivenessText: Phaser.GameObjects.Text; + protected currentEffectiveness?: string; + // #endregion + + override get statOrder(): Stat[] { + return [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.boss ? "pbinfo_enemy_boss" : "pbinfo_enemy_mini"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -124, + nameTextY: -11.2, + levelContainerX: -50, + levelContainerY: -5, + hpBarX: -71, + hpBarY: 4.5, + statBox: { + xOffset: 5, + paddingX: 2, + statOverflow: 0, + }, + }; + + super(140, -141, false, posParams); + + this.ownedIcon = globalScene.add + .sprite(0, 0, "icon_owned") + .setName("icon_owned") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 0, 11.75); + + this.championRibbon = globalScene.add + .sprite(0, 0, "champion_ribbon") + .setName("icon_champion_ribbon") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 8, 11.75); + // Ensure these two icons are positioned below the stats container + this.addAt([this.ownedIcon, this.championRibbon], this.getIndex(this.statsContainer)); + + this.flyoutMenu = new BattleFlyout(this.player); + this.add(this.flyoutMenu); + + this.moveBelow(this.flyoutMenu, this.box); + + this.effectivenessContainer = globalScene.add + .container(0, 0) + .setVisible(false) + .setPositionRelative(this.type1Icon, 22, 4); + this.add(this.effectivenessContainer); + + this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); + this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); + + this.effectivenessContainer.add([this.effectivenessWindow, this.effectivenessText]); + } + + override initInfo(pokemon: EnemyPokemon): void { + this.flyoutMenu.initInfo(pokemon); + super.initInfo(pokemon); + + if (this.nameText.visible) { + this.nameText + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + i18next.t("battleInfo:generation", { + generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), + }), + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; + this.ownedIcon.setVisible(!!dexEntry.caughtAttr); + const opponentPokemonDexAttr = pokemon.getDexAttr(); + if ( + globalScene.gameMode.isClassic && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 + ) { + this.championRibbon.setVisible(true); + } + + // Check if Player owns all genders and forms of the Pokemon + const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; + + const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; + + // Check if the player owns ability for the root form + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); + + if (missingDexAttrs || !playerOwnsThisAbility) { + this.ownedIcon.setTint(0x808080); + } + + if (this.boss) { + this.updateBossSegmentDividers(pokemon as EnemyPokemon); + } + } + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.currentEffectiveness = effectiveness; + + if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) { + this.effectivenessContainer.setVisible(false); + return; + } + + this.effectivenessText.setText(effectiveness); + this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; + this.effectivenessContainer.setVisible(true); + } + + /** + * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary + */ + toggleFlyout(visible: boolean): void { + this.flyoutMenu.toggleFlyout(visible); + + if (visible) { + this.effectivenessContainer.setVisible(false); + } else { + this.updateEffectiveness(this.currentEffectiveness); + } + } + + updateBossSegments(pokemon: EnemyPokemon): void { + const boss = !!pokemon.bossSegments; + + if (boss !== this.boss) { + this.boss = boss; + + [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.ownedIcon, + this.championRibbon, + this.statusIndicator, + this.levelContainer, + this.statValuesContainer, + ].map(e => (e.x += 48 * (boss ? -1 : 1))); + this.hpBar.x += 38 * (boss ? -1 : 1); + this.hpBar.y += 2 * (this.boss ? -1 : 1); + this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + } + + this.bossSegments = boss ? pokemon.bossSegments : 0; + this.updateBossSegmentDividers(pokemon); + } + + updateBossSegmentDividers(pokemon: EnemyPokemon): void { + while (this.hpBarSegmentDividers.length) { + this.hpBarSegmentDividers.pop()?.destroy(); + } + + if (this.boss && this.bossSegments > 1) { + const uiTheme = globalScene.uiTheme; + const maxHp = pokemon.getMaxHp(); + for (let s = 1; s < this.bossSegments; s++) { + const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; + const divider = globalScene.add.rectangle( + 0, + 0, + 1, + this.hpBar.height - (uiTheme ? 0 : 1), + pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, + ); + divider.setOrigin(0.5, 0).setName("hpBar_divider_" + s.toString()); + this.add(divider); + this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); + + divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); + this.hpBarSegmentDividers.push(divider); + } + } + } + + override updateStatusIcon(pokemon: EnemyPokemon): void { + super.updateStatusIcon(pokemon, (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0)); + } + + protected override updatePokemonHp( + pokemon: EnemyPokemon, + resolve: (r: void | PromiseLike) => void, + instant?: boolean, + ): void { + super.updatePokemonHp(pokemon, resolve, instant); + this.lastHp = pokemon.hp; + } +} diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts new file mode 100644 index 00000000000..634f89b7922 --- /dev/null +++ b/src/ui/battle-info/player-battle-info.ts @@ -0,0 +1,242 @@ +import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { Stat } from "#enums/stat"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class PlayerBattleInfo extends BattleInfo { + protected player: true = true; + protected hpNumbersContainer: Phaser.GameObjects.Container; + + override get statOrder(): Stat[] { + return [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.mini ? "pbinfo_player_mini" : "pbinfo_player"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -115, + nameTextY: -15.2, + levelContainerX: -41, + levelContainerY: -10, + hpBarX: -61, + hpBarY: -1, + statBox: { + xOffset: 8, + paddingX: 4, + statOverflow: 1, + }, + }; + super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams); + + this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp"); + + // hp number container must be beneath the stat container for overlay to display properly + this.addAt(this.hpNumbersContainer, this.getIndex(this.statsContainer)); + + const expBar = globalScene.add.image(-98, 18, "overlay_exp").setName("overlay_exp").setOrigin(0); + this.add(expBar); + + const expMaskRect = globalScene.make + .graphics({}) + .setScale(6) + .fillStyle(0xffffff) + .beginPath() + .fillRect(127, 126, 85, 2); + + const expMask = expMaskRect.createGeometryMask(); + + expBar.setMask(expMask); + + this.expBar = expBar; + this.expMaskRect = expMaskRect; + } + + override initInfo(pokemon: PlayerPokemon): void { + super.initInfo(pokemon); + this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); + this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + + this.statValuesContainer.setPosition(8, 7); + } + + override setMini(mini: boolean): void { + if (this.mini === mini) { + return; + } + + this.mini = mini; + + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + + if (this.player) { + this.y -= 12 * (mini ? 1 : -1); + this.baseY = this.y; + } + + const offsetElements = [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.statusIndicator, + this.levelContainer, + ]; + offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); + + [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { + el.x += 4 * (mini ? 1 : -1); + el.y += -8 * (mini ? 1 : -1); + }); + + this.statValuesContainer.x += 2 * (mini ? 1 : -1); + this.statValuesContainer.y += -7 * (mini ? 1 : -1); + + const toggledElements = [this.hpNumbersContainer, this.expBar]; + toggledElements.forEach(el => el.setVisible(!mini)); + } + + /** + * Updates the Hp Number text (that is the "HP/Max HP" text that appears below the player's health bar) + * while the health bar is tweening. + * @param pokemon - The Pokemon the health bar belongs to. + */ + protected override onHpTweenUpdate(pokemon: PlayerPokemon): void { + const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); + this.setHpNumbers(tweenHp, pokemon.getMaxHp()); + this.lastHp = tweenHp; + this.updateHpFrame(); + } + + updatePokemonExp(pokemon: PlayerPokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { + const levelUp = this.lastLevel < pokemon.level; + const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); + const levelExp = levelUp ? relLevelExp : pokemon.levelExp; + let ratio = relLevelExp ? levelExp / relLevelExp : 0; + if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { + ratio = levelUp ? 1 : 0; + instant = true; + } + const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( + 1 - Math.max(this.lastLevel - 100, 0) / 150, + ); + let duration = + this.visible && !instant + ? ((levelExp - this.lastLevelExp) / relLevelExp) * + BattleInfo.EXP_GAINS_DURATION_BASE * + durationMultiplier * + levelDurationMultiplier + : 0; + const speed = globalScene.expGainsSpeed; + if (speed && speed >= ExpGainsSpeed.DEFAULT) { + duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); + } + if (ratio === 1) { + this.lastLevelExp = 0; + this.lastLevel++; + } else { + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + } + if (duration) { + globalScene.playSound("se/exp"); + } + return new Promise(resolve => { + globalScene.tweens.add({ + targets: this.expMaskRect, + ease: "Sine.easeIn", + x: ratio * 510, + duration: duration, + onComplete: () => { + if (!globalScene) { + return resolve(); + } + if (duration) { + globalScene.sound.stopByKey("se/exp"); + } + if (ratio === 1) { + globalScene.playSound("se/level_up"); + this.setLevel(this.lastLevel); + globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { + this.expMaskRect.x = 0; + this.updateInfo(pokemon, instant).then(() => resolve()); + }); + return; + } + resolve(); + }, + }); + }); + } + + /** + * Updates the info on the info bar. + * + * In addition to performing all the steps of {@linkcode BattleInfo.updateInfo}, + * it also updates the EXP Bar + */ + override async updateInfo(pokemon: PlayerPokemon, instant?: boolean): Promise { + await super.updateInfo(pokemon, instant); + const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); + const oldLevelCapped = this.lastLevelCapped; + this.lastLevelCapped = isLevelCapped; + + if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { + const durationMultipler = Math.max( + Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10), + 0.1, + ); + await this.updatePokemonExp(pokemon, false, durationMultipler); + } else if (isLevelCapped !== oldLevelCapped) { + this.setLevel(pokemon.level); + } + } + + /** + * Set the HP numbers text, that is the "HP/Max HP" text that appears below the player's health bar. + * @param hp - The current HP of the player. + * @param maxHp - The maximum HP of the player. + */ + setHpNumbers(hp: number, maxHp: number): void { + if (!globalScene) { + return; + } + this.hpNumbersContainer.removeAll(true); + const hpStr = hp.toString(); + const maxHpStr = maxHp.toString(); + let offset = 0; + for (let i = maxHpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); + } + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); + for (let i = hpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); + } + } + + /** + * Set the level numbers container to display the provided level + * + * Overrides the default implementation to handle displaying level capped numbers in red. + * @param level - The level to display + */ + override setLevel(level: number): void { + super.setLevel(level, level >= globalScene.getMaxExpLevel() ? "numbers_red" : "numbers"); + } +} diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 601eef9c51d..6b22dca219d 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -10,7 +10,7 @@ import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; @@ -287,7 +287,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { this.moveInfoOverlay.show(pokemonMove.getMove()); pokemon.getOpponents().forEach(opponent => { - opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + (opponent as EnemyPokemon).updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); }); } @@ -399,7 +399,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); opponents.forEach(opponent => { - opponent.updateEffectiveness(undefined); + (opponent as EnemyPokemon).updateEffectiveness(); }); } diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 714a9b39771..5c48cf55753 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -151,9 +151,9 @@ export default class LoginFormUiHandler extends FormModalUiHandler { // Prevent overlapping overrides on action modification this.submitAction = originalLoginAction; this.sanitizeInputs(); - globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); + globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); const onFail = error => { - globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); globalScene.ui.playError(); }; if (!this.inputs[0].text) { @@ -243,7 +243,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { }, }); } - globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { options: options, delay: 1000, }); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 2531c933d31..6b905830d68 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -52,6 +52,7 @@ export enum PartyUiMode { * Indicates that the party UI is open because of a start-of-encounter optional * switch. This type of switch can be cancelled. */ + // TODO: Rename to PRE_BATTLE_SWITCH POST_BATTLE_SWITCH, /** * Indicates that the party UI is open because of the move Revival Blessing. @@ -356,6 +357,522 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } + private processSummaryOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + ui.playSelect(); + ui.setModeWithoutClear(UiMode.SUMMARY, pokemon).then(() => this.clearOptions()); + return true; + } + + private processPokedexOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + ui.playSelect(); + const attributes = { + shiny: pokemon.shiny, + variant: pokemon.variant, + form: pokemon.formIndex, + female: pokemon.gender === Gender.FEMALE, + }; + ui.setOverlayMode(UiMode.POKEDEX_PAGE, pokemon.species, attributes).then(() => this.clearOptions()); + return true; + } + + private processUnpauseEvolutionOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + pokemon.pauseEvolutions = !pokemon.pauseEvolutions; + this.showText( + i18next.t(pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions", { + pokemonName: getPokemonNameWithAffix(pokemon, false), + }), + undefined, + () => this.showText("", 0), + null, + true, + ); + return true; + } + + private processUnspliceOption(pokemon: PlayerPokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + this.showText( + i18next.t("partyUiHandler:unspliceConfirmation", { + fusionName: pokemon.fusionSpecies?.name, + pokemonName: pokemon.getName(), + }), + null, + () => { + ui.setModeWithoutClear( + UiMode.CONFIRM, + () => { + const fusionName = pokemon.getName(); + pokemon.unfuse().then(() => { + this.clearPartySlots(); + this.populatePartySlots(); + ui.setMode(UiMode.PARTY); + this.showText( + i18next.t("partyUiHandler:wasReverted", { + fusionName: fusionName, + pokemonName: pokemon.getName(false), + }), + undefined, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + null, + true, + ); + }); + }, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + ); + }, + ); + return true; + } + + private processReleaseOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + // In release mode, we do not ask for confirmation when clicking release. + if (this.partyUiMode === PartyUiMode.RELEASE) { + this.doRelease(this.cursor); + return true; + } + if (this.cursor >= globalScene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { + this.blockInput = true; + this.showText( + i18next.t("partyUiHandler:releaseConfirmation", { + pokemonName: getPokemonNameWithAffix(pokemon, false), + }), + null, + () => { + this.blockInput = false; + ui.setModeWithoutClear( + UiMode.CONFIRM, + () => { + ui.setMode(UiMode.PARTY); + this.doRelease(this.cursor); + }, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + ); + }, + ); + } else { + this.showText(i18next.t("partyUiHandler:releaseInBattle"), null, () => this.showText("", 0), null, true); + } + return true; + } + + private processRenameOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + ui.setModeWithoutClear( + UiMode.RENAME_POKEMON, + { + buttonActions: [ + (nickname: string) => { + ui.playSelect(); + pokemon.nickname = nickname; + pokemon.updateInfo(); + this.clearPartySlots(); + this.populatePartySlots(); + ui.setMode(UiMode.PARTY); + }, + () => { + ui.setMode(UiMode.PARTY); + }, + ], + }, + pokemon, + ); + return true; + } + + // TODO: Does this need to check that selectCallback exists? + private processTransferOption(): boolean { + const ui = this.getUi(); + if (this.transferCursor !== this.cursor) { + if (this.transferAll) { + this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( + (_, i, array) => { + const invertedIndex = array.length - 1 - i; + (this.selectCallback as PartyModifierTransferSelectCallback)( + this.transferCursor, + invertedIndex, + this.transferQuantitiesMax[invertedIndex], + this.cursor, + ); + }, + ); + } else { + (this.selectCallback as PartyModifierTransferSelectCallback)( + this.transferCursor, + this.transferOptionCursor, + this.transferQuantities[this.transferOptionCursor], + this.cursor, + ); + } + } + this.clearTransfer(); + this.clearOptions(); + ui.playSelect(); + return true; + } + + // TODO: This will be largely changed with the modifier rework + private processModifierTransferModeInput(pokemon: PlayerPokemon) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + if (option === PartyOption.TRANSFER) { + return this.processTransferOption(); + } + + // TODO: Revise this condition + if (!this.transferMode) { + this.startTransfer(); + + let ableToTransferText: string; + for (let p = 0; p < globalScene.getPlayerParty().length; p++) { + // this for look goes through each of the party pokemon + const newPokemon = globalScene.getPlayerParty()[p]; + // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon `p` + // this returns `undefined` if the new pokemon doesn't have the item at all, otherwise it returns the `pokemonHeldItemModifier` for that item + const matchingModifier = globalScene.findModifier( + m => + m instanceof PokemonHeldItemModifier && + m.pokemonId === newPokemon.id && + m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), + ) as PokemonHeldItemModifier; + const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us + if (p !== this.transferCursor) { + // this skips adding the able/not able labels on the pokemon doing the transfer + if (matchingModifier) { + // if matchingModifier exists then the item exists on the new pokemon + if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { + // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" + ableToTransferText = i18next.t("partyUiHandler:notAble"); + } else { + // if the pokemon isn't at max stack, make the label "Able" + ableToTransferText = i18next.t("partyUiHandler:able"); + } + } else { + // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able" + ableToTransferText = i18next.t("partyUiHandler:able"); + } + } else { + // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text + ableToTransferText = ""; + } + partySlot.slotHpBar.setVisible(false); + partySlot.slotHpOverlay.setVisible(false); + partySlot.slotHpText.setVisible(false); + partySlot.slotDescriptionLabel.setText(ableToTransferText); + partySlot.slotDescriptionLabel.setVisible(true); + } + this.clearOptions(); + ui.playSelect(); + return true; + } + return false; + } + + // TODO: Might need to check here for when this.transferMode is active. + private processModifierTransferModeLeftRightInput(button: Button) { + let success = false; + const option = this.options[this.optionsCursor]; + if (button === Button.LEFT) { + /** Decrease quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = + this.transferQuantities[option] === 1 + ? this.transferQuantitiesMax[option] + : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor( + this.optionsCursor, + ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + } + + if (button === Button.RIGHT) { + /** Increase quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = + this.transferQuantities[option] === this.transferQuantitiesMax[option] + ? 1 + : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor( + this.optionsCursor, + ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + } + return success; + } + + // TODO: Might need to check here for when this.transferMode is active. + private processModifierTransferModeUpDownInput(button: Button.UP | Button.DOWN) { + let success = false; + const option = this.options[this.optionsCursor]; + + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); + } + success = this.moveOptionCursor(button); + + return success; + } + + private moveOptionCursor(button: Button.UP | Button.DOWN): boolean { + if (button === Button.UP) { + return this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); + } + return this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); + } + + private processRememberMoveModeInput(pokemon: PlayerPokemon) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + // clear overlay on cancel + this.moveInfoOverlay.clear(); + const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null) { + this.selectCallback?.(this.cursor, option); + this.clearOptions(); + } else { + this.clearOptions(); + this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); + } + ui.playSelect(); + return true; + } + + private processRememberMoveModeUpDownInput(button: Button.UP | Button.DOWN) { + let success = false; + + success = this.moveOptionCursor(button); + + // show move description + const option = this.options[this.optionsCursor]; + const pokemon = globalScene.getPlayerParty()[this.cursor]; + const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; + if (move) { + this.moveInfoOverlay.show(move); + } else { + // or hide the overlay, in case it's the cancel button + this.moveInfoOverlay.clear(); + } + + return success; + } + + private getTransferrableItemsFromPokemon(pokemon: PlayerPokemon) { + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + ) as PokemonHeldItemModifier[]; + } + + private getFilterResult(option: number, pokemon: PlayerPokemon): string | null { + let filterResult: string | null; + if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { + filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { + filterResult = this.FilterChallengeLegal(pokemon); + } + if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { + filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); + } + } else { + filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( + pokemon, + this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ + this.transferOptionCursor + ], + ); + } + return filterResult; + } + + private processActionButtonForOptions(option: PartyOption) { + const ui = this.getUi(); + if (option === PartyOption.CANCEL) { + return this.processOptionMenuInput(Button.CANCEL); + } + + // If the input has been already processed we are done, otherwise move on until the correct option is found + const pokemon = globalScene.getPlayerParty()[this.cursor]; + + // TODO: Careful about using success for the return values here. Find a better way + // PartyOption.ALL, and options specific to the mode (held items) + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeInput(pokemon); + } + + // options specific to the mode (moves) + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + return this.processRememberMoveModeInput(pokemon); + } + + // These are the options that do not involve a callback + if (option === PartyOption.SUMMARY) { + return this.processSummaryOption(pokemon); + } + if (option === PartyOption.POKEDEX) { + return this.processPokedexOption(pokemon); + } + if (option === PartyOption.UNPAUSE_EVOLUTION) { + return this.processUnpauseEvolutionOption(pokemon); + } + if (option === PartyOption.UNSPLICE) { + return this.processUnspliceOption(pokemon); + } + if (option === PartyOption.RENAME) { + return this.processRenameOption(pokemon); + } + // This is only relevant for PartyUiMode.CHECK + // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? + if ( + option >= PartyOption.FORM_CHANGE_ITEM && + globalScene.getCurrentPhase() instanceof SelectModifierPhase && + this.partyUiMode === PartyUiMode.CHECK + ) { + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; + modifier.active = !modifier.active; + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); + } + + // If the pokemon is filtered out for this option, we cannot continue + const filterResult = this.getFilterResult(option, pokemon); + if (filterResult) { + this.clearOptions(); + this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); + return true; + } + + // For what modes is a selectCallback needed? + // PartyUiMode.SELECT (SELECT) + // PartyUiMode.RELEASE (RELEASE) + // PartyUiMode.FAINT_SWITCH (SEND_OUT or PASS_BATON (?)) + // PartyUiMode.REVIVAL_BLESSING (REVIVE) + // PartyUiMode.MODIFIER_TRANSFER (held items, and ALL) + // PartyUiMode.CHECK --- no specific option, only relevant on cancel? + // PartyUiMode.SPLICE (SPLICE) + // PartyUiMode.MOVE_MODIFIER (MOVE_1, MOVE_2, MOVE_3, MOVE_4) + // PartyUiMode.TM_MODIFIER (TEACH) + // PartyUiMode.REMEMBER_MOVE_MODIFIER (no specific option, callback is invoked when selecting a move) + // PartyUiMode.MODIFIER (APPLY option) + // PartyUiMode.POST_BATTLE_SWITCH (SEND_OUT) + + // These are the options that need a callback + if (option === PartyOption.RELEASE) { + return this.processReleaseOption(pokemon); + } + + if (this.partyUiMode === PartyUiMode.SPLICE) { + if (option === PartyOption.SPLICE) { + (this.selectCallback as PartyModifierSpliceSelectCallback)(this.transferCursor, this.cursor); + this.clearTransfer(); + } else if (option === PartyOption.APPLY) { + this.startTransfer(); + } + this.clearOptions(); + ui.playSelect(); + return true; + } + + // This is used when switching out using the Pokemon command (possibly holding a Baton held item). In this case there is no callback. + if ( + (option === PartyOption.PASS_BATON || option === PartyOption.SEND_OUT) && + this.partyUiMode === PartyUiMode.SWITCH + ) { + this.clearOptions(); + (globalScene.getCurrentPhase() as CommandPhase).handleCommand( + Command.POKEMON, + this.cursor, + option === PartyOption.PASS_BATON, + ); + } + + if ( + [ + PartyOption.SEND_OUT, // When sending out at the start of battle, or due to an effect + PartyOption.PASS_BATON, // When passing the baton due to the Baton Pass move + PartyOption.REVIVE, + PartyOption.APPLY, + PartyOption.TEACH, + PartyOption.MOVE_1, + PartyOption.MOVE_2, + PartyOption.MOVE_3, + PartyOption.MOVE_4, + PartyOption.SELECT, + ].includes(option) && + this.selectCallback + ) { + this.clearOptions(); + const selectCallback = this.selectCallback; + this.selectCallback = null; + selectCallback(this.cursor, option); + return true; + } + + return false; + } + + private processOptionMenuInput(button: Button) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + // Button.CANCEL has no special behavior for any option + if (button === Button.CANCEL) { + this.clearOptions(); + ui.playSelect(); + return true; + } + + if (button === Button.ACTION) { + return this.processActionButtonForOptions(option); + } + + if (button === Button.UP || button === Button.DOWN) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeUpDownInput(button); + } + + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + return this.processRememberMoveModeUpDownInput(button); + } + + return this.moveOptionCursor(button); + } + + if (button === Button.LEFT || button === Button.RIGHT) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeLeftRightInput(button); + } + } + + return false; + } + processInput(button: Button): boolean { const ui = this.getUi(); @@ -375,463 +892,120 @@ export default class PartyUiHandler extends MessageUiHandler { return false; } - let success = false; - if (this.optionsMode) { - const option = this.options[this.optionsCursor]; - if (button === Button.ACTION) { - const pokemon = globalScene.getPlayerParty()[this.cursor]; - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { - this.startTransfer(); - - let ableToTransfer: string; - for (let p = 0; p < globalScene.getPlayerParty().length; p++) { - // this for look goes through each of the party pokemon - const newPokemon = globalScene.getPlayerParty()[p]; - // this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p] - const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) => - globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - (m as PokemonHeldItemModifier).isTransferable && - (m as PokemonHeldItemModifier).pokemonId === newPokemon.id, - ) as PokemonHeldItemModifier[]; - // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; this returns undefined if the new pokemon doesn't have the item at all, otherwise it returns the pokemonHeldItemModifier for that item - const matchingModifier = globalScene.findModifier( - m => - m instanceof PokemonHeldItemModifier && - m.pokemonId === newPokemon.id && - m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), - ) as PokemonHeldItemModifier; - const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us - if (p !== this.transferCursor) { - // this skips adding the able/not able labels on the pokemon doing the transfer - if (matchingModifier) { - // if matchingModifier exists then the item exists on the new pokemon - if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { - // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" - ableToTransfer = i18next.t("partyUiHandler:notAble"); - } else { - // if the pokemon isn't at max stack, make the label "Able" - ableToTransfer = i18next.t("partyUiHandler:able"); - } - } else { - // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able" - ableToTransfer = i18next.t("partyUiHandler:able"); - } - } else { - // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text - ableToTransfer = ""; - } - partySlot.slotHpBar.setVisible(false); - partySlot.slotHpOverlay.setVisible(false); - partySlot.slotHpText.setVisible(false); - partySlot.slotDescriptionLabel.setText(ableToTransfer); - partySlot.slotDescriptionLabel.setVisible(true); - } - - this.clearOptions(); - ui.playSelect(); - return true; - } - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && option !== PartyOption.CANCEL) { - // clear overlay on cancel - this.moveInfoOverlay.clear(); - const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); - if (filterResult === null) { - this.selectCallback?.(this.cursor, option); - this.clearOptions(); - } else { - this.clearOptions(); - this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); - } - ui.playSelect(); - return true; - } - if ( - ![ - PartyOption.SUMMARY, - PartyOption.POKEDEX, - PartyOption.UNPAUSE_EVOLUTION, - PartyOption.UNSPLICE, - PartyOption.RELEASE, - PartyOption.CANCEL, - PartyOption.RENAME, - ].includes(option) || - (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE) - ) { - let filterResult: string | null; - const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => - globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]; - if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { - filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); - if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { - filterResult = this.FilterChallengeLegal(pokemon); - } - if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { - filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); - } - } else { - filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( - pokemon, - getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ - this.transferOptionCursor - ], - ); - } - if (filterResult === null) { - if (this.partyUiMode !== PartyUiMode.SPLICE) { - this.clearOptions(); - } - if (this.selectCallback && this.partyUiMode !== PartyUiMode.CHECK) { - if (option === PartyOption.TRANSFER) { - if (this.transferCursor !== this.cursor) { - if (this.transferAll) { - getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( - (_, i, array) => { - const invertedIndex = array.length - 1 - i; - (this.selectCallback as PartyModifierTransferSelectCallback)( - this.transferCursor, - invertedIndex, - this.transferQuantitiesMax[invertedIndex], - this.cursor, - ); - }, - ); - } else { - (this.selectCallback as PartyModifierTransferSelectCallback)( - this.transferCursor, - this.transferOptionCursor, - this.transferQuantities[this.transferOptionCursor], - this.cursor, - ); - } - } - this.clearTransfer(); - } else if (this.partyUiMode === PartyUiMode.SPLICE) { - if (option === PartyOption.SPLICE) { - (this.selectCallback as PartyModifierSpliceSelectCallback)(this.transferCursor, this.cursor); - this.clearTransfer(); - } else { - this.startTransfer(); - } - this.clearOptions(); - } else if (option === PartyOption.RELEASE) { - this.doRelease(this.cursor); - } else { - const selectCallback = this.selectCallback; - this.selectCallback = null; - selectCallback(this.cursor, option); - } - } else { - if ( - option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.getCurrentPhase() instanceof SelectModifierPhase - ) { - if (this.partyUiMode === PartyUiMode.CHECK) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - modifier.active = !modifier.active; - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); - } - } else if (this.cursor) { - (globalScene.getCurrentPhase() as CommandPhase).handleCommand( - Command.POKEMON, - this.cursor, - option === PartyOption.PASS_BATON, - ); - } - } - if ( - this.partyUiMode !== PartyUiMode.MODIFIER && - this.partyUiMode !== PartyUiMode.TM_MODIFIER && - this.partyUiMode !== PartyUiMode.MOVE_MODIFIER - ) { - ui.playSelect(); - } - return true; - } - this.clearOptions(); - this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); - } else if (option === PartyOption.SUMMARY) { - ui.playSelect(); - ui.setModeWithoutClear(UiMode.SUMMARY, pokemon).then(() => this.clearOptions()); - return true; - } else if (option === PartyOption.POKEDEX) { - ui.playSelect(); - const attributes = { - shiny: pokemon.shiny, - variant: pokemon.variant, - form: pokemon.formIndex, - female: pokemon.gender === Gender.FEMALE, - }; - ui.setOverlayMode(UiMode.POKEDEX_PAGE, pokemon.species, attributes).then(() => this.clearOptions()); - return true; - } else if (option === PartyOption.UNPAUSE_EVOLUTION) { - this.clearOptions(); - ui.playSelect(); - pokemon.pauseEvolutions = !pokemon.pauseEvolutions; - this.showText( - i18next.t( - pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions", - { pokemonName: getPokemonNameWithAffix(pokemon, false) }, - ), - undefined, - () => this.showText("", 0), - null, - true, - ); - } else if (option === PartyOption.UNSPLICE) { - this.clearOptions(); - ui.playSelect(); - this.showText( - i18next.t("partyUiHandler:unspliceConfirmation", { - fusionName: pokemon.fusionSpecies?.name, - pokemonName: pokemon.getName(), - }), - null, - () => { - ui.setModeWithoutClear( - UiMode.CONFIRM, - () => { - const fusionName = pokemon.getName(); - pokemon.unfuse().then(() => { - this.clearPartySlots(); - this.populatePartySlots(); - ui.setMode(UiMode.PARTY); - this.showText( - i18next.t("partyUiHandler:wasReverted", { - fusionName: fusionName, - pokemonName: pokemon.getName(false), - }), - undefined, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - null, - true, - ); - }); - }, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - ); - }, - ); - } else if (option === PartyOption.RELEASE) { - this.clearOptions(); - ui.playSelect(); - if (this.cursor >= globalScene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { - this.blockInput = true; - this.showText( - i18next.t("partyUiHandler:releaseConfirmation", { - pokemonName: getPokemonNameWithAffix(pokemon, false), - }), - null, - () => { - this.blockInput = false; - ui.setModeWithoutClear( - UiMode.CONFIRM, - () => { - ui.setMode(UiMode.PARTY); - this.doRelease(this.cursor); - }, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - ); - }, - ); - } else { - this.showText(i18next.t("partyUiHandler:releaseInBattle"), null, () => this.showText("", 0), null, true); - } - return true; - } else if (option === PartyOption.RENAME) { - this.clearOptions(); - ui.playSelect(); - ui.setModeWithoutClear( - UiMode.RENAME_POKEMON, - { - buttonActions: [ - (nickname: string) => { - ui.playSelect(); - pokemon.nickname = nickname; - pokemon.updateInfo(); - this.clearPartySlots(); - this.populatePartySlots(); - ui.setMode(UiMode.PARTY); - }, - () => { - ui.setMode(UiMode.PARTY); - }, - ], - }, - pokemon, - ); - return true; - } else if (option === PartyOption.CANCEL) { - return this.processInput(Button.CANCEL); - } else if (option === PartyOption.SELECT) { - ui.playSelect(); - return true; - } - } else if (button === Button.CANCEL) { - this.clearOptions(); + let success = false; + success = this.processOptionMenuInput(button); + if (success) { ui.playSelect(); - return true; + } + return success; + } + + if (button === Button.ACTION) { + return this.processPartyActionInput(); + } + + if (button === Button.CANCEL) { + return this.processPartyCancelInput(); + } + + if (button === Button.UP || button === Button.DOWN || button === Button.RIGHT || button === Button.LEFT) { + return this.processPartyDirectionalInput(button); + } + + return false; + } + + private allowCancel(): boolean { + return !(this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING); + } + + private processPartyActionInput(): boolean { + const ui = this.getUi(); + if (this.cursor < 6) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { + /** Initialize item quantities for the selected Pokemon */ + const itemModifiers = globalScene.findModifiers( + m => + m instanceof PokemonHeldItemModifier && + m.isTransferable && + m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, + ) as PokemonHeldItemModifier[]; + this.transferQuantities = itemModifiers.map(item => item.getStackCount()); + this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); + } + this.showOptions(); + ui.playSelect(); + } + // Pressing return button + if (this.cursor === 6) { + if (!this.allowCancel()) { + ui.playError(); } else { - switch (button) { - case Button.LEFT: - /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = - this.transferQuantities[option] === 1 - ? this.transferQuantitiesMax[option] - : this.transferQuantities[option] - 1; - this.updateOptions(); - success = this.setCursor( - this.optionsCursor, - ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.RIGHT: - /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = - this.transferQuantities[option] === this.transferQuantitiesMax[option] - ? 1 - : this.transferQuantities[option] + 1; - this.updateOptions(); - success = this.setCursor( - this.optionsCursor, - ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.UP: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; - } - this.updateOptions(); - } - success = this.setCursor( - this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1, - ); /** Move cursor */ - break; - case Button.DOWN: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; - } - this.updateOptions(); - } - success = this.setCursor( - this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0, - ); /** Move cursor */ - break; - } - - // show move description - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { - const option = this.options[this.optionsCursor]; - const pokemon = globalScene.getPlayerParty()[this.cursor]; - const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; - if (move) { - this.moveInfoOverlay.show(move); - } else { - // or hide the overlay, in case it's the cancel button - this.moveInfoOverlay.clear(); - } - } + return this.processInput(Button.CANCEL); } - } else { - if (button === Button.ACTION) { - if (this.cursor < 6) { - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { - /** Initialize item quantities for the selected Pokemon */ - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, - ) as PokemonHeldItemModifier[]; - this.transferQuantities = itemModifiers.map(item => item.getStackCount()); - this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); - } - this.showOptions(); - ui.playSelect(); - } else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING) { - ui.playError(); - } else { - return this.processInput(Button.CANCEL); - } - return true; + } + return true; + } + + private processPartyCancelInput(): boolean { + const ui = this.getUi(); + if ( + (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && + this.transferMode + ) { + this.clearTransfer(); + ui.playSelect(); + } else if (this.allowCancel()) { + if (this.selectCallback) { + const selectCallback = this.selectCallback; + this.selectCallback = null; + selectCallback(6, PartyOption.CANCEL); + ui.playSelect(); + } else { + ui.setMode(UiMode.COMMAND, this.fieldIndex); + ui.playSelect(); } - if (button === Button.CANCEL) { - if ( - (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && - this.transferMode - ) { - this.clearTransfer(); - ui.playSelect(); - } else if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.partyUiMode !== PartyUiMode.REVIVAL_BLESSING) { - if (this.selectCallback) { - const selectCallback = this.selectCallback; - this.selectCallback = null; - selectCallback(6, PartyOption.CANCEL); - ui.playSelect(); - } else { - ui.setMode(UiMode.COMMAND, this.fieldIndex); - ui.playSelect(); - } + } + return true; + } + + private processPartyDirectionalInput(button: Button.UP | Button.DOWN | Button.LEFT | Button.RIGHT): boolean { + const ui = this.getUi(); + const slotCount = this.partySlots.length; + const battlerCount = globalScene.currentBattle.getBattlerCount(); + + let success = false; + switch (button) { + case Button.UP: + success = this.setCursor(this.cursor ? (this.cursor < 6 ? this.cursor - 1 : slotCount - 1) : 6); + break; + case Button.DOWN: + success = this.setCursor(this.cursor < 6 ? (this.cursor < slotCount - 1 ? this.cursor + 1 : 6) : 0); + break; + case Button.LEFT: + if (this.cursor >= battlerCount && this.cursor <= 6) { + success = this.setCursor(0); } - - return true; - } - - const slotCount = this.partySlots.length; - const battlerCount = globalScene.currentBattle.getBattlerCount(); - - switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? (this.cursor < 6 ? this.cursor - 1 : slotCount - 1) : 6); + break; + case Button.RIGHT: + if (slotCount === battlerCount) { + success = this.setCursor(6); break; - case Button.DOWN: - success = this.setCursor(this.cursor < 6 ? (this.cursor < slotCount - 1 ? this.cursor + 1 : 6) : 0); + } + if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { + success = this.setCursor(2); break; - case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); - } + } + if (slotCount > battlerCount && this.cursor < battlerCount) { + success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); break; - case Button.RIGHT: - if (slotCount === battlerCount) { - success = this.setCursor(6); - break; - } - if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); - break; - } - if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); - break; - } - } + } } if (success) { ui.playSelect(); } - return success; } @@ -860,64 +1034,66 @@ export default class PartyUiHandler extends MessageUiHandler { } setCursor(cursor: number): boolean { - let changed: boolean; - if (this.optionsMode) { - changed = this.optionsCursor !== cursor; - let isScroll = false; - if (changed && this.optionsScroll) { - if (Math.abs(cursor - this.optionsCursor) === this.options.length - 1) { - this.optionsScrollCursor = cursor ? this.optionsScrollTotal - 8 : 0; - this.updateOptions(); - } else { - const isDown = cursor && cursor > this.optionsCursor; - if (isDown) { - if (this.options[cursor] === PartyOption.SCROLL_DOWN) { - isScroll = true; - this.optionsScrollCursor++; - } - } else { - if (!cursor && this.optionsScrollCursor) { - isScroll = true; - this.optionsScrollCursor--; - } - } - if (isScroll && this.optionsScrollCursor === 1) { - this.optionsScrollCursor += isDown ? 1 : -1; - } - } + return this.setOptionsCursor(cursor); + } + const changed = this.cursor !== cursor; + if (changed) { + this.lastCursor = this.cursor; + this.cursor = cursor; + if (this.lastCursor < 6) { + this.partySlots[this.lastCursor].deselect(); + } else if (this.lastCursor === 6) { + this.partyCancelButton.deselect(); } - if (isScroll) { + if (cursor < 6) { + this.partySlots[cursor].select(); + } else if (cursor === 6) { + this.partyCancelButton.select(); + } + } + return changed; + } + + private setOptionsCursor(cursor: number): boolean { + const changed = this.optionsCursor !== cursor; + let isScroll = false; + if (changed && this.optionsScroll) { + if (Math.abs(cursor - this.optionsCursor) === this.options.length - 1) { + this.optionsScrollCursor = cursor ? this.optionsScrollTotal - 8 : 0; this.updateOptions(); } else { - this.optionsCursor = cursor; - } - if (!this.optionsCursorObj) { - this.optionsCursorObj = globalScene.add.image(0, 0, "cursor"); - this.optionsCursorObj.setOrigin(0, 0); - this.optionsContainer.add(this.optionsCursorObj); - } - this.optionsCursorObj.setPosition( - 8 - this.optionsBg.displayWidth, - -19 - 16 * (this.options.length - 1 - this.optionsCursor), - ); - } else { - changed = this.cursor !== cursor; - if (changed) { - this.lastCursor = this.cursor; - this.cursor = cursor; - if (this.lastCursor < 6) { - this.partySlots[this.lastCursor].deselect(); - } else if (this.lastCursor === 6) { - this.partyCancelButton.deselect(); + const isDown = cursor && cursor > this.optionsCursor; + if (isDown) { + if (this.options[cursor] === PartyOption.SCROLL_DOWN) { + isScroll = true; + this.optionsScrollCursor++; + } + } else { + if (!cursor && this.optionsScrollCursor) { + isScroll = true; + this.optionsScrollCursor--; + } } - if (cursor < 6) { - this.partySlots[cursor].select(); - } else if (cursor === 6) { - this.partyCancelButton.select(); + if (isScroll && this.optionsScrollCursor === 1) { + this.optionsScrollCursor += isDown ? 1 : -1; } } } + if (isScroll) { + this.updateOptions(); + } else { + this.optionsCursor = cursor; + } + if (!this.optionsCursorObj) { + this.optionsCursorObj = globalScene.add.image(0, 0, "cursor"); + this.optionsCursorObj.setOrigin(0, 0); + this.optionsContainer.add(this.optionsCursorObj); + } + this.optionsCursorObj.setPosition( + 8 - this.optionsBg.displayWidth, + -19 - 16 * (this.options.length - 1 - this.optionsCursor), + ); return changed; } @@ -984,149 +1160,80 @@ export default class PartyUiHandler extends MessageUiHandler { this.setCursor(0); } - updateOptions(): void { - const pokemon = globalScene.getPlayerParty()[this.cursor]; + private allowBatonModifierSwitch(): boolean { + return !!( + this.partyUiMode !== PartyUiMode.FAINT_SWITCH && + globalScene.findModifier( + m => + m instanceof SwitchEffectTransferModifier && + m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, + ) + ); + } - const learnableLevelMoves = - this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER ? pokemon.getLearnableLevelMoves() : []; + // TODO: add FORCED_SWITCH (and perhaps also BATON_PASS_SWITCH) to the modes + private isBatonPassMove(): boolean { + const lastMove: TurnMove | undefined = globalScene.getPlayerField()[this.fieldIndex].getLastXMoves()[0]; + return ( + this.partyUiMode === PartyUiMode.FAINT_SWITCH && + allMoves[lastMove.move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && + lastMove?.result === MoveResult.SUCCESS + ); + } - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && learnableLevelMoves?.length) { + private getItemModifiers(pokemon: Pokemon): PokemonHeldItemModifier[] { + return ( + (globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + ) as PokemonHeldItemModifier[]) ?? [] + ); + } + + private updateOptionsWithRememberMoveModifierMode(pokemon): void { + const learnableMoves = pokemon.getLearnableLevelMoves(); + for (let m = 0; m < learnableMoves.length; m++) { + this.options.push(m); + } + if (learnableMoves?.length) { // show the move overlay with info for the first move - this.moveInfoOverlay.show(allMoves[learnableLevelMoves[0]]); + this.moveInfoOverlay.show(allMoves[learnableMoves[0]]); } + } - const itemModifiers = - this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER - ? (globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]) - : []; - - if (this.options.length) { - this.options.splice(0, this.options.length); - this.optionsContainer.removeAll(true); - this.eraseOptionsCursor(); + private updateOptionsWithMoveModifierMode(pokemon): void { + // MOVE_1, MOVE_2, MOVE_3, MOVE_4 + for (let m = 0; m < pokemon.moveset.length; m++) { + this.options.push(PartyOption.MOVE_1 + m); } + } - let formChangeItemModifiers: PokemonFormChangeItemModifier[] | undefined; + private updateOptionsWithModifierTransferMode(pokemon): void { + const itemModifiers = this.getItemModifiers(pokemon); + for (let im = 0; im < itemModifiers.length; im++) { + this.options.push(im); + } + if (itemModifiers.length > 1) { + this.options.push(PartyOption.ALL); + } + } + + private addCommonOptions(pokemon): void { + this.options.push(PartyOption.SUMMARY); + this.options.push(PartyOption.POKEDEX); + this.options.push(PartyOption.RENAME); if ( - this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && - this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && - (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER) + pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || + (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)) ) { - switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = - this.partyUiMode !== PartyUiMode.FAINT_SWITCH && - globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, - ); - - const lastMove: TurnMove | undefined = globalScene.getPlayerField()[this.fieldIndex].getLastXMoves()[0]; - const isBatonPassMove = - this.partyUiMode === PartyUiMode.FAINT_SWITCH && - !isNullOrUndefined(lastMove) && - allMoves[lastMove.move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && - lastMove.result === MoveResult.SUCCESS; - - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true - // at the same time, because they both explicitly check for a mutually - // exclusive partyUiMode. But better safe than sorry. - this.options.push( - isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT, - ); - if (allowBatonModifierSwitch && !isBatonPassMove) { - // the BATON modifier gives an extra switch option for - // pokemon-command switches, allowing buffs to be optionally passed - this.options.push(PartyOption.PASS_BATON); - } - } - break; - case PartyUiMode.REVIVAL_BLESSING: - this.options.push(PartyOption.REVIVE); - break; - case PartyUiMode.MODIFIER: - this.options.push(PartyOption.APPLY); - break; - case PartyUiMode.TM_MODIFIER: - this.options.push(PartyOption.TEACH); - break; - case PartyUiMode.MODIFIER_TRANSFER: - this.options.push(PartyOption.TRANSFER); - break; - case PartyUiMode.SPLICE: - if (this.transferMode) { - if (this.cursor !== this.transferCursor) { - this.options.push(PartyOption.SPLICE); - } - } else { - this.options.push(PartyOption.APPLY); - } - break; - case PartyUiMode.RELEASE: - this.options.push(PartyOption.RELEASE); - break; - case PartyUiMode.CHECK: - if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { - formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); - } - } - break; - case PartyUiMode.SELECT: - this.options.push(PartyOption.SELECT); - break; - } - - this.options.push(PartyOption.SUMMARY); - this.options.push(PartyOption.POKEDEX); - this.options.push(PartyOption.RENAME); - - if ( - pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || - (pokemon.isFusion() && - pokemon.fusionSpecies && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)) - ) { - this.options.push(PartyOption.UNPAUSE_EVOLUTION); - } - - if (this.partyUiMode === PartyUiMode.SWITCH) { - if (pokemon.isFusion()) { - this.options.push(PartyOption.UNSPLICE); - } - this.options.push(PartyOption.RELEASE); - } else if (this.partyUiMode === PartyUiMode.SPLICE && pokemon.isFusion()) { - this.options.push(PartyOption.UNSPLICE); - } - } else if (this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { - for (let m = 0; m < pokemon.moveset.length; m++) { - this.options.push(PartyOption.MOVE_1 + m); - } - } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { - const learnableMoves = pokemon.getLearnableLevelMoves(); - for (let m = 0; m < learnableMoves.length; m++) { - this.options.push(m); - } - } else { - for (let im = 0; im < itemModifiers.length; im++) { - this.options.push(im); - } - if (itemModifiers.length > 1) { - this.options.push(PartyOption.ALL); - } + this.options.push(PartyOption.UNPAUSE_EVOLUTION); } + } + private addCancelAndScrollOptions(): void { this.optionsScrollTotal = this.options.length; - let optionStartIndex = this.optionsScrollCursor; - let optionEndIndex = Math.min( + const optionStartIndex = this.optionsScrollCursor; + const optionEndIndex = Math.min( this.optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.optionsScrollCursor + 8 >= this.optionsScrollTotal ? 8 : 7), ); @@ -1145,14 +1252,122 @@ export default class PartyUiHandler extends MessageUiHandler { } this.options.push(PartyOption.CANCEL); + } + + updateOptions(): void { + const pokemon = globalScene.getPlayerParty()[this.cursor]; + + if (this.options.length) { + this.options.splice(0, this.options.length); + this.optionsContainer.removeAll(true); + this.eraseOptionsCursor(); + } + + switch (this.partyUiMode) { + case PartyUiMode.MOVE_MODIFIER: + this.updateOptionsWithMoveModifierMode(pokemon); + break; + case PartyUiMode.REMEMBER_MOVE_MODIFIER: + this.updateOptionsWithRememberMoveModifierMode(pokemon); + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) { + this.updateOptionsWithModifierTransferMode(pokemon); + } else { + this.options.push(PartyOption.TRANSFER); + this.addCommonOptions(pokemon); + } + break; + // TODO: This still needs to be broken up. + // It could use a rework differentiating different kind of switches + // to treat baton passing separately from switching on faint. + case PartyUiMode.SWITCH: + case PartyUiMode.FAINT_SWITCH: + case PartyUiMode.POST_BATTLE_SWITCH: + if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { + const allowBatonModifierSwitch = this.allowBatonModifierSwitch(); + const isBatonPassMove = this.isBatonPassMove(); + + // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // at the same time, because they both explicitly check for a mutually + // exclusive partyUiMode. But better safe than sorry. + this.options.push( + isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT, + ); + if (allowBatonModifierSwitch && !isBatonPassMove) { + // the BATON modifier gives an extra switch option for + // pokemon-command switches, allowing buffs to be optionally passed + this.options.push(PartyOption.PASS_BATON); + } + } + this.addCommonOptions(pokemon); + if (this.partyUiMode === PartyUiMode.SWITCH) { + if (pokemon.isFusion()) { + this.options.push(PartyOption.UNSPLICE); + } + this.options.push(PartyOption.RELEASE); + } + break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.MODIFIER: + this.options.push(PartyOption.APPLY); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.TM_MODIFIER: + this.options.push(PartyOption.TEACH); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.SPLICE: + if (this.transferMode) { + if (this.cursor !== this.transferCursor) { + this.options.push(PartyOption.SPLICE); + } + } else { + this.options.push(PartyOption.APPLY); + } + this.addCommonOptions(pokemon); + if (pokemon.isFusion()) { + this.options.push(PartyOption.UNSPLICE); + } + break; + case PartyUiMode.RELEASE: + this.options.push(PartyOption.RELEASE); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.CHECK: + if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + this.addCommonOptions(pokemon); + break; + case PartyUiMode.SELECT: + this.options.push(PartyOption.SELECT); + this.addCommonOptions(pokemon); + break; + } + + // Generic, these are applied to all Modes + this.addCancelAndScrollOptions(); + + this.updateOptionsWindow(); + } + + private updateOptionsWindow(): void { + const pokemon = globalScene.getPlayerParty()[this.cursor]; this.optionsBg = addWindow(0, 0, 0, 16 * this.options.length + 13); this.optionsBg.setOrigin(1, 1); this.optionsContainer.add(this.optionsBg); - optionStartIndex = 0; - optionEndIndex = this.options.length; + const optionStartIndex = 0; + const optionEndIndex = this.options.length; let widestOptionWidth = 0; const optionTexts: BBCodeText[] = []; @@ -1185,6 +1400,7 @@ export default class PartyUiHandler extends MessageUiHandler { } break; default: + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; @@ -1200,6 +1416,7 @@ export default class PartyUiHandler extends MessageUiHandler { break; } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + const learnableLevelMoves = pokemon.getLearnableLevelMoves(); const move = learnableLevelMoves[option]; optionName = allMoves[move].name; altText = !pokemon @@ -1209,6 +1426,7 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.ALL) { optionName = i18next.t("partyUiHandler:ALL"); } else { + const itemModifiers = this.getItemModifiers(pokemon); const itemModifier = itemModifiers[option]; optionName = itemModifier.type.name; } @@ -1222,6 +1440,7 @@ export default class PartyUiHandler extends MessageUiHandler { optionText.setOrigin(0, 0); /** For every item that has stack bigger than 1, display the current quantity selection */ + const itemModifiers = this.getItemModifiers(pokemon); const itemModifier = itemModifiers[option]; if ( this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 18b5d2384ef..d8012a58875 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -8,7 +8,7 @@ import type Pokemon from "../field/pokemon"; import i18next from "i18next"; import type { DexEntry, StarterDataEntry } from "../system/game-data"; import { DexAttr } from "../system/game-data"; -import { fixedInt } from "#app/utils/common"; +import { fixedInt, getShinyDescriptor } from "#app/utils/common"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; @@ -343,18 +343,19 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.setVisible(pokemon.isShiny()); this.pokemonShinyIcon.setTint(getVariantTint(baseVariant)); if (this.pokemonShinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.pokemonShinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.pokemonShinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0)); const newVariant = BigInt(1 << (pokemon.variant + 4)); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 80acac6a6b4..a971ba86479 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -108,17 +108,21 @@ const languageSettings: { [key: string]: LanguageSetting } = { instructionTextSize: "38px", }, de: { - starterInfoTextSize: "48px", + starterInfoTextSize: "54px", instructionTextSize: "35px", - starterInfoXPos: 33, + starterInfoXPos: 35, }, "es-ES": { - starterInfoTextSize: "52px", - instructionTextSize: "35px", + starterInfoTextSize: "50px", + instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 38, }, "es-MX": { - starterInfoTextSize: "52px", - instructionTextSize: "35px", + starterInfoTextSize: "50px", + instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 38, }, fr: { starterInfoTextSize: "54px", @@ -128,21 +132,16 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoTextSize: "56px", instructionTextSize: "38px", }, - pt_BR: { - starterInfoTextSize: "47px", - instructionTextSize: "38px", + "pt-BR": { + starterInfoTextSize: "48px", + instructionTextSize: "42px", + starterInfoYOffset: 0.5, starterInfoXPos: 33, }, zh: { - starterInfoTextSize: "47px", - instructionTextSize: "38px", - starterInfoYOffset: 1, - starterInfoXPos: 24, - }, - pt: { - starterInfoTextSize: "48px", - instructionTextSize: "42px", - starterInfoXPos: 33, + starterInfoTextSize: "56px", + instructionTextSize: "36px", + starterInfoXPos: 26, }, ko: { starterInfoTextSize: "60px", @@ -156,9 +155,11 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 33, }, - "ca-ES": { - starterInfoTextSize: "52px", + ca: { + starterInfoTextSize: "48px", instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 29, }, }; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index f93a1826b3e..24e790f7c61 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -11,6 +11,7 @@ import { isNullOrUndefined, toReadableString, formatStat, + getShinyDescriptor, } from "#app/utils/common"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; @@ -444,18 +445,19 @@ export default class SummaryUiHandler extends UiHandler { this.shinyIcon.setVisible(this.pokemon.isShiny(false)); this.shinyIcon.setTint(getVariantTint(baseVariant)); if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.shinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); } this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); diff --git a/src/utils/common.ts b/src/utils/common.ts index b9111578e2f..a018b49da3c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -2,6 +2,7 @@ import { MoneyFormat } from "#enums/money-format"; import { Moves } from "#enums/moves"; import i18next from "i18next"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import type { Variant } from "#app/sprites/variant"; export type nil = null | undefined; @@ -576,3 +577,18 @@ export function animationFileName(move: Moves): string { export function camelCaseToKebabCase(str: string): string { return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); } + +/** Get the localized shiny descriptor for the provided variant + * @param variant - The variant to get the shiny descriptor for + * @returns The localized shiny descriptor + */ +export function getShinyDescriptor(variant: Variant): string { + switch (variant) { + case 2: + return i18next.t("common:epicShiny"); + case 1: + return i18next.t("common:rareShiny"); + case 0: + return i18next.t("common:commonShiny"); + } +} diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 8247d29c0a0..333619d16db 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -25,7 +25,7 @@ describe("Moves - Gastro Acid", () => { game.override.battleStyle("double"); game.override.startingLevel(1); game.override.enemyLevel(100); - game.override.ability(Abilities.NONE); + game.override.ability(Abilities.BALL_FETCH); game.override.moveset([Moves.GASTRO_ACID, Moves.WATER_GUN, Moves.SPLASH, Moves.CORE_ENFORCER]); game.override.enemySpecies(Species.BIDOOF); game.override.enemyMoveset(Moves.SPLASH); @@ -40,7 +40,7 @@ describe("Moves - Gastro Acid", () => { * - player mon 1 should have dealt damage, player mon 2 should have not */ - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.GASTRO_ACID, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); @@ -63,7 +63,7 @@ describe("Moves - Gastro Acid", () => { it("fails if used on an enemy with an already-suppressed ability", async () => { game.override.battleStyle("single"); - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.CORE_ENFORCER); // Force player to be slower to enable Core Enforcer to proc its suppression effect @@ -77,4 +77,27 @@ describe("Moves - Gastro Acid", () => { expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); + + it("should suppress the passive of a target even if its main ability is unsuppressable and not suppress main abli", async () => { + game.override + .enemyAbility(Abilities.COMATOSE) + .enemyPassiveAbility(Abilities.WATER_ABSORB) + .moveset([Moves.SPLASH, Moves.GASTRO_ACID, Moves.WATER_GUN]); + await game.classicMode.startBattle([Species.MAGIKARP]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + + game.move.select(Moves.GASTRO_ACID); + await game.toNextTurn(); + expect(enemyPokemon?.summonData.abilitySuppressed).toBe(true); + + game.move.select(Moves.WATER_GUN); + await game.toNextTurn(); + expect(enemyPokemon?.getHpRatio()).toBeLessThan(1); + + game.move.select(Moves.SPORE); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.status?.effect).toBeFalsy(); + }); }); diff --git a/test/testUtils/mocks/mockGameObject.ts b/test/testUtils/mocks/mockGameObject.ts index 4c243ec9ca1..1e156b99d21 100644 --- a/test/testUtils/mocks/mockGameObject.ts +++ b/test/testUtils/mocks/mockGameObject.ts @@ -1,3 +1,7 @@ export interface MockGameObject { name: string; + active: boolean; + destroy?(): void; + setActive(active: boolean): this; + setName(name: string): this; } diff --git a/test/testUtils/mocks/mockVideoGameObject.ts b/test/testUtils/mocks/mockVideoGameObject.ts index 65a5c37b244..1789229b1c7 100644 --- a/test/testUtils/mocks/mockVideoGameObject.ts +++ b/test/testUtils/mocks/mockVideoGameObject.ts @@ -3,11 +3,20 @@ import type { MockGameObject } from "./mockGameObject"; /** Mocks video-related stuff */ export class MockVideoGameObject implements MockGameObject { public name: string; + public active = true; public play = () => null; public stop = () => this; - public setOrigin = () => null; - public setScale = () => null; - public setVisible = () => null; - public setLoop = () => null; + public setOrigin = () => this; + public setScale = () => this; + public setVisible = () => this; + public setLoop = () => this; + public setName = (name: string) => { + this.name = name; + return this; + }; + public setActive = (active: boolean) => { + this.active = active; + return this; + }; } diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index 5e739fbe3cc..f1371643ce3 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -2,199 +2,257 @@ import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager"; import type { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { - protected x; - protected y; + protected x: number; + protected y: number; protected scene; - protected width; - protected height; - protected visible; - private alpha; + protected width: number; + protected height: number; + protected visible: boolean; + private alpha: number; private style; public frame; protected textureManager; public list: MockGameObject[] = []; public name: string; + public active = true; - constructor(textureManager: MockTextureManager, x, y) { + constructor(textureManager: MockTextureManager, x: number, y: number) { this.x = x; this.y = y; this.frame = {}; this.textureManager = textureManager; } - setVisible(visible) { + setVisible(visible: boolean): this { this.visible = visible; + return this; } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } - removeBetween(_startIndex, _endIndex, _destroyChild) { + removeBetween(_startIndex, _endIndex, _destroyChild): this { // Removes multiple children across an index range + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setSize(_width, _height) { + setSize(_width: number, _height: number): this { // Sets the size of this Game Object. + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setOrigin(x, y) { + setOrigin(x = 0.5, y = x): this { this.x = x; this.y = y; + return this; } - setAlpha(alpha) { + setAlpha(alpha = 1): this { this.alpha = alpha; + return this; } - setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. + return this; } - setScale(_scale) { + setScale(_x = 1, _y = _x): this { // Sets the scale of this Game Object. + return this; } - setPosition(x, y) { + setPosition(x = 0, y = x, _z = 0, _w = 0): this { this.x = x; this.y = y; + return this; } - setX(x) { + setX(x = 0): this { this.x = x; + return this; } - setY(y) { + setY(y = 0): this { this.y = y; + return this; } destroy() { this.list = []; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. + return this; } - setText(_text) { + setText(_text): this { // Sets the text this Game Object will display. + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. + return this; } setWordWrapWidth(_width) { // Sets the width (in pixels) to use for wrapping lines. } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. + return this; } getBounds() { return { width: this.width, height: this.height }; } - setColor(_color) { + setColor(_color): this { // Sets the tint of this Game Object. + return this; } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. + return this; } - setTint(_color) { + setTint(_color: this) { // Sets the tint of this Game Object. + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. return this; } - setDepth(_depth) { - // Sets the depth of this Game Object. + setDepth(_depth): this { + // Sets the depth of this Game Object.\ + return this; } - setTexture(_texture) { - // Sets the texture this Game Object will use to render with. + setTexture(_texture): this { + // Sets the texture this Game Object will use to render with.\ + return this; } - clearTint() { - // Clears any previously set tint. + clearTint(): this { + // Clears any previously set tint.\ + return this; } - sendToBack() { - // Sends this Game Object to the back of its parent's display list. + sendToBack(): this { + // Sends this Game Object to the back of its parent's display list.\ + return this; } - moveTo(_obj) { - // Moves this Game Object to the given index in the list. + moveTo(_obj): this { + // Moves this Game Object to the given index in the list.\ + return this; } - moveAbove(_obj) { + moveAbove(_obj): this { // Moves this Game Object to be above the given Game Object in the display list. + return this; } - moveBelow(_obj) { + moveBelow(_obj): this { // Moves this Game Object to be below the given Game Object in the display list. + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - bringToTop(_obj) { + bringToTop(_obj): this { // Brings this Game Object to the top of its parents display list. + return this; } - on(_event, _callback, _source) {} - - add(obj) { - // Adds a child to this Game Object. - this.list.push(obj); + on(_event, _callback, _source): this { + return this; } - removeAll() { + add(obj: MockGameObject | MockGameObject[]): this { + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } + return this; + } + + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj: MockGameObject | MockGameObject[], index = 0): this { // Adds a Game Object to this Container at the given index. - this.list.splice(index, 0, obj); + if (!Array.isArray(obj)) { + obj = [obj]; + } + this.list.splice(index, 0, ...obj); + return this; } - remove(obj) { - const index = this.list.indexOf(obj); - if (index !== -1) { - this.list.splice(index, 1); + remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this { + if (!Array.isArray(obj)) { + obj = [obj]; } + for (const item of obj) { + const index = this.list.indexOf(item); + if (index !== -1) { + this.list.splice(index, 1); + } + if (destroyChild) { + item.destroy?.(); + } + } + return this; } getIndex(obj) { @@ -210,15 +268,48 @@ export default class MockContainer implements MockGameObject { return this.list; } - getByName(key: string) { + getByName(key: string): MockGameObject | null { return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0); } - disableInteractive = () => null; + disableInteractive(): this { + return this; + } - each(method) { - for (const item of this.list) { - method(item); + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + each(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); } + for (const item of this.list.slice()) { + callback(item, ...args); + } + return this; + } + + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + iterate(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); + } + for (const item of this.list) { + callback(item, ...args); + } + return this; + } + + copyPosition(source: { x?: number; y?: number }): this { + if (source.x !== undefined) { + this.x = source.x; + } + if (source.y !== undefined) { + this.y = source.y; + } + return this; + } + + setActive(active: boolean): this { + this.active = active; + return this; } } diff --git a/test/testUtils/mocks/mocksContainer/mockGraphics.ts b/test/testUtils/mocks/mocksContainer/mockGraphics.ts index ebf84e935e3..cd43bb3a877 100644 --- a/test/testUtils/mocks/mocksContainer/mockGraphics.ts +++ b/test/testUtils/mocks/mocksContainer/mockGraphics.ts @@ -4,61 +4,81 @@ export default class MockGraphics implements MockGameObject { private scene; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, _config) { this.scene = textureManager.scene; } - fillStyle(_color) { + fillStyle(_color): this { // Sets the fill style to be used by the fill methods. + return this; } - beginPath() { + beginPath(): this { // Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path. + return this; } - fillRect(_x, _y, _width, _height) { + fillRect(_x, _y, _width, _height): this { // Adds a rectangle shape to the path which is filled when you call fill(). + return this; } - createGeometryMask() { + createGeometryMask(): this { // Creates a geometry mask. + return this; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} + setAlpha(_alpha): this { + return this; + } - setVisible(_visible) {} + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name) { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source) { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - setScale(_scale) { - // Sets the scale of this Game Object. + setScale(_scale): this { + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -90,4 +110,13 @@ export default class MockGraphics implements MockGameObject { getAll() { return this.list; } + + copyPosition(_source): this { + return this; + } + + setActive(active: boolean): this { + this.active = active; + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index 7bdf343759d..7f54a0e255f 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -5,39 +5,57 @@ export default class MockRectangle implements MockGameObject { private scene; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, _x, _y, _width, _height, fillColor) { this.fillColor = fillColor; this.scene = textureManager.scene; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} - setVisible(_visible) {} + setAlpha(_alpha): this { + return this; + } + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name): this { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - add(obj) { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } + return this; } removeAll() { @@ -45,16 +63,18 @@ export default class MockRectangle implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -69,9 +89,17 @@ export default class MockRectangle implements MockGameObject { getAll() { return this.list; } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - off() {} + off(): this { + return this; + } + + setActive(active: boolean): this { + this.active = active; + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index dcc3588f127..df36b3a29fd 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; import type { MockGameObject } from "../mockGameObject"; -import Sprite = Phaser.GameObjects.Sprite; import Frame = Phaser.Textures.Frame; export default class MockSprite implements MockGameObject { @@ -14,6 +13,7 @@ export default class MockSprite implements MockGameObject { public anims; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, x, y, texture) { this.textureManager = textureManager; this.scene = textureManager.scene; @@ -21,7 +21,9 @@ export default class MockSprite implements MockGameObject { Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive; // @ts-ignore Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame; // Phaser.GameObjects.Sprite.prototype.disable = this.disable; @@ -37,46 +39,55 @@ export default class MockSprite implements MockGameObject { }; } - setTexture(_key: string, _frame?: string | number) { + setTexture(_key: string, _frame?: string | number): this { return this; } - setSizeToFrame(_frame?: boolean | Frame): Sprite { - return {} as Sprite; + setSizeToFrame(_frame?: boolean | Frame): this { + return this; } - setPipeline(obj) { + setPipeline(obj): this { // Sets the pipeline of this Game Object. - return this.phaserSprite.setPipeline(obj); + this.phaserSprite.setPipeline(obj); + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - setTintFill(color) { + setTintFill(color): this { // Sets the tint fill color. - return this.phaserSprite.setTintFill(color); + this.phaserSprite.setTintFill(color); + return this; } - setScale(scale) { - return this.phaserSprite.setScale(scale); + setScale(scale = 1): this { + this.phaserSprite.setScale(scale); + return this; } - setOrigin(x, y) { - return this.phaserSprite.setOrigin(x, y); + setOrigin(x = 0.5, y = x): this { + this.phaserSprite.setOrigin(x, y); + return this; } - setSize(width, height) { + setSize(width, height): this { // Sets the size of this Game Object. - return this.phaserSprite.setSize(width, height); + this.phaserSprite.setSize(width, height); + return this; } - once(event, callback, source) { - return this.phaserSprite.once(event, callback, source); + once(event, callback, source): this { + this.phaserSprite.once(event, callback, source); + return this; } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy - return this.phaserSprite.removeFromDisplayList(); + this.phaserSprite.removeFromDisplayList(); + return this; } addedToScene() { @@ -84,97 +95,121 @@ export default class MockSprite implements MockGameObject { return this.phaserSprite.addedToScene(); } - setVisible(visible) { - return this.phaserSprite.setVisible(visible); + setVisible(visible): this { + this.phaserSprite.setVisible(visible); + return this; } - setPosition(x, y) { - return this.phaserSprite.setPosition(x, y); + setPosition(x, y): this { + this.phaserSprite.setPosition(x, y); + return this; } - setRotation(radians) { - return this.phaserSprite.setRotation(radians); + setRotation(radians): this { + this.phaserSprite.setRotation(radians); + return this; } - stop() { - return this.phaserSprite.stop(); + stop(): this { + this.phaserSprite.stop(); + return this; } - setInteractive = () => null; - - on(event, callback, source) { - return this.phaserSprite.on(event, callback, source); + setInteractive(): this { + return this; } - setAlpha(alpha) { - return this.phaserSprite.setAlpha(alpha); + on(event, callback, source): this { + this.phaserSprite.on(event, callback, source); + return this; } - setTint(color) { + setAlpha(alpha): this { + this.phaserSprite.setAlpha(alpha); + return this; + } + + setTint(color): this { // Sets the tint of this Game Object. - return this.phaserSprite.setTint(color); + this.phaserSprite.setTint(color); + return this; } - setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. this.frame = frame; - return frame; + return this; } - setPositionRelative(source, x, y) { + setPositionRelative(source, x, y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. - return this.phaserSprite.setPositionRelative(source, x, y); + this.phaserSprite.setPositionRelative(source, x, y); + return this; } - setY(y) { - return this.phaserSprite.setY(y); + setY(y: number): this { + this.phaserSprite.setY(y); + return this; } - setCrop(x, y, width, height) { + setCrop(x: number, y: number, width: number, height: number): this { // Sets the crop size of this Game Object. - return this.phaserSprite.setCrop(x, y, width, height); + this.phaserSprite.setCrop(x, y, width, height); + return this; } - clearTint() { + clearTint(): this { // Clears any previously set tint. - return this.phaserSprite.clearTint(); + this.phaserSprite.clearTint(); + return this; } - disableInteractive() { + disableInteractive(): this { // Disables Interactive features of this Game Object. - return null; + return this; } apply() { - return this.phaserSprite.apply(); + this.phaserSprite.apply(); + return this; } - play() { + play(): this { // return this.phaserSprite.play(); return this; } - setPipelineData(key, value) { + setPipelineData(key: string, value: any): this { this.pipelineData[key] = value; + return this; } destroy() { return this.phaserSprite.destroy(); } - setName(name) { - return this.phaserSprite.setName(name); + setName(name: string): this { + this.phaserSprite.setName(name); + return this; } - setAngle(angle) { - return this.phaserSprite.setAngle(angle); + setAngle(angle): this { + this.phaserSprite.setAngle(angle); + return this; } - setMask() {} + setMask(): this { + return this; + } - add(obj) { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } + return this; } removeAll() { @@ -182,16 +217,18 @@ export default class MockSprite implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -206,4 +243,14 @@ export default class MockSprite implements MockGameObject { getAll() { return this.list; } + + copyPosition(obj): this { + this.phaserSprite.copyPosition(obj); + return this; + } + + setActive(active: boolean): this { + this.phaserSprite.setActive(active); + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index 1f3f0ad792f..2345ff70c0d 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -12,6 +12,7 @@ export default class MockText implements MockGameObject { public text = ""; public name: string; public color?: string; + public active = true; constructor(textureManager, _x, _y, _content, _styleOptions) { this.scene = textureManager.scene; @@ -107,42 +108,51 @@ export default class MockText implements MockGameObject { } } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. // return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor); + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. // return this.phaserText.setLineSpacing(lineSpacing); + return this; } - setOrigin(_x, _y) { + setOrigin(_x, _y): this { // return this.phaserText.setOrigin(x, y); + return this; } - once(_event, _callback, _source) { + once(_event, _callback, _source): this { // return this.phaserText.once(event, callback, source); + return this; } off(_event, _callback, _obj) {} removedFromScene() {} - addToDisplayList() {} - - setStroke(_color, _thickness) { - // Sets the stroke color and thickness. - // return this.phaserText.setStroke(color, thickness); + addToDisplayList(): this { + return this; } - removeFromDisplayList() { + setStroke(_color, _thickness): this { + // Sets the stroke color and thickness. + // return this.phaserText.setStroke(color, thickness); + return this; + } + + removeFromDisplayList(): this { // same as remove or destroy // return this.phaserText.removeFromDisplayList(); + return this; } addedToScene() { @@ -150,16 +160,18 @@ export default class MockText implements MockGameObject { // return this.phaserText.addedToScene(); } - setVisible(_visible) { - // return this.phaserText.setVisible(visible); + setVisible(_visible): this { + return this; } - setY(_y) { + setY(_y): this { // return this.phaserText.setY(y); + return this; } - setX(_x) { + setX(_x): this { // return this.phaserText.setX(x); + return this; } /** @@ -169,37 +181,45 @@ export default class MockText implements MockGameObject { * @param z The z position of this Game Object. Default 0. * @param w The w position of this Game Object. Default 0. */ - setPosition(_x?: number, _y?: number, _z?: number, _w?: number) {} + setPosition(_x?: number, _y?: number, _z?: number, _w?: number): this { + return this; + } - setText(text) { + setText(text): this { // Sets the text this Game Object will display. // return this.phaserText.setText\(text); this.text = text; + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. // return this.phaserText.setAngle(angle); + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. // return this.phaserText.setPositionRelative(source, x, y); + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. // return this.phaserText.setShadowOffset(offsetX, offsetY); + return this; } - setWordWrapWidth(width) { + setWordWrapWidth(width): this { // Sets the width (in pixels) to use for wrapping lines. this.wordWrapWidth = width; + return this; } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. // return this.phaserText.setFontSize(fontSize); + return this; } getBounds() { @@ -209,25 +229,31 @@ export default class MockText implements MockGameObject { }; } - setColor(color: string) { + setColor(color: string): this { this.color = color; + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. // return this.phaserText.setShadowColor(color); + return this; } - setTint(_color) { + setTint(_color): this { // Sets the tint of this Game Object. // return this.phaserText.setTint(color); + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. // return this.phaserText.setStrokeStyle(thickness, color); + return this; } destroy() { @@ -235,20 +261,24 @@ export default class MockText implements MockGameObject { this.list = []; } - setAlpha(_alpha) { + setAlpha(_alpha): this { // return this.phaserText.setAlpha(alpha); + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - setAlign(_align) { + setAlign(_align): this { // return this.phaserText.setAlign(align); + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } getBottomLeft() { @@ -265,37 +295,43 @@ export default class MockText implements MockGameObject { }; } - disableInteractive() { + disableInteractive(): this { // Disables interaction with this Game Object. + return this; } - clearTint() { + clearTint(): this { // Clears tint on this Game Object. + return this; } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } - removeAll() { + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } - getIndex(obj) { + getIndex(obj): number { const index = this.list.indexOf(obj); return index || -1; } @@ -317,5 +353,10 @@ export default class MockText implements MockGameObject { return this.runWordWrap(this.text).split("\n"); } + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks on(_event: string | symbol, _fn: Function, _context?: any) {} + + setActive(_active: boolean): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockTexture.ts b/test/testUtils/mocks/mocksContainer/mockTexture.ts index eb8b70902fa..3b01f9ab4ea 100644 --- a/test/testUtils/mocks/mocksContainer/mockTexture.ts +++ b/test/testUtils/mocks/mocksContainer/mockTexture.ts @@ -12,6 +12,7 @@ export default class MockTexture implements MockGameObject { public frames: object; public firstFrame: string; public name: string; + public active: boolean; constructor(manager, key: string, source) { this.manager = manager; @@ -39,4 +40,14 @@ export default class MockTexture implements MockGameObject { getSourceImage() { return null; } + + setActive(active: boolean): this { + this.active = active; + return this; + } + + setName(name: string): this { + this.name = name; + return this; + } } diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index 15635289e6f..ba90cba7d5b 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -70,10 +70,10 @@ export function initTestFile() { * @param x The relative x position * @param y The relative y position */ - const setPositionRelative = function (guideObject: any, x: number, y: number) { + const setPositionRelative = function (guideObject: any, x: number, y: number): any { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;