Merge branch 'beta' into rogue-ball-balance-fix

This commit is contained in:
Opaque02 2024-10-25 09:40:52 +10:00
commit 1c019d88cd
183 changed files with 11183 additions and 8269 deletions

View File

@ -1,5 +1,5 @@
import tseslint from '@typescript-eslint/eslint-plugin'; import tseslint from '@typescript-eslint/eslint-plugin';
import stylisticTs from '@stylistic/eslint-plugin-ts' import stylisticTs from '@stylistic/eslint-plugin-ts';
import parser from '@typescript-eslint/parser'; import parser from '@typescript-eslint/parser';
import importX from 'eslint-plugin-import-x'; import importX from 'eslint-plugin-import-x';
@ -16,15 +16,15 @@ export default [
'@typescript-eslint': tseslint '@typescript-eslint': tseslint
}, },
rules: { rules: {
"eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and != "eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=`
"indent": ["error", 2], // Enforces a 2-space indentation "indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements
"quotes": ["error", "double"], // Enforces the use of double quotes for strings "quotes": ["error", "double"], // Enforces the use of double quotes for strings
"no-var": "error", // Disallows the use of var, enforcing let or const instead "no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead
"prefer-const": "error", // Prefers the use of const for variables that are never reassigned "prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this) "no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
"@typescript-eslint/no-unused-vars": [ "error", { "@typescript-eslint/no-unused-vars": [ "error", {
"args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used. "args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used.
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest. "ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others.
}], }],
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files "eol-last": ["error", "always"], // Enforces at least one newline at the end of files
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax "@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
@ -32,14 +32,14 @@ export default [
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax "no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors "brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements "curly": ["error", "all"], // Enforces the use of curly braces for all control statements
"@stylistic/ts/brace-style": ["error", "1tbs"], "@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines "no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
"skipBlankLines": false, // Enforces the rule even on blank lines "skipBlankLines": false, // Enforces the rule even on blank lines
"ignoreComments": false // Enforces the rule on lines containing comments "ignoreComments": false // Enforces the rule on lines containing comments
}], }],
"space-before-blocks": ["error", "always"], // Enforces a space before blocks "space-before-blocks": ["error", "always"], // Enforces a space before blocks
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords "keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json "import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets "array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers "object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "future_self_f.png",
"format": "RGBA8888",
"size": {
"w": 29,
"h": 69
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 69,
"h": 69
},
"spriteSourceSize": {
"x": 21,
"y": 0,
"w": 29,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 29,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4eb16332c2e77886e4e621b62269f05e:26f1bc53c853efdbe228d67604b95b54:d25525a5db42bd57d2afe4b6e3081ee1$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "future_self_m.png",
"format": "RGBA8888",
"size": {
"w": 36,
"h": 73
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 73,
"h": 73
},
"spriteSourceSize": {
"x": 18,
"y": 0,
"w": 36,
"h": 73
},
"frame": {
"x": 0,
"y": 0,
"w": 36,
"h": 73
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:0d8d68d06ae75bc93d72b54183a9df02:d3d509801da9ff5c0bd4793a05ece391:83c4b8c2ed25ea7d9795bec5c40c8602$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

@ -1 +1 @@
Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef Subproject commit 87615556d8a2bd7eef7abac818f84423a8a13b03

View File

@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph
import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters";
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -1191,10 +1191,7 @@ export default class BattleScene extends SceneBase {
if (trainerConfigs[trainerType].doubleOnly) { if (trainerConfigs[trainerType].doubleOnly) {
doubleTrainer = true; doubleTrainer = true;
} else if (trainerConfigs[trainerType].hasDouble) { } else if (trainerConfigs[trainerType].hasDouble) {
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
doubleTrainer = !Utils.randSeedInt(doubleChance.value);
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) {
doubleTrainer = false; doubleTrainer = false;
@ -1207,12 +1204,10 @@ export default class BattleScene extends SceneBase {
// Check for mystery encounter // Check for mystery encounter
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180 // Can only occur in place of a standard (non-boss) wild battle, waves 10-180
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) {
newBattleType = BattleType.MYSTERY_ENCOUNTER; newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight // Reset to base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else if (newBattleType === BattleType.WILD) {
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
} }
} }
@ -1359,69 +1354,69 @@ export default class BattleScene extends SceneBase {
} }
switch (species.speciesId) { switch (species.speciesId) {
case Species.UNOWN: case Species.UNOWN:
case Species.SHELLOS: case Species.SHELLOS:
case Species.GASTRODON: case Species.GASTRODON:
case Species.BASCULIN: case Species.BASCULIN:
case Species.DEERLING: case Species.DEERLING:
case Species.SAWSBUCK: case Species.SAWSBUCK:
case Species.FROAKIE: case Species.FROAKIE:
case Species.FROGADIER: case Species.FROGADIER:
case Species.SCATTERBUG: case Species.SCATTERBUG:
case Species.SPEWPA: case Species.SPEWPA:
case Species.VIVILLON: case Species.VIVILLON:
case Species.FLABEBE: case Species.FLABEBE:
case Species.FLOETTE: case Species.FLOETTE:
case Species.FLORGES: case Species.FLORGES:
case Species.FURFROU: case Species.FURFROU:
case Species.PUMPKABOO: case Species.PUMPKABOO:
case Species.GOURGEIST: case Species.GOURGEIST:
case Species.ORICORIO: case Species.ORICORIO:
case Species.MAGEARNA: case Species.MAGEARNA:
case Species.ZARUDE: case Species.ZARUDE:
case Species.SQUAWKABILLY: case Species.SQUAWKABILLY:
case Species.TATSUGIRI: case Species.TATSUGIRI:
case Species.PALDEA_TAUROS: case Species.PALDEA_TAUROS:
return Utils.randSeedInt(species.forms.length);
case Species.PIKACHU:
return Utils.randSeedInt(8);
case Species.EEVEE:
return Utils.randSeedInt(2);
case Species.GRENINJA:
return Utils.randSeedInt(2);
case Species.ZYGARDE:
return Utils.randSeedInt(4);
case Species.MINIOR:
return Utils.randSeedInt(6);
case Species.ALCREMIE:
return Utils.randSeedInt(9);
case Species.MEOWSTIC:
case Species.INDEEDEE:
case Species.BASCULEGION:
case Species.OINKOLOGNE:
return gender === Gender.FEMALE ? 1 : 0;
case Species.TOXTRICITY:
const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ];
if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) {
return 1;
}
return 0;
case Species.GIMMIGHOUL:
// Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs
if (this.gameMode.hasMysteryEncounters) {
return 1; // Wandering form
} else {
return Utils.randSeedInt(species.forms.length); return Utils.randSeedInt(species.forms.length);
} case Species.PIKACHU:
return Utils.randSeedInt(8);
case Species.EEVEE:
return Utils.randSeedInt(2);
case Species.GRENINJA:
return Utils.randSeedInt(2);
case Species.ZYGARDE:
return Utils.randSeedInt(4);
case Species.MINIOR:
return Utils.randSeedInt(7);
case Species.ALCREMIE:
return Utils.randSeedInt(9);
case Species.MEOWSTIC:
case Species.INDEEDEE:
case Species.BASCULEGION:
case Species.OINKOLOGNE:
return gender === Gender.FEMALE ? 1 : 0;
case Species.TOXTRICITY:
const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ];
if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) {
return 1;
}
return 0;
case Species.GIMMIGHOUL:
// Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs
if (this.gameMode.hasMysteryEncounters) {
return 1; // Wandering form
} else {
return Utils.randSeedInt(species.forms.length);
}
} }
if (ignoreArena) { if (ignoreArena) {
switch (species.speciesId) { switch (species.speciesId) {
case Species.BURMY: case Species.BURMY:
case Species.WORMADAM: case Species.WORMADAM:
case Species.ROTOM: case Species.ROTOM:
case Species.LYCANROC: case Species.LYCANROC:
return Utils.randSeedInt(species.forms.length); return Utils.randSeedInt(species.forms.length);
} }
return 0; return 0;
} }
@ -1886,17 +1881,17 @@ export default class BattleScene extends SceneBase {
const soundDetails = sound.key.split("/"); const soundDetails = sound.key.split("/");
switch (soundDetails[0]) { switch (soundDetails[0]) {
case "battle_anims": case "battle_anims":
case "cry": case "cry":
if (soundDetails[1].startsWith("PRSFX- ")) { if (soundDetails[1].startsWith("PRSFX- ")) {
sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); sound.setVolume(this.masterVolume * this.fieldVolume * 0.5);
} else { } else {
sound.setVolume(this.masterVolume * this.fieldVolume); sound.setVolume(this.masterVolume * this.fieldVolume);
} }
break; break;
case "se": case "se":
case "ui": case "ui":
sound.setVolume(this.masterVolume * this.seVolume); sound.setVolume(this.masterVolume * this.seVolume);
} }
} }
} }
@ -1936,31 +1931,31 @@ export default class BattleScene extends SceneBase {
const keyDetails = key.split("/"); const keyDetails = key.split("/");
config["volume"] = config["volume"] ?? 1; config["volume"] = config["volume"] ?? 1;
switch (keyDetails[0]) { switch (keyDetails[0]) {
case "level_up_fanfare": case "level_up_fanfare":
case "item_fanfare": case "item_fanfare":
case "minor_fanfare": case "minor_fanfare":
case "heal": case "heal":
case "evolution": case "evolution":
case "evolution_fanfare": case "evolution_fanfare":
// These sounds are loaded in as BGM, but played as sound effects // These sounds are loaded in as BGM, but played as sound effects
// When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM()
config["volume"] *= (this.masterVolume * this.bgmVolume); config["volume"] *= (this.masterVolume * this.bgmVolume);
break; break;
case "battle_anims": case "battle_anims":
case "cry": case "cry":
config["volume"] *= (this.masterVolume * this.fieldVolume); config["volume"] *= (this.masterVolume * this.fieldVolume);
//PRSFX sound files are unusually loud //PRSFX sound files are unusually loud
if (keyDetails[1].startsWith("PRSFX- ")) { if (keyDetails[1].startsWith("PRSFX- ")) {
config["volume"] *= 0.5; config["volume"] *= 0.5;
} }
break; break;
case "ui": case "ui":
//As of, right now this applies to the "select", "menu_open", "error" sound effects //As of, right now this applies to the "select", "menu_open", "error" sound effects
config["volume"] *= (this.masterVolume * this.uiVolume); config["volume"] *= (this.masterVolume * this.uiVolume);
break; break;
case "se": case "se":
config["volume"] *= (this.masterVolume * this.seVolume); config["volume"] *= (this.masterVolume * this.seVolume);
break; break;
} }
this.sound.play(key, config); this.sound.play(key, config);
return this.sound.get(key) as AnySound; return this.sound.get(key) as AnySound;
@ -1989,208 +1984,208 @@ export default class BattleScene extends SceneBase {
getBgmLoopPoint(bgmName: string): number { getBgmLoopPoint(bgmName: string): number {
switch (bgmName) { switch (bgmName) {
case "battle_kanto_champion": //B2W2 Kanto Champion Battle case "battle_kanto_champion": //B2W2 Kanto Champion Battle
return 13.950; return 13.950;
case "battle_johto_champion": //B2W2 Johto Champion Battle case "battle_johto_champion": //B2W2 Johto Champion Battle
return 23.498; return 23.498;
case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle
return 11.328; return 11.328;
case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle
return 11.762; return 11.762;
case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle
return 12.235; return 12.235;
case "battle_champion_alder": //BW Unova Champion Battle case "battle_champion_alder": //BW Unova Champion Battle
return 27.653; return 27.653;
case "battle_champion_iris": //B2W2 Unova Champion Battle case "battle_champion_iris": //B2W2 Unova Champion Battle
return 10.145; return 10.145;
case "battle_kalos_champion": //XY Kalos Champion Battle case "battle_kalos_champion": //XY Kalos Champion Battle
return 10.380; return 10.380;
case "battle_alola_champion": //USUM Alola Champion Battle case "battle_alola_champion": //USUM Alola Champion Battle
return 13.025; return 13.025;
case "battle_galar_champion": //SWSH Galar Champion Battle case "battle_galar_champion": //SWSH Galar Champion Battle
return 61.635; return 61.635;
case "battle_champion_geeta": //SV Champion Geeta Battle case "battle_champion_geeta": //SV Champion Geeta Battle
return 37.447; return 37.447;
case "battle_champion_nemona": //SV Champion Nemona Battle case "battle_champion_nemona": //SV Champion Nemona Battle
return 14.914; return 14.914;
case "battle_champion_kieran": //SV Champion Kieran Battle case "battle_champion_kieran": //SV Champion Kieran Battle
return 7.206; return 7.206;
case "battle_hoenn_elite": //ORAS Elite Four Battle case "battle_hoenn_elite": //ORAS Elite Four Battle
return 11.350; return 11.350;
case "battle_unova_elite": //BW Elite Four Battle case "battle_unova_elite": //BW Elite Four Battle
return 17.730; return 17.730;
case "battle_kalos_elite": //XY Elite Four Battle case "battle_kalos_elite": //XY Elite Four Battle
return 12.340; return 12.340;
case "battle_alola_elite": //SM Elite Four Battle case "battle_alola_elite": //SM Elite Four Battle
return 19.212; return 19.212;
case "battle_galar_elite": //SWSH League Tournament Battle case "battle_galar_elite": //SWSH League Tournament Battle
return 164.069; return 164.069;
case "battle_paldea_elite": //SV Elite Four Battle case "battle_paldea_elite": //SV Elite Four Battle
return 12.770; return 12.770;
case "battle_bb_elite": //SV BB League Elite Four Battle case "battle_bb_elite": //SV BB League Elite Four Battle
return 19.434; return 19.434;
case "battle_final_encounter": //PMD RTDX Rayquaza's Domain case "battle_final_encounter": //PMD RTDX Rayquaza's Domain
return 19.159; return 19.159;
case "battle_final": //BW Ghetsis Battle case "battle_final": //BW Ghetsis Battle
return 16.453; return 16.453;
case "battle_kanto_gym": //B2W2 Kanto Gym Battle case "battle_kanto_gym": //B2W2 Kanto Gym Battle
return 13.857; return 13.857;
case "battle_johto_gym": //B2W2 Johto Gym Battle case "battle_johto_gym": //B2W2 Johto Gym Battle
return 12.911; return 12.911;
case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle
return 12.379; return 12.379;
case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle
return 13.122; return 13.122;
case "battle_unova_gym": //BW Unova Gym Battle case "battle_unova_gym": //BW Unova Gym Battle
return 19.145; return 19.145;
case "battle_kalos_gym": //XY Kalos Gym Battle case "battle_kalos_gym": //XY Kalos Gym Battle
return 44.810; return 44.810;
case "battle_galar_gym": //SWSH Galar Gym Battle case "battle_galar_gym": //SWSH Galar Gym Battle
return 171.262; return 171.262;
case "battle_paldea_gym": //SV Paldea Gym Battle case "battle_paldea_gym": //SV Paldea Gym Battle
return 127.489; return 127.489;
case "battle_legendary_kanto": //XY Kanto Legendary Battle case "battle_legendary_kanto": //XY Kanto Legendary Battle
return 32.966; return 32.966;
case "battle_legendary_raikou": //HGSS Raikou Battle case "battle_legendary_raikou": //HGSS Raikou Battle
return 12.632; return 12.632;
case "battle_legendary_entei": //HGSS Entei Battle case "battle_legendary_entei": //HGSS Entei Battle
return 2.905; return 2.905;
case "battle_legendary_suicune": //HGSS Suicune Battle case "battle_legendary_suicune": //HGSS Suicune Battle
return 12.636; return 12.636;
case "battle_legendary_lugia": //HGSS Lugia Battle case "battle_legendary_lugia": //HGSS Lugia Battle
return 19.770; return 19.770;
case "battle_legendary_ho_oh": //HGSS Ho-oh Battle case "battle_legendary_ho_oh": //HGSS Ho-oh Battle
return 17.668; return 17.668;
case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle
return 49.500; return 49.500;
case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle
return 21.130; return 21.130;
case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle
return 10.547; return 10.547;
case "battle_legendary_rayquaza": //ORAS Rayquaza Battle case "battle_legendary_rayquaza": //ORAS Rayquaza Battle
return 10.495; return 10.495;
case "battle_legendary_deoxys": //ORAS Deoxys Battle case "battle_legendary_deoxys": //ORAS Deoxys Battle
return 13.333; return 13.333;
case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle
return 16.887; return 16.887;
case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle
return 22.770; return 22.770;
case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle
return 16.009; return 16.009;
case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle
return 18.961; return 18.961;
case "battle_legendary_giratina": //ORAS Giratina Battle case "battle_legendary_giratina": //ORAS Giratina Battle
return 10.451; return 10.451;
case "battle_legendary_arceus": //HGSS Arceus Battle case "battle_legendary_arceus": //HGSS Arceus Battle
return 9.595; return 9.595;
case "battle_legendary_unova": //BW Unova Legendary Battle case "battle_legendary_unova": //BW Unova Legendary Battle
return 13.855; return 13.855;
case "battle_legendary_kyurem": //BW Kyurem Battle case "battle_legendary_kyurem": //BW Kyurem Battle
return 18.314; return 18.314;
case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle
return 18.329; return 18.329;
case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle
return 26.468; return 26.468;
case "battle_legendary_tapu": //SM Tapu Battle case "battle_legendary_tapu": //SM Tapu Battle
return 0.000; return 0.000;
case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle
return 6.525; return 6.525;
case "battle_legendary_ub": //SM Ultra Beast Battle case "battle_legendary_ub": //SM Ultra Beast Battle
return 9.818; return 9.818;
case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle
return 5.211; return 5.211;
case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle
return 10.344; return 10.344;
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
return 11.424; return 11.424;
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
return 12.503; return 12.503;
case "battle_legendary_calyrex": //SWSH Calyrex Battle case "battle_legendary_calyrex": //SWSH Calyrex Battle
return 50.641; return 50.641;
case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle
return 18.155; return 18.155;
case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle
return 0.175; return 0.175;
case "battle_legendary_ruinous": //SV Treasures of Ruin Battle case "battle_legendary_ruinous": //SV Treasures of Ruin Battle
return 6.333; return 6.333;
case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle
return 6.442; return 6.442;
case "battle_legendary_loyal_three": //SV Loyal Three Battle case "battle_legendary_loyal_three": //SV Loyal Three Battle
return 6.500; return 6.500;
case "battle_legendary_ogerpon": //SV Ogerpon Battle case "battle_legendary_ogerpon": //SV Ogerpon Battle
return 14.335; return 14.335;
case "battle_legendary_terapagos": //SV Terapagos Battle case "battle_legendary_terapagos": //SV Terapagos Battle
return 24.377; return 24.377;
case "battle_legendary_pecharunt": //SV Pecharunt Battle case "battle_legendary_pecharunt": //SV Pecharunt Battle
return 6.508; return 6.508;
case "battle_rival": //BW Rival Battle case "battle_rival": //BW Rival Battle
return 14.110; return 14.110;
case "battle_rival_2": //BW N Battle case "battle_rival_2": //BW N Battle
return 17.714; return 17.714;
case "battle_rival_3": //BW Final N Battle case "battle_rival_3": //BW Final N Battle
return 17.586; return 17.586;
case "battle_trainer": //BW Trainer Battle case "battle_trainer": //BW Trainer Battle
return 13.686; return 13.686;
case "battle_wild": //BW Wild Battle case "battle_wild": //BW Wild Battle
return 12.703; return 12.703;
case "battle_wild_strong": //BW Strong Wild Battle case "battle_wild_strong": //BW Strong Wild Battle
return 13.940; return 13.940;
case "end_summit": //PMD RTDX Sky Tower Summit case "end_summit": //PMD RTDX Sky Tower Summit
return 30.025; return 30.025;
case "battle_rocket_grunt": //HGSS Team Rocket Battle case "battle_rocket_grunt": //HGSS Team Rocket Battle
return 12.707; return 12.707;
case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle
return 12.062; return 12.062;
case "battle_galactic_grunt": //BDSP Team Galactic Battle case "battle_galactic_grunt": //BDSP Team Galactic Battle
return 13.043; return 13.043;
case "battle_plasma_grunt": //BW Team Plasma Battle case "battle_plasma_grunt": //BW Team Plasma Battle
return 12.974; return 12.974;
case "battle_flare_grunt": //XY Team Flare Battle case "battle_flare_grunt": //XY Team Flare Battle
return 4.228; return 4.228;
case "battle_aether_grunt": // SM Aether Foundation Battle case "battle_aether_grunt": // SM Aether Foundation Battle
return 16.00; return 16.00;
case "battle_skull_grunt": // SM Team Skull Battle case "battle_skull_grunt": // SM Team Skull Battle
return 20.87; return 20.87;
case "battle_macro_grunt": // SWSH Trainer Battle case "battle_macro_grunt": // SWSH Trainer Battle
return 11.56; return 11.56;
case "battle_star_grunt": //SV Team Star Battle case "battle_star_grunt": //SV Team Star Battle
return 133.362; return 133.362;
case "battle_galactic_admin": //BDSP Team Galactic Admin Battle case "battle_galactic_admin": //BDSP Team Galactic Admin Battle
return 11.997; return 11.997;
case "battle_skull_admin": //SM Team Skull Admin Battle case "battle_skull_admin": //SM Team Skull Admin Battle
return 15.463; return 15.463;
case "battle_oleana": //SWSH Oleana Battle case "battle_oleana": //SWSH Oleana Battle
return 14.110; return 14.110;
case "battle_star_admin": //SV Team Star Boss Battle case "battle_star_admin": //SV Team Star Boss Battle
return 9.493; return 9.493;
case "battle_rocket_boss": //USUM Giovanni Battle case "battle_rocket_boss": //USUM Giovanni Battle
return 9.115; return 9.115;
case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle
return 14.847; return 14.847;
case "battle_galactic_boss": //BDSP Cyrus Battle case "battle_galactic_boss": //BDSP Cyrus Battle
return 106.962; return 106.962;
case "battle_plasma_boss": //B2W2 Ghetsis Battle case "battle_plasma_boss": //B2W2 Ghetsis Battle
return 25.624; return 25.624;
case "battle_flare_boss": //XY Lysandre Battle case "battle_flare_boss": //XY Lysandre Battle
return 8.085; return 8.085;
case "battle_aether_boss": //SM Lusamine Battle case "battle_aether_boss": //SM Lusamine Battle
return 11.33; return 11.33;
case "battle_skull_boss": //SM Guzma Battle case "battle_skull_boss": //SM Guzma Battle
return 13.13; return 13.13;
case "battle_macro_boss": //SWSH Rose Battle case "battle_macro_boss": //SWSH Rose Battle
return 11.42; return 11.42;
case "battle_star_boss": //SV Cassiopeia Battle case "battle_star_boss": //SV Cassiopeia Battle
return 25.764; return 25.764;
case "mystery_encounter_gen_5_gts": // BW GTS case "mystery_encounter_gen_5_gts": // BW GTS
return 8.52; return 8.52;
case "mystery_encounter_gen_6_gts": // XY GTS case "mystery_encounter_gen_6_gts": // XY GTS
return 9.24; return 9.24;
case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff
return 4.78; return 4.78;
case "mystery_encounter_weird_dream": // EoS Temporal Spire case "mystery_encounter_weird_dream": // EoS Temporal Spire
return 41.42; return 41.42;
case "mystery_encounter_delibirdy": // Firel Delibirdy case "mystery_encounter_delibirdy": // Firel Delibirdy
return 82.28; return 82.28;
} }
return 0; return 0;
@ -2364,6 +2359,19 @@ export default class BattleScene extends SceneBase {
return false; return false;
} }
/**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
* @param phaseFilter filter function
*/
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueuePrepend.splice(phaseIndex, 1);
return true;
}
return false;
}
/** /**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() * Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase {@linkcode Phase} the phase to be added * @param phase {@linkcode Phase} the phase to be added
@ -3125,18 +3133,26 @@ export default class BattleScene extends SceneBase {
} }
} }
/**
* Returns if a wave COULD spawn a {@linkcode MysteryEncounter}.
* Even if returns `true`, does not guarantee that a wave will actually be a ME.
* That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead.
*/
isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean {
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave;
}
/** /**
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}. * Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
* Currently, the only modes that MEs are allowed in are Classic and Challenge. * Currently, the only modes that MEs are allowed in are Classic and Challenge.
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes * Additionally, MEs cannot spawn outside of waves 10-180 in those modes
*
* @param newBattleType * @param newBattleType
* @param waveIndex * @param waveIndex
* @param sessionDataEncounterType
*/ */
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean {
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) {
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;

View File

@ -7,7 +7,7 @@ import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag } from "./battler-tags"; import { BattlerTag, GroundedTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender"; import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
@ -1139,7 +1139,9 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
// Disable showAbility during getTargetBenefitScore // Disable showAbility during getTargetBenefitScore
this.showAbility = args[4]; this.showAbility = args[4];
if ((args[0] as Utils.NumberHolder).value <= 0 || (args[1] as Move).id === Moves.ORDER_UP) {
const exceptMoves = [ Moves.ORDER_UP, Moves.ELECTRO_SHOT ];
if ((args[0] as Utils.NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)) {
return false; return false;
} }
@ -1329,7 +1331,6 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
*/ */
const exceptAttrs: Constructor<MoveAttr>[] = [ const exceptAttrs: Constructor<MoveAttr>[] = [
MultiHitAttr, MultiHitAttr,
ChargeAttr,
SacrificialAttr, SacrificialAttr,
SacrificialAttrOnHit SacrificialAttrOnHit
]; ];
@ -1345,6 +1346,7 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
/** Also check if this move is an Attack move and if it's only targeting one Pokemon */ /** Also check if this move is an Attack move and if it's only targeting one Pokemon */
return numTargets === 1 return numTargets === 1
&& !move.isChargingMove()
&& !exceptAttrs.some(attr => move.hasAttr(attr)) && !exceptAttrs.some(attr => move.hasAttr(attr))
&& !exceptMoves.some(id => move.id === id) && !exceptMoves.some(id => move.id === id)
&& move.category !== MoveCategory.STATUS; && move.category !== MoveCategory.STATUS;
@ -2420,11 +2422,12 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
super(true); super(true);
} }
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { async applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): Promise<boolean> {
const targets = pokemon.getOpponents(); const targets = pokemon.getOpponents();
if (simulated || !targets.length) { if (simulated || !targets.length) {
return simulated; return simulated;
} }
const promises: Promise<void>[] = [];
let target: Pokemon; let target: Pokemon;
if (targets.length > 1) { if (targets.length > 1) {
@ -2433,7 +2436,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0]; target = targets[0];
} }
target = target!; // compiler doesn't know its guranteed to be defined target = target!;
pokemon.summonData.speciesForm = target.getSpeciesForm(); pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target.getAbility().id;
@ -2450,18 +2453,23 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.setStatStage(s, target.getStatStage(s)); pokemon.setStatStage(s, target.getStatStage(s));
} }
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId ?? Moves.NONE, m?.ppUsed, m?.ppUp)); pokemon.summonData.moveset = target.getMoveset().map(m => {
const pp = m?.getMove().pp ?? 0;
// if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value.
const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1);
return new PokemonMove(m?.moveId ?? Moves.NONE, 0, ppUp);
});
pokemon.summonData.types = target.getTypes(); pokemon.summonData.types = target.getTypes();
promises.push(pokemon.updateInfo());
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target!.name, }));
pokemon.scene.playSound("battle_anims/PRSFX- Transform"); pokemon.scene.playSound("battle_anims/PRSFX- Transform");
promises.push(pokemon.loadAssets(false).then(() => {
pokemon.loadAssets(false).then(() => {
pokemon.playAnim(); pokemon.playAnim();
pokemon.updateInfo(); pokemon.updateInfo();
}); }));
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); await Promise.all(promises);
return true; return true;
} }
@ -2579,24 +2587,24 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // Clear weather only if user's ability matches the weather and no other pokemon has the ability.
switch (weatherType) { switch (weatherType) {
case (WeatherType.HARSH_SUN): case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND) if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
case (WeatherType.HEAVY_RAIN): case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
case (WeatherType.STRONG_WINDS): case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM) if (pokemon.hasAbility(Abilities.DELTA_STREAM)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
} }
if (simulated) { if (simulated) {
@ -4079,24 +4087,24 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // Clear weather only if user's ability matches the weather and no other pokemon has the ability.
switch (weatherType) { switch (weatherType) {
case (WeatherType.HARSH_SUN): case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND) if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
case (WeatherType.HEAVY_RAIN): case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
case (WeatherType.STRONG_WINDS): case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM) if (pokemon.hasAbility(Abilities.DELTA_STREAM)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
turnOffWeather = true; turnOffWeather = true;
} }
break; break;
} }
if (simulated) { if (simulated) {
@ -4200,6 +4208,11 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
export class BlockRedirectAbAttr extends AbAttr { } export class BlockRedirectAbAttr extends AbAttr { }
/**
* Used by Early Bird, makes the pokemon wake up faster
* @param statusEffect - The {@linkcode StatusEffect} to check for
* @see {@linkcode apply}
*/
export class ReduceStatusEffectDurationAbAttr extends AbAttr { export class ReduceStatusEffectDurationAbAttr extends AbAttr {
private statusEffect: StatusEffect; private statusEffect: StatusEffect;
@ -4209,9 +4222,19 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr {
this.statusEffect = statusEffect; this.statusEffect = statusEffect;
} }
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { /**
* Reduces the number of sleep turns remaining by an extra 1 when applied
* @param args - The args passed to the `AbAttr`:
* - `[0]` - The {@linkcode StatusEffect} of the Pokemon
* - `[1]` - The number of turns remaining until the status is healed
* @returns `true` if the ability was applied
*/
apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (!(args[1] instanceof Utils.NumberHolder)) {
return false;
}
if (args[0] === this.statusEffect) { if (args[0] === this.statusEffect) {
(args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2); args[1].value -= 1;
return true; return true;
} }
@ -4342,6 +4365,30 @@ export class AlwaysHitAbAttr extends AbAttr { }
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
export class IgnoreProtectOnContactAbAttr extends AbAttr { } export class IgnoreProtectOnContactAbAttr extends AbAttr { }
/**
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}.
* Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute.
*/
export class InfiltratorAbAttr extends AbAttr {
/**
* Sets a flag to bypass screens, Substitute, Safeguard, and Mist
* @param pokemon n/a
* @param passive n/a
* @param simulated n/a
* @param cancelled n/a
* @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag
* @returns `true` if the bypass flag was successfully set; `false` otherwise.
*/
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean {
const bypassed = args[0];
if (args[0] instanceof Utils.BooleanHolder) {
bypassed.value = true;
return true;
}
return false;
}
}
export class UncopiableAbilityAbAttr extends AbAttr { export class UncopiableAbilityAbAttr extends AbAttr {
constructor() { constructor() {
super(false); super(false);
@ -5321,7 +5368,8 @@ export function initAbilities() {
.attr(PostSummonTransformAbAttr) .attr(PostSummonTransformAbAttr)
.attr(UncopiableAbilityAbAttr), .attr(UncopiableAbilityAbAttr),
new Ability(Abilities.INFILTRATOR, 5) new Ability(Abilities.INFILTRATOR, 5)
.unimplemented(), .attr(InfiltratorAbAttr)
.partial(), // does not bypass Mist
new Ability(Abilities.MUMMY, 5) new Ability(Abilities.MUMMY, 5)
.attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY) .attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY)
.bypassFaint(), .bypassFaint(),

View File

@ -7,7 +7,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
import { StatusEffect } from "#app/data/status-effect"; import { StatusEffect } from "#app/data/status-effect";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
import i18next from "i18next"; import i18next from "i18next";
@ -88,13 +88,13 @@ export abstract class ArenaTag {
*/ */
public getAffectedPokemon(scene: BattleScene): Pokemon[] { public getAffectedPokemon(scene: BattleScene): Pokemon[] {
switch (this.side) { switch (this.side) {
case ArenaTagSide.PLAYER: case ArenaTagSide.PLAYER:
return scene.getPlayerField() ?? []; return scene.getPlayerField() ?? [];
case ArenaTagSide.ENEMY: case ArenaTagSide.ENEMY:
return scene.getEnemyField() ?? []; return scene.getEnemyField() ?? [];
case ArenaTagSide.BOTH: case ArenaTagSide.BOTH:
default: default:
return scene.getField(true) ?? []; return scene.getField(true) ?? [];
} }
} }
} }
@ -130,7 +130,18 @@ export class MistTag extends ArenaTag {
* to flag the stat reduction as cancelled * to flag the stat reduction as cancelled
* @returns `true` if a stat reduction was cancelled; `false` otherwise * @returns `true` if a stat reduction was cancelled; `false` otherwise
*/ */
override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean { override apply(arena: Arena, simulated: boolean, attacker: Pokemon, cancelled: BooleanHolder): boolean {
// `StatStageChangePhase` currently doesn't have a reference to the source of stat drops,
// so this code currently has no effect on gameplay.
if (attacker) {
const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
if (bypassed.value) {
return false;
}
}
cancelled.value = true; cancelled.value = true;
if (!simulated) { if (!simulated) {
@ -169,12 +180,18 @@ export class WeakenMoveScreenTag extends ArenaTag {
* *
* @param arena the {@linkcode Arena} where the move is applied. * @param arena the {@linkcode Arena} where the move is applied.
* @param simulated n/a * @param simulated n/a
* @param attacker the attacking {@linkcode Pokemon}
* @param moveCategory the attacking move's {@linkcode MoveCategory}. * @param moveCategory the attacking move's {@linkcode MoveCategory}.
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier * @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
* @returns `true` if the attacking move was weakened; `false` otherwise. * @returns `true` if the attacking move was weakened; `false` otherwise.
*/ */
override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { override apply(arena: Arena, simulated: boolean, attacker: Pokemon, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
if (this.weakenedCategories.includes(moveCategory)) { if (this.weakenedCategories.includes(moveCategory)) {
const bypassed = new BooleanHolder(false);
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
if (bypassed.value) {
return false;
}
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
return true; return true;
} }
@ -332,11 +349,11 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
const move = allMoves[moveId]; const move = allMoves[moveId];
switch (move.moveTarget) { switch (move.moveTarget) {
case MoveTarget.ALL_ENEMIES: case MoveTarget.ALL_ENEMIES:
case MoveTarget.ALL_NEAR_ENEMIES: case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_OTHERS: case MoveTarget.ALL_OTHERS:
case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_NEAR_OTHERS:
return true; return true;
} }
return false; return false;
}; };
@ -626,7 +643,7 @@ export class ArenaTrapTag extends ArenaTag {
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise. * @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
*/ */
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean { override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
return false; return false;
} }
@ -807,24 +824,24 @@ class StealthRockTag extends ArenaTrapTag {
let damageHpRatio: number = 0; let damageHpRatio: number = 0;
switch (effectiveness) { switch (effectiveness) {
case 0: case 0:
damageHpRatio = 0; damageHpRatio = 0;
break; break;
case 0.25: case 0.25:
damageHpRatio = 0.03125; damageHpRatio = 0.03125;
break; break;
case 0.5: case 0.5:
damageHpRatio = 0.0625; damageHpRatio = 0.0625;
break; break;
case 1: case 1:
damageHpRatio = 0.125; damageHpRatio = 0.125;
break; break;
case 2: case 2:
damageHpRatio = 0.25; damageHpRatio = 0.25;
break; break;
case 4: case 4:
damageHpRatio = 0.5; damageHpRatio = 0.5;
break; break;
} }
return damageHpRatio; return damageHpRatio;
@ -953,6 +970,9 @@ export class GravityTag extends ArenaTag {
if (pokemon !== null) { if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING); pokemon.removeTag(BattlerTagType.FLOATING);
pokemon.removeTag(BattlerTagType.TELEKINESIS); pokemon.removeTag(BattlerTagType.TELEKINESIS);
if (pokemon.getTag(BattlerTagType.FLYING)) {
pokemon.addTag(BattlerTagType.INTERRUPTED);
}
} }
}); });
} }
@ -1185,63 +1205,63 @@ class GrassWaterPledgeTag extends ArenaTag {
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) { switch (tagType) {
case ArenaTagType.MIST: case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side); return new MistTag(turnCount, sourceId, side);
case ArenaTagType.QUICK_GUARD: case ArenaTagType.QUICK_GUARD:
return new QuickGuardTag(sourceId, side); return new QuickGuardTag(sourceId, side);
case ArenaTagType.WIDE_GUARD: case ArenaTagType.WIDE_GUARD:
return new WideGuardTag(sourceId, side); return new WideGuardTag(sourceId, side);
case ArenaTagType.MAT_BLOCK: case ArenaTagType.MAT_BLOCK:
return new MatBlockTag(sourceId, side); return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD: case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side); return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT: case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
case ArenaTagType.MUD_SPORT: case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId); return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT: case ArenaTagType.WATER_SPORT:
return new WaterSportTag(turnCount, sourceId); return new WaterSportTag(turnCount, sourceId);
case ArenaTagType.ION_DELUGE: case ArenaTagType.ION_DELUGE:
return new IonDelugeTag(sourceMove); return new IonDelugeTag(sourceMove);
case ArenaTagType.SPIKES: case ArenaTagType.SPIKES:
return new SpikesTag(sourceId, side); return new SpikesTag(sourceId, side);
case ArenaTagType.TOXIC_SPIKES: case ArenaTagType.TOXIC_SPIKES:
return new ToxicSpikesTag(sourceId, side); return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT: case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE: case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH: case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side); return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK: case ArenaTagType.STEALTH_ROCK:
return new StealthRockTag(sourceId, side); return new StealthRockTag(sourceId, side);
case ArenaTagType.STICKY_WEB: case ArenaTagType.STICKY_WEB:
return new StickyWebTag(sourceId, side); return new StickyWebTag(sourceId, side);
case ArenaTagType.TRICK_ROOM: case ArenaTagType.TRICK_ROOM:
return new TrickRoomTag(turnCount, sourceId); return new TrickRoomTag(turnCount, sourceId);
case ArenaTagType.GRAVITY: case ArenaTagType.GRAVITY:
return new GravityTag(turnCount); return new GravityTag(turnCount);
case ArenaTagType.REFLECT: case ArenaTagType.REFLECT:
return new ReflectTag(turnCount, sourceId, side); return new ReflectTag(turnCount, sourceId, side);
case ArenaTagType.LIGHT_SCREEN: case ArenaTagType.LIGHT_SCREEN:
return new LightScreenTag(turnCount, sourceId, side); return new LightScreenTag(turnCount, sourceId, side);
case ArenaTagType.AURORA_VEIL: case ArenaTagType.AURORA_VEIL:
return new AuroraVeilTag(turnCount, sourceId, side); return new AuroraVeilTag(turnCount, sourceId, side);
case ArenaTagType.TAILWIND: case ArenaTagType.TAILWIND:
return new TailwindTag(turnCount, sourceId, side); return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR: case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side); return new HappyHourTag(turnCount, sourceId, side);
case ArenaTagType.SAFEGUARD: case ArenaTagType.SAFEGUARD:
return new SafeguardTag(turnCount, sourceId, side); return new SafeguardTag(turnCount, sourceId, side);
case ArenaTagType.IMPRISON: case ArenaTagType.IMPRISON:
return new ImprisonTag(sourceId, side); return new ImprisonTag(sourceId, side);
case ArenaTagType.FIRE_GRASS_PLEDGE: case ArenaTagType.FIRE_GRASS_PLEDGE:
return new FireGrassPledgeTag(sourceId, side); return new FireGrassPledgeTag(sourceId, side);
case ArenaTagType.WATER_FIRE_PLEDGE: case ArenaTagType.WATER_FIRE_PLEDGE:
return new WaterFirePledgeTag(sourceId, side); return new WaterFirePledgeTag(sourceId, side);
case ArenaTagType.GRASS_WATER_PLEDGE: case ArenaTagType.GRASS_WATER_PLEDGE:
return new GrassWaterPledgeTag(sourceId, side); return new GrassWaterPledgeTag(sourceId, side);
default: default:
return null; return null;
} }
} }

View File

@ -13,14 +13,14 @@ export function getBiomeName(biome: Biome | -1) {
return i18next.t("biome:unknownLocation"); return i18next.t("biome:unknownLocation");
} }
switch (biome) { switch (biome) {
case Biome.GRASS: case Biome.GRASS:
return i18next.t("biome:GRASS"); return i18next.t("biome:GRASS");
case Biome.RUINS: case Biome.RUINS:
return i18next.t("biome:RUINS"); return i18next.t("biome:RUINS");
case Biome.END: case Biome.END:
return i18next.t("biome:END"); return i18next.t("biome:END");
default: default:
return i18next.t(`biome:${Biome[biome].toUpperCase()}`); return i18next.t(`biome:${Biome[biome].toUpperCase()}`);
} }
} }

View File

@ -1,7 +1,6 @@
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { PokeballType } from "#app/data/pokeball"; import { PokeballType } from "#app/data/pokeball";
import Pokemon from "#app/field/pokemon"; import Pokemon from "#app/field/pokemon";
import { Stat } from "#enums/stat";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { WeatherType } from "#app/data/weather"; import { WeatherType } from "#app/data/weather";
@ -10,7 +9,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier } from "#app/modifier/modifier"; import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
@ -271,9 +270,21 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
], ],
[Species.TYROGUE]: [ [Species.TYROGUE]: [
new SpeciesEvolution(Species.HITMONLEE, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] > p.stats[Stat.DEF])), /**
new SpeciesEvolution(Species.HITMONCHAN, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] < p.stats[Stat.DEF])), * Custom: Evolves into Hitmonlee, Hitmonchan or Hitmontop at level 20
new SpeciesEvolution(Species.HITMONTOP, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] === p.stats[Stat.DEF])) * if it knows Low Sweep, Mach Punch, or Rapid Spin, respectively.
* If Tyrogue knows multiple of these moves, its evolution is based on
* the first qualifying move in its moveset.
*/
new SpeciesEvolution(Species.HITMONLEE, 20, null, new SpeciesEvolutionCondition(p =>
p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.LOW_SWEEP
)),
new SpeciesEvolution(Species.HITMONCHAN, 20, null, new SpeciesEvolutionCondition(p =>
p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.MACH_PUNCH
)),
new SpeciesEvolution(Species.HITMONTOP, 20, null, new SpeciesEvolutionCondition(p =>
p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.RAPID_SPIN
)),
], ],
[Species.KOFFING]: [ [Species.KOFFING]: [
new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
@ -1652,11 +1663,11 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG)
] ]
}; };

File diff suppressed because it is too large Load Diff

View File

@ -15,25 +15,25 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
*/ */
export function getStarterValueFriendshipCap(starterCost: number): number { export function getStarterValueFriendshipCap(starterCost: number): number {
switch (starterCost) { switch (starterCost) {
case 1: case 1:
return 20; return 20;
case 2: case 2:
return 40; return 40;
case 3: case 3:
return 60; return 60;
case 4: case 4:
return 100; return 100;
case 5: case 5:
return 140; return 140;
case 6: case 6:
return 200; return 200;
case 7: case 7:
return 280; return 280;
case 8: case 8:
case 9: case 9:
return 450; return 450;
default: default:
return 600; return 600;
} }
} }

View File

@ -1,6 +1,6 @@
//import { battleAnimRawData } from "./battle-anim-raw-data"; //import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { AttackMove, BeakBlastHeaderAttr, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import Pokemon from "../field/pokemon"; import Pokemon from "../field/pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
@ -134,15 +134,15 @@ export class AnimConfig {
for (const te of frameTimedEvents[fte]) { for (const te of frameTimedEvents[fte]) {
let timedEvent: AnimTimedEvent | undefined; let timedEvent: AnimTimedEvent | undefined;
switch (te.eventType) { switch (te.eventType) {
case "AnimTimedSoundEvent": case "AnimTimedSoundEvent":
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
break; break;
case "AnimTimedAddBgEvent": case "AnimTimedAddBgEvent":
timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te);
break; break;
case "AnimTimedUpdateBgEvent": case "AnimTimedUpdateBgEvent":
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break; break;
} }
timedEvent && timedEvents.push(timedEvent); timedEvent && timedEvents.push(timedEvent);
@ -243,12 +243,12 @@ class AnimFrame {
if (!init) { if (!init) {
let target = AnimFrameTarget.GRAPHIC; let target = AnimFrameTarget.GRAPHIC;
switch (pattern) { switch (pattern) {
case -2: case -2:
target = AnimFrameTarget.TARGET; target = AnimFrameTarget.TARGET;
break; break;
case -1: case -1:
target = AnimFrameTarget.USER; target = AnimFrameTarget.USER;
break; break;
} }
this.target = target; this.target = target;
this.graphicFrame = pattern >= 0 ? pattern : 0; this.graphicFrame = pattern >= 0 ? pattern : 0;
@ -476,8 +476,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else { } else {
const loadedCheckTimer = setInterval(() => { const loadedCheckTimer = setInterval(() => {
if (moveAnims.get(move) !== null) { if (moveAnims.get(move) !== null) {
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0]; const chargeAnimSource = (allMoves[move].isChargingMove())
if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) { ? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0]
?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) {
return; return;
} }
clearInterval(loadedCheckTimer); clearInterval(loadedCheckTimer);
@ -507,11 +510,12 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else { } else {
populateMoveAnim(move, ba); populateMoveAnim(move, ba);
} }
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] const chargeAnimSource = (allMoves[move].isChargingMove())
|| allMoves[move].getAttrs(DelayedAttackAttr)[0] ? allMoves[move]
|| allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]; : (allMoves[move].getAttrs(DelayedAttackAttr)[0]
if (chargeAttr) { ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve()); if (chargeAnimSource) {
initMoveChargeAnim(scene, chargeAnimSource.chargeAnim).then(() => resolve());
} else { } else {
resolve(); resolve();
} }
@ -638,11 +642,12 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
return new Promise(resolve => { return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
for (const moveId of moveIds) { for (const moveId of moveIds) {
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] const chargeAnimSource = (allMoves[moveId].isChargingMove())
|| allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ? allMoves[moveId]
|| allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]; : (allMoves[moveId].getAttrs(DelayedAttackAttr)[0]
if (chargeAttr) { ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]);
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); if (chargeAnimSource) {
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?
if (Array.isArray(moveChargeAnims)) { if (Array.isArray(moveChargeAnims)) {
moveAnimations.push(moveChargeAnims[1]); moveAnimations.push(moveChargeAnims[1]);
@ -803,23 +808,23 @@ export abstract class BattleAnim {
let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1); let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100); const scaleY = (frame.zoomY / 100);
switch (frame.focus) { switch (frame.focus) {
case AnimFocus.TARGET: case AnimFocus.TARGET:
x += targetInitialX - targetFocusX; x += targetInitialX - targetFocusX;
y += (targetInitialY - targetHalfHeight) - targetFocusY; y += (targetInitialY - targetHalfHeight) - targetFocusY;
break; break;
case AnimFocus.USER: case AnimFocus.USER:
x += userInitialX - userFocusX; x += userInitialX - userFocusX;
y += (userInitialY - userHalfHeight) - userFocusY; y += (userInitialY - userHalfHeight) - userFocusY;
break; break;
case AnimFocus.USER_TARGET: case AnimFocus.USER_TARGET:
const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3],
this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y);
x = point[0]; x = point[0];
y = point[1]; y = point[1];
if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) {
scaleX = scaleX * -1; scaleX = scaleX * -1;
} }
break; break;
} }
const angle = -frame.angle; const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++; const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
@ -993,44 +998,44 @@ export abstract class BattleAnim {
spritePriorities[graphicIndex] = frame.priority; spritePriorities[graphicIndex] = frame.priority;
const setSpritePriority = (priority: integer) => { const setSpritePriority = (priority: integer) => {
switch (priority) { switch (priority) {
case 0: case 0:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption?
break; break;
case 1: case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
break; break;
case 2: case 2:
switch (frame.focus) { switch (frame.focus) {
case AnimFocus.USER: case AnimFocus.USER:
if (this.bgSprite) { if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else { } else {
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
}
break;
case AnimFocus.TARGET:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
break;
} }
break; break;
case AnimFocus.TARGET: case 3:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? switch (frame.focus) {
case AnimFocus.USER:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break;
case AnimFocus.TARGET:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
break;
}
break; break;
default: default:
setSpritePriority(1); setSpritePriority(1);
break;
}
break;
case 3:
switch (frame.focus) {
case AnimFocus.USER:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break;
case AnimFocus.TARGET:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
break;
}
break;
default:
setSpritePriority(1);
} }
}; };
setSpritePriority(frame.priority); setSpritePriority(frame.priority);
@ -1396,108 +1401,108 @@ export async function populateAnims() {
const fieldName = field.slice(0, field.indexOf(":")); const fieldName = field.slice(0, field.indexOf(":"));
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim(); const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim();
switch (fieldName) { switch (fieldName) {
case "array": case "array":
const framesData = fieldData.split(" - - - ").slice(1); const framesData = fieldData.split(" - - - ").slice(1);
for (let fd = 0; fd < framesData.length; fd++) { for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]); anim.frames.push([]);
const frameData = framesData[fd]; const frameData = framesData[fd];
const focusFramesData = frameData.split(" - - "); const focusFramesData = frameData.split(" - - ");
for (let tf = 0; tf < focusFramesData.length; tf++) { for (let tf = 0; tf < focusFramesData.length; tf++) {
const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); const values = focusFramesData[tf].replace(/ \- /g, "").split("\n");
const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]),
parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]),
parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]),
parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus);
anim.frames[fd].push(targetFrame); anim.frames[fd].push(targetFrame);
}
} }
} break;
break; case "graphic":
case "graphic": const graphic = fieldData !== "''" ? fieldData : "";
const graphic = fieldData !== "''" ? fieldData : ""; anim.graphic = graphic.indexOf(".") > -1
anim.graphic = graphic.indexOf(".") > -1 ? graphic.slice(0, fieldData.indexOf("."))
? graphic.slice(0, fieldData.indexOf(".")) : graphic;
: graphic; break;
break; case "timing":
case "timing": const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); for (let t = 0; t < timingEntries.length; t++) {
for (let t = 0; t < timingEntries.length; t++) { const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",")
const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1");
.replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? let timedEvent: AnimTimedEvent | undefined;
let timedEvent: AnimTimedEvent | undefined; switch (timingType) {
switch (timingType) { case 0:
case 0: if (resourceName && resourceName.indexOf(".") === -1) {
if (resourceName && resourceName.indexOf(".") === -1) { let ext: string | undefined;
let ext: string | undefined; [ "wav", "mp3", "m4a" ].every(e => {
[ "wav", "mp3", "m4a" ].every(e => { if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) { ext = e;
ext = e; return false;
return false; }
return true;
});
if (!ext) {
ext = ".wav";
}
resourceName += `.${ext}`;
} }
return true; timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
}); break;
if (!ext) { case 1:
ext = ".wav"; timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
case 2:
timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
}
if (!timedEvent) {
continue;
}
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct?
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
case "bgX":
case "bgY":
value = parseFloat(value);
break;
case "volume":
case "pitch":
case "opacity":
case "colorRed":
case "colorGreen":
case "colorBlue":
case "colorAlpha":
case "duration":
case "flashScope":
case "flashRed":
case "flashGreen":
case "flashBlue":
case "flashAlpha":
case "flashDuration":
value = parseInt(value);
break;
}
if (timedEvent.hasOwnProperty(prop)) {
timedEvent[prop] = value;
} }
resourceName += `.${ext}`;
} }
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); if (!anim.frameTimedEvents.has(frameIndex)) {
break; anim.frameTimedEvents.set(frameIndex, []);
case 1:
timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
case 2:
timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
}
if (!timedEvent) {
continue;
}
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct?
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
case "bgX":
case "bgY":
value = parseFloat(value);
break;
case "volume":
case "pitch":
case "opacity":
case "colorRed":
case "colorGreen":
case "colorBlue":
case "colorAlpha":
case "duration":
case "flashScope":
case "flashRed":
case "flashGreen":
case "flashBlue":
case "flashAlpha":
case "flashDuration":
value = parseInt(value);
break;
} }
if (timedEvent.hasOwnProperty(prop)) {
timedEvent[prop] = value;
}
}
if (!anim.frameTimedEvents.has(frameIndex)) {
anim.frameTimedEvents.set(frameIndex, []);
}
anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct? anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
} }
break; break;
case "position": case "position":
anim.position = parseInt(fieldData); anim.position = parseInt(fieldData);
break; break;
case "hue": case "hue":
anim.hue = parseInt(fieldData); anim.hue = parseInt(fieldData);
break; break;
} }
} }
} }

View File

@ -1,29 +1,43 @@
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; import BattleScene from "#app/battle-scene";
import { getPokemonNameWithAffix } from "../messages"; import {
import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; allAbilities,
import { StatusEffect } from "./status-effect"; applyAbAttrs,
import * as Utils from "../utils"; BlockNonDirectDamageAbAttr,
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move"; FlinchEffectAbAttr,
import { Type } from "./type"; ProtectStatAbAttr,
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability"; ReverseDrainAbAttr
import { TerrainType } from "./terrain"; } from "#app/data/ability";
import { WeatherType } from "./weather"; import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import { allAbilities } from "./ability"; import Move, {
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; allMoves,
import { Abilities } from "#enums/abilities"; applyMoveAttrs,
import { BattlerTagType } from "#enums/battler-tag-type"; ConsecutiveUseDoublePowerAttr,
import { Moves } from "#enums/moves"; HealOnAllyAttr,
import { Species } from "#enums/species"; MoveCategory,
import i18next from "#app/plugins/i18n"; MoveFlags,
import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat"; StatusCategoryOnAllyAttr
} from "#app/data/move";
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
import { StatusEffect } from "#app/data/status-effect";
import { TerrainType } from "#app/data/terrain";
import { Type } from "#app/data/type";
import { WeatherType } from "#app/data/weather";
import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import i18next from "#app/plugins/i18n";
import BattleScene from "#app/battle-scene"; import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { Species } from "#enums/species";
import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat";
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
@ -33,6 +47,7 @@ export enum BattlerTagLapseType {
MOVE_EFFECT, MOVE_EFFECT,
TURN_END, TURN_END,
HIT, HIT,
AFTER_HIT,
CUSTOM CUSTOM
} }
@ -405,7 +420,7 @@ export class RechargingTag extends BattlerTag {
*/ */
export class BeakBlastChargingTag extends BattlerTag { export class BeakBlastChargingTag extends BattlerTag {
constructor() { constructor() {
super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST); super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1, Moves.BEAK_BLAST);
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -421,16 +436,13 @@ export class BeakBlastChargingTag extends BattlerTag {
* to be removed after the source makes a move (or the turn ends, whichever comes first) * to be removed after the source makes a move (or the turn ends, whichever comes first)
* @param pokemon {@linkcode Pokemon} the owner of this tag * @param pokemon {@linkcode Pokemon} the owner of this tag
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
* @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise * @returns `true` if invoked with the `AFTER_HIT` lapse type
*/ */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const effectPhase = pokemon.scene.getCurrentPhase(); const phaseData = getMoveEffectPhaseData(pokemon);
if (effectPhase instanceof MoveEffectPhase) { if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
if (effectPhase.move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
} }
return true; return true;
} }
@ -444,11 +456,10 @@ export class BeakBlastChargingTag extends BattlerTag {
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap} * @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
*/ */
export class ShellTrapTag extends BattlerTag { export class ShellTrapTag extends BattlerTag {
public activated: boolean; public activated: boolean = false;
constructor() { constructor() {
super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1); super(BattlerTagType.SHELL_TRAP, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1);
this.activated = false;
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -459,25 +470,33 @@ export class ShellTrapTag extends BattlerTag {
* "Activates" the shell trap, causing the tag owner to move next. * "Activates" the shell trap, causing the tag owner to move next.
* @param pokemon {@linkcode Pokemon} the owner of this tag * @param pokemon {@linkcode Pokemon} the owner of this tag
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
* @returns `true` if invoked with the `CUSTOM` lapse type; `false` otherwise * @returns `true` if invoked with the `AFTER_HIT` lapse type
*/ */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( const phaseData = getMoveEffectPhaseData(pokemon);
phase => phase instanceof MovePhase && phase.pokemon === pokemon
);
const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase
);
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { // Trap should only be triggered by opponent's Physical moves
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) {
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase && phase.pokemon === pokemon
);
const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase
);
// Only shift MovePhase timing if it's not already next up
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase);
}
this.activated = true;
} }
this.activated = true;
return true; return true;
} }
return super.lapse(pokemon, lapseType); return super.lapse(pokemon, lapseType);
} }
} }
@ -641,7 +660,7 @@ export class ConfusedTag extends BattlerTag {
if (pokemon.randSeedInt(3) === 0) { if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getEffectiveStat(Stat.ATK); const atk = pokemon.getEffectiveStat(Stat.ATK);
const def = pokemon.getEffectiveStat(Stat.DEF); const def = pokemon.getEffectiveStat(Stat.DEF);
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); const damage = toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage); pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++; pokemon.battleData.hitCount++;
@ -812,13 +831,13 @@ export class SeedTag extends BattlerTag {
if (ret) { if (ret) {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) { if (source) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1, !reverseDrain ? damage : damage * -1,
@ -838,7 +857,7 @@ export class SeedTag extends BattlerTag {
export class NightmareTag extends BattlerTag { export class NightmareTag extends BattlerTag {
constructor() { constructor() {
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE); super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE);
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -860,11 +879,11 @@ export class NightmareTag extends BattlerTag {
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4));
} }
} }
@ -919,18 +938,14 @@ export class EncoreTag extends BattlerTag {
} }
switch (repeatableMove.move) { switch (repeatableMove.move) {
case Moves.MIMIC: case Moves.MIMIC:
case Moves.MIRROR_MOVE: case Moves.MIRROR_MOVE:
case Moves.TRANSFORM: case Moves.TRANSFORM:
case Moves.STRUGGLE: case Moves.STRUGGLE:
case Moves.SKETCH: case Moves.SKETCH:
case Moves.SLEEP_TALK: case Moves.SLEEP_TALK:
case Moves.ENCORE: case Moves.ENCORE:
return false; return false;
}
if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) {
return false;
} }
this.moveId = repeatableMove.move; this.moveId = repeatableMove.move;
@ -1004,7 +1019,7 @@ export class IngrainTag extends TrappedTag {
new PokemonHealPhase( new PokemonHealPhase(
pokemon.scene, pokemon.scene,
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 16), toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true true
) )
@ -1067,7 +1082,7 @@ export class AquaRingTag extends BattlerTag {
new PokemonHealPhase( new PokemonHealPhase(
pokemon.scene, pokemon.scene,
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 16), toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:aquaRingLapse", { i18next.t("battlerTags:aquaRingLapse", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
pokemonName: getPokemonNameWithAffix(pokemon) pokemonName: getPokemonNameWithAffix(pokemon)
@ -1161,11 +1176,11 @@ export abstract class DamagingTrapTag extends TrappedTag {
); );
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
} }
} }
@ -1356,7 +1371,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); const attacker = effectPhase.getPokemon();
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
} }
} }
} }
@ -1641,12 +1656,12 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
this.stat = highestStat; this.stat = highestStat;
switch (this.stat) { switch (this.stat) {
case Stat.SPD: case Stat.SPD:
this.multiplier = 1.5; this.multiplier = 1.5;
break; break;
default: default:
this.multiplier = 1.3; this.multiplier = 1.3;
break; break;
} }
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true);
@ -1709,7 +1724,7 @@ export class SemiInvulnerableTag extends BattlerTag {
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible
pokemon.scene.tweens.addCounter({ pokemon.scene.tweens.addCounter({
duration: Utils.getFrameMs(2), duration: getFrameMs(2),
onComplete: () => pokemon.setVisible(true) onComplete: () => pokemon.setVisible(true)
}); });
} }
@ -1860,12 +1875,12 @@ export class SaltCuredTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8));
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battlerTags:saltCuredLapse", { i18next.t("battlerTags:saltCuredLapse", {
@ -1907,11 +1922,11 @@ export class CursedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4));
pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
} }
} }
@ -2173,7 +2188,7 @@ export class GulpMissileTag extends BattlerTag {
return true; return true;
} }
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
@ -2289,7 +2304,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
* @returns `true` if the move cannot be used because the target is an ally * @returns `true` if the move cannot be used because the target is an ally
*/ */
override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) { override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) {
const moveCategory = new Utils.IntegerHolder(allMoves[move].category); const moveCategory = new NumberHolder(allMoves[move].category);
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) { if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
return true; return true;
@ -2436,15 +2451,15 @@ export class SubstituteTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
switch (lapseType) { switch (lapseType) {
case BattlerTagLapseType.PRE_MOVE: case BattlerTagLapseType.PRE_MOVE:
this.onPreMove(pokemon); this.onPreMove(pokemon);
break; break;
case BattlerTagLapseType.AFTER_MOVE: case BattlerTagLapseType.AFTER_MOVE:
this.onAfterMove(pokemon); this.onAfterMove(pokemon);
break; break;
case BattlerTagLapseType.HIT: case BattlerTagLapseType.HIT:
this.onHit(pokemon); this.onHit(pokemon);
break; break;
} }
return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse
} }
@ -2506,7 +2521,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
const ret = super.lapse(pokemon, lapseType); const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) { if (pokemon.mysteryEncounterBattleEffects) {
@ -2571,7 +2586,7 @@ export class TormentTag extends MoveRestrictionBattlerTag {
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move]; const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY);
const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS);
if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) {
return true; return true;
@ -2769,179 +2784,179 @@ export class PowerTrickTag extends BattlerTag {
*/ */
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) { switch (tagType) {
case BattlerTagType.RECHARGING: case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove); return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING: case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag(); return new BeakBlastChargingTag();
case BattlerTagType.SHELL_TRAP: case BattlerTagType.SHELL_TRAP:
return new ShellTrapTag(); return new ShellTrapTag();
case BattlerTagType.FLINCHED: case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove); return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED: case BattlerTagType.INTERRUPTED:
return new InterruptedTag(sourceMove); return new InterruptedTag(sourceMove);
case BattlerTagType.CONFUSED: case BattlerTagType.CONFUSED:
return new ConfusedTag(turnCount, sourceMove); return new ConfusedTag(turnCount, sourceMove);
case BattlerTagType.INFATUATED: case BattlerTagType.INFATUATED:
return new InfatuatedTag(sourceMove, sourceId); return new InfatuatedTag(sourceMove, sourceId);
case BattlerTagType.SEEDED: case BattlerTagType.SEEDED:
return new SeedTag(sourceId); return new SeedTag(sourceId);
case BattlerTagType.NIGHTMARE: case BattlerTagType.NIGHTMARE:
return new NightmareTag(); return new NightmareTag();
case BattlerTagType.FRENZY: case BattlerTagType.FRENZY:
return new FrenzyTag(turnCount, sourceMove, sourceId); return new FrenzyTag(turnCount, sourceMove, sourceId);
case BattlerTagType.CHARGING: case BattlerTagType.CHARGING:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId);
case BattlerTagType.ENCORE: case BattlerTagType.ENCORE:
return new EncoreTag(sourceId); return new EncoreTag(sourceId);
case BattlerTagType.HELPING_HAND: case BattlerTagType.HELPING_HAND:
return new HelpingHandTag(sourceId); return new HelpingHandTag(sourceId);
case BattlerTagType.INGRAIN: case BattlerTagType.INGRAIN:
return new IngrainTag(sourceId); return new IngrainTag(sourceId);
case BattlerTagType.AQUA_RING: case BattlerTagType.AQUA_RING:
return new AquaRingTag(); return new AquaRingTag();
case BattlerTagType.DROWSY: case BattlerTagType.DROWSY:
return new DrowsyTag(); return new DrowsyTag();
case BattlerTagType.TRAPPED: case BattlerTagType.TRAPPED:
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
case BattlerTagType.NO_RETREAT: case BattlerTagType.NO_RETREAT:
return new NoRetreatTag(sourceId); return new NoRetreatTag(sourceId);
case BattlerTagType.BIND: case BattlerTagType.BIND:
return new BindTag(turnCount, sourceId); return new BindTag(turnCount, sourceId);
case BattlerTagType.WRAP: case BattlerTagType.WRAP:
return new WrapTag(turnCount, sourceId); return new WrapTag(turnCount, sourceId);
case BattlerTagType.FIRE_SPIN: case BattlerTagType.FIRE_SPIN:
return new FireSpinTag(turnCount, sourceId); return new FireSpinTag(turnCount, sourceId);
case BattlerTagType.WHIRLPOOL: case BattlerTagType.WHIRLPOOL:
return new WhirlpoolTag(turnCount, sourceId); return new WhirlpoolTag(turnCount, sourceId);
case BattlerTagType.CLAMP: case BattlerTagType.CLAMP:
return new ClampTag(turnCount, sourceId); return new ClampTag(turnCount, sourceId);
case BattlerTagType.SAND_TOMB: case BattlerTagType.SAND_TOMB:
return new SandTombTag(turnCount, sourceId); return new SandTombTag(turnCount, sourceId);
case BattlerTagType.MAGMA_STORM: case BattlerTagType.MAGMA_STORM:
return new MagmaStormTag(turnCount, sourceId); return new MagmaStormTag(turnCount, sourceId);
case BattlerTagType.SNAP_TRAP: case BattlerTagType.SNAP_TRAP:
return new SnapTrapTag(turnCount, sourceId); return new SnapTrapTag(turnCount, sourceId);
case BattlerTagType.THUNDER_CAGE: case BattlerTagType.THUNDER_CAGE:
return new ThunderCageTag(turnCount, sourceId); return new ThunderCageTag(turnCount, sourceId);
case BattlerTagType.INFESTATION: case BattlerTagType.INFESTATION:
return new InfestationTag(turnCount, sourceId); return new InfestationTag(turnCount, sourceId);
case BattlerTagType.PROTECTED: case BattlerTagType.PROTECTED:
return new ProtectedTag(sourceMove); return new ProtectedTag(sourceMove);
case BattlerTagType.SPIKY_SHIELD: case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8); return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD: case BattlerTagType.KINGS_SHIELD:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT: case BattlerTagType.OBSTRUCT:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP: case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER: case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove); return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK: case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove); return new ContactBurnProtectedTag(sourceMove);
case BattlerTagType.ENDURING: case BattlerTagType.ENDURING:
return new EnduringTag(sourceMove); return new EnduringTag(sourceMove);
case BattlerTagType.STURDY: case BattlerTagType.STURDY:
return new SturdyTag(sourceMove); return new SturdyTag(sourceMove);
case BattlerTagType.PERISH_SONG: case BattlerTagType.PERISH_SONG:
return new PerishSongTag(turnCount); return new PerishSongTag(turnCount);
case BattlerTagType.CENTER_OF_ATTENTION: case BattlerTagType.CENTER_OF_ATTENTION:
return new CenterOfAttentionTag(sourceMove); return new CenterOfAttentionTag(sourceMove);
case BattlerTagType.TRUANT: case BattlerTagType.TRUANT:
return new TruantTag(); return new TruantTag();
case BattlerTagType.SLOW_START: case BattlerTagType.SLOW_START:
return new SlowStartTag(); return new SlowStartTag();
case BattlerTagType.PROTOSYNTHESIS: case BattlerTagType.PROTOSYNTHESIS:
return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN);
case BattlerTagType.QUARK_DRIVE: case BattlerTagType.QUARK_DRIVE:
return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC);
case BattlerTagType.FLYING: case BattlerTagType.FLYING:
case BattlerTagType.UNDERGROUND: case BattlerTagType.UNDERGROUND:
case BattlerTagType.UNDERWATER: case BattlerTagType.UNDERWATER:
case BattlerTagType.HIDDEN: case BattlerTagType.HIDDEN:
return new SemiInvulnerableTag(tagType, turnCount, sourceMove); return new SemiInvulnerableTag(tagType, turnCount, sourceMove);
case BattlerTagType.FIRE_BOOST: case BattlerTagType.FIRE_BOOST:
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST: case BattlerTagType.CRIT_BOOST:
return new CritBoostTag(tagType, sourceMove); return new CritBoostTag(tagType, sourceMove);
case BattlerTagType.DRAGON_CHEER: case BattlerTagType.DRAGON_CHEER:
return new DragonCheerTag(); return new DragonCheerTag();
case BattlerTagType.ALWAYS_CRIT: case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY: case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT: case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
case BattlerTagType.BYPASS_SLEEP: case BattlerTagType.BYPASS_SLEEP:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove);
case BattlerTagType.IGNORE_FLYING: case BattlerTagType.IGNORE_FLYING:
return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.ROOSTED: case BattlerTagType.ROOSTED:
return new RoostedTag(); return new RoostedTag();
case BattlerTagType.BURNED_UP: case BattlerTagType.BURNED_UP:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.DOUBLE_SHOCKED: case BattlerTagType.DOUBLE_SHOCKED:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.SALT_CURED: case BattlerTagType.SALT_CURED:
return new SaltCuredTag(sourceId); return new SaltCuredTag(sourceId);
case BattlerTagType.CURSED: case BattlerTagType.CURSED:
return new CursedTag(sourceId); return new CursedTag(sourceId);
case BattlerTagType.CHARGED: case BattlerTagType.CHARGED:
return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true);
case BattlerTagType.FLOATING: case BattlerTagType.FLOATING:
return new FloatingTag(tagType, sourceMove); return new FloatingTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED: case BattlerTagType.MINIMIZED:
return new MinimizeTag(); return new MinimizeTag();
case BattlerTagType.DESTINY_BOND: case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId); return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE: case BattlerTagType.ICE_FACE:
return new IceFaceBlockDamageTag(tagType); return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE: case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType); return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING: case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove); return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK: case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId); return new OctolockTag(sourceId);
case BattlerTagType.DISABLED: case BattlerTagType.DISABLED:
return new DisabledTag(sourceId); return new DisabledTag(sourceId);
case BattlerTagType.IGNORE_GHOST: case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]);
case BattlerTagType.IGNORE_DARK: case BattlerTagType.IGNORE_DARK:
return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]);
case BattlerTagType.GULP_MISSILE_ARROKUDA: case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU: case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove); return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.TAR_SHOT: case BattlerTagType.TAR_SHOT:
return new TarShotTag(); return new TarShotTag();
case BattlerTagType.ELECTRIFIED: case BattlerTagType.ELECTRIFIED:
return new ElectrifiedTag(); return new ElectrifiedTag();
case BattlerTagType.THROAT_CHOPPED: case BattlerTagType.THROAT_CHOPPED:
return new ThroatChoppedTag(); return new ThroatChoppedTag();
case BattlerTagType.GORILLA_TACTICS: case BattlerTagType.GORILLA_TACTICS:
return new GorillaTacticsTag(); return new GorillaTacticsTag();
case BattlerTagType.SUBSTITUTE: case BattlerTagType.SUBSTITUTE:
return new SubstituteTag(sourceMove, sourceId); return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.AUTOTOMIZED: case BattlerTagType.AUTOTOMIZED:
return new AutotomizedTag(); return new AutotomizedTag();
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag(); return new MysteryEncounterPostSummonTag();
case BattlerTagType.HEAL_BLOCK: case BattlerTagType.HEAL_BLOCK:
return new HealBlockTag(turnCount, sourceMove); return new HealBlockTag(turnCount, sourceMove);
case BattlerTagType.TORMENT: case BattlerTagType.TORMENT:
return new TormentTag(sourceId); return new TormentTag(sourceId);
case BattlerTagType.TAUNT: case BattlerTagType.TAUNT:
return new TauntTag(); return new TauntTag();
case BattlerTagType.IMPRISON: case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId); return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB: case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag(sourceId); return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS: case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove); return new TelekinesisTag(sourceMove);
case BattlerTagType.POWER_TRICK: case BattlerTagType.POWER_TRICK:
return new PowerTrickTag(sourceMove, sourceId); return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
} }
} }
@ -2955,3 +2970,22 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag {
tag.loadTag(source); tag.loadTag(source);
return tag; return tag;
} }
/**
* Helper function to verify that the current phase is a MoveEffectPhase and provide quick access to commonly used fields
*
* @param pokemon {@linkcode Pokemon} The Pokémon used to access the current phase
* @returns null if current phase is not MoveEffectPhase, otherwise Object containing the {@linkcode MoveEffectPhase}, and its
* corresponding {@linkcode Move} and user {@linkcode Pokemon}
*/
function getMoveEffectPhaseData(pokemon: Pokemon): {phase: MoveEffectPhase, attacker: Pokemon, move: Move} | null {
const phase = pokemon.scene.getCurrentPhase();
if (phase instanceof MoveEffectPhase) {
return {
phase : phase,
attacker : phase.getPokemon(),
move : phase.move.getMove()
};
}
return null;
}

View File

@ -22,42 +22,42 @@ export type BerryPredicate = (pokemon: Pokemon) => boolean;
export function getBerryPredicate(berryType: BerryType): BerryPredicate { export function getBerryPredicate(berryType: BerryType): BerryPredicate {
switch (berryType) { switch (berryType) {
case BerryType.SITRUS: case BerryType.SITRUS:
return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5;
case BerryType.LUM: case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA: case BerryType.ENIGMA:
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI: case BerryType.LIECHI:
case BerryType.GANLON: case BerryType.GANLON:
case BerryType.PETAYA: case BerryType.PETAYA:
case BerryType.APICOT: case BerryType.APICOT:
case BerryType.SALAC: case BerryType.SALAC:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25; return pokemon.getHpRatio() < 0.25;
}; };
case BerryType.LEPPA: case BerryType.LEPPA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
}; };
} }
} }
@ -65,70 +65,70 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void;
export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
switch (berryType) { switch (berryType) {
case BerryType.SITRUS: case BerryType.SITRUS:
case BerryType.ENIGMA: case BerryType.ENIGMA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
}; };
case BerryType.LUM: case BerryType.LUM:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
if (pokemon.status) { if (pokemon.status) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
} }
pokemon.resetStatus(true, true); pokemon.resetStatus(true, true);
pokemon.updateInfo(); pokemon.updateInfo();
}; };
case BerryType.LIECHI: case BerryType.LIECHI:
case BerryType.GANLON: case BerryType.GANLON:
case BerryType.PETAYA: case BerryType.PETAYA:
case BerryType.APICOT: case BerryType.APICOT:
case BerryType.SALAC: case BerryType.SALAC:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new Utils.NumberHolder(1); const statStages = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
pokemon.addTag(BattlerTagType.CRIT_BOOST); pokemon.addTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
const stages = new Utils.NumberHolder(2); const stages = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
}; };
case BerryType.LEPPA: case BerryType.LEPPA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
if (ppRestoreMove !== undefined) { if (ppRestoreMove !== undefined) {
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
} }
}; };
} }
} }

View File

@ -448,21 +448,21 @@ export class SingleGenerationChallenge extends Challenge {
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
let trainerTypes: TrainerType[] = []; let trainerTypes: TrainerType[] = [];
switch (waveIndex) { switch (waveIndex) {
case 182: case 182:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
break; break;
case 184: case 184:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
break; break;
case 186: case 186:
trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ];
break; break;
case 188: case 188:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
break; break;
case 190: case 190:
trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ];
break; break;
} }
if (trainerTypes.length === 0) { if (trainerTypes.length === 0) {
return false; return false;
@ -891,45 +891,45 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
gameMode.challenges.forEach(c => { gameMode.challenges.forEach(c => {
if (c.value !== 0) { if (c.value !== 0) {
switch (challengeType) { switch (challengeType) {
case ChallengeType.STARTER_CHOICE: case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
break; break;
case ChallengeType.STARTER_POINTS: case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]); ret ||= c.applyStarterPoints(args[0]);
break; break;
case ChallengeType.STARTER_COST: case ChallengeType.STARTER_COST:
ret ||= c.applyStarterCost(args[0], args[1]); ret ||= c.applyStarterCost(args[0], args[1]);
break; break;
case ChallengeType.STARTER_MODIFY: case ChallengeType.STARTER_MODIFY:
ret ||= c.applyStarterModify(args[0]); ret ||= c.applyStarterModify(args[0]);
break; break;
case ChallengeType.POKEMON_IN_BATTLE: case ChallengeType.POKEMON_IN_BATTLE:
ret ||= c.applyPokemonInBattle(args[0], args[1]); ret ||= c.applyPokemonInBattle(args[0], args[1]);
break; break;
case ChallengeType.FIXED_BATTLES: case ChallengeType.FIXED_BATTLES:
ret ||= c.applyFixedBattle(args[0], args[1]); ret ||= c.applyFixedBattle(args[0], args[1]);
break; break;
case ChallengeType.TYPE_EFFECTIVENESS: case ChallengeType.TYPE_EFFECTIVENESS:
ret ||= c.applyTypeEffectiveness(args[0]); ret ||= c.applyTypeEffectiveness(args[0]);
break; break;
case ChallengeType.AI_LEVEL: case ChallengeType.AI_LEVEL:
ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]);
break; break;
case ChallengeType.AI_MOVE_SLOTS: case ChallengeType.AI_MOVE_SLOTS:
ret ||= c.applyMoveSlot(args[0], args[1]); ret ||= c.applyMoveSlot(args[0], args[1]);
break; break;
case ChallengeType.PASSIVE_ACCESS: case ChallengeType.PASSIVE_ACCESS:
ret ||= c.applyPassiveAccess(args[0], args[1]); ret ||= c.applyPassiveAccess(args[0], args[1]);
break; break;
case ChallengeType.GAME_MODE_MODIFY: case ChallengeType.GAME_MODE_MODIFY:
ret ||= c.applyGameModeModify(gameMode); ret ||= c.applyGameModeModify(gameMode);
break; break;
case ChallengeType.MOVE_ACCESS: case ChallengeType.MOVE_ACCESS:
ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]);
break; break;
case ChallengeType.MOVE_WEIGHT: case ChallengeType.MOVE_WEIGHT:
ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]);
break; break;
} }
} }
}); });
@ -943,18 +943,18 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
*/ */
export function copyChallenge(source: Challenge | any): Challenge { export function copyChallenge(source: Challenge | any): Challenge {
switch (source.id) { switch (source.id) {
case Challenges.SINGLE_GENERATION: case Challenges.SINGLE_GENERATION:
return SingleGenerationChallenge.loadChallenge(source); return SingleGenerationChallenge.loadChallenge(source);
case Challenges.SINGLE_TYPE: case Challenges.SINGLE_TYPE:
return SingleTypeChallenge.loadChallenge(source); return SingleTypeChallenge.loadChallenge(source);
case Challenges.LOWER_MAX_STARTER_COST: case Challenges.LOWER_MAX_STARTER_COST:
return LowerStarterMaxCostChallenge.loadChallenge(source); return LowerStarterMaxCostChallenge.loadChallenge(source);
case Challenges.LOWER_STARTER_POINTS: case Challenges.LOWER_STARTER_POINTS:
return LowerStarterPointsChallenge.loadChallenge(source); return LowerStarterPointsChallenge.loadChallenge(source);
case Challenges.FRESH_START: case Challenges.FRESH_START:
return FreshStartChallenge.loadChallenge(source); return FreshStartChallenge.loadChallenge(source);
case Challenges.INVERSE_BATTLE: case Challenges.INVERSE_BATTLE:
return InverseBattleChallenge.loadChallenge(source); return InverseBattleChallenge.loadChallenge(source);
} }
throw new Error("Unknown challenge copied"); throw new Error("Unknown challenge copied");
} }

View File

@ -1,18 +1,20 @@
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { Nature } from "#enums/nature";
/** /**
* Data that can customize a Pokemon in non-standard ways from its Species * Data that can customize a Pokemon in non-standard ways from its Species
* Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used * Currently only used by Mystery Encounters and Mints.
*/ */
export class MysteryEncounterPokemonData { export class CustomPokemonData {
public spriteScale: number; public spriteScale: number;
public ability: Abilities | -1; public ability: Abilities | -1;
public passive: Abilities | -1; public passive: Abilities | -1;
public nature: Nature | -1;
public types: Type[]; public types: Type[];
constructor(data?: MysteryEncounterPokemonData | Partial<MysteryEncounterPokemonData>) { constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
if (!isNullOrUndefined(data)) { if (!isNullOrUndefined(data)) {
Object.assign(this, data); Object.assign(this, data);
} }
@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData {
this.spriteScale = this.spriteScale ?? -1; this.spriteScale = this.spriteScale ?? -1;
this.ability = this.ability ?? -1; this.ability = this.ability ?? -1;
this.passive = this.passive ?? -1; this.passive = this.passive ?? -1;
this.nature = this.nature ?? -1;
this.types = this.types ?? []; this.types = this.types ?? [];
} }
} }

View File

@ -262,14 +262,14 @@ export class Egg {
return "Manaphy"; return "Manaphy";
} }
switch (this.tier) { switch (this.tier) {
case EggTier.RARE: case EggTier.RARE:
return i18next.t("egg:greatTier"); return i18next.t("egg:greatTier");
case EggTier.EPIC: case EggTier.EPIC:
return i18next.t("egg:ultraTier"); return i18next.t("egg:ultraTier");
case EggTier.LEGENDARY: case EggTier.LEGENDARY:
return i18next.t("egg:masterTier"); return i18next.t("egg:masterTier");
default: default:
return i18next.t("egg:defaultTier"); return i18next.t("egg:defaultTier");
} }
} }
@ -288,19 +288,19 @@ export class Egg {
public getEggTypeDescriptor(scene: BattleScene): string { public getEggTypeDescriptor(scene: BattleScene): string {
switch (this.sourceType) { switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG: case EggSourceType.SAME_SPECIES_EGG:
return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() });
case EggSourceType.GACHA_LEGENDARY: case EggSourceType.GACHA_LEGENDARY:
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`;
case EggSourceType.GACHA_SHINY: case EggSourceType.GACHA_SHINY:
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove");
case EggSourceType.EVENT: case EggSourceType.EVENT:
return this._eggDescriptor ?? i18next.t("egg:eventType"); return this._eggDescriptor ?? i18next.t("egg:eventType");
default: default:
console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
return ""; return "";
} }
} }
@ -315,14 +315,14 @@ export class Egg {
private rollEggMoveIndex() { private rollEggMoveIndex() {
let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE; let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE;
switch (this._sourceType) { switch (this._sourceType) {
case EggSourceType.SAME_SPECIES_EGG: case EggSourceType.SAME_SPECIES_EGG:
baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE;
break; break;
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE;
break; break;
default: default:
break; break;
} }
const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier); const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier);
@ -335,12 +335,12 @@ export class Egg {
} }
switch (eggTier ?? this._tier) { switch (eggTier ?? this._tier) {
case EggTier.COMMON: case EggTier.COMMON:
return HATCH_WAVES_COMMON_EGG; return HATCH_WAVES_COMMON_EGG;
case EggTier.RARE: case EggTier.RARE:
return HATCH_WAVES_RARE_EGG; return HATCH_WAVES_RARE_EGG;
case EggTier.EPIC: case EggTier.EPIC:
return HATCH_WAVES_EPIC_EGG; return HATCH_WAVES_EPIC_EGG;
} }
return HATCH_WAVES_LEGENDARY_EGG; return HATCH_WAVES_LEGENDARY_EGG;
} }
@ -379,22 +379,22 @@ export class Egg {
let maxStarterValue: integer; let maxStarterValue: integer;
switch (this.tier) { switch (this.tier) {
case EggTier.RARE: case EggTier.RARE:
minStarterValue = 4; minStarterValue = 4;
maxStarterValue = 5; maxStarterValue = 5;
break; break;
case EggTier.EPIC: case EggTier.EPIC:
minStarterValue = 6; minStarterValue = 6;
maxStarterValue = 7; maxStarterValue = 7;
break; break;
case EggTier.LEGENDARY: case EggTier.LEGENDARY:
minStarterValue = 8; minStarterValue = 8;
maxStarterValue = 9; maxStarterValue = 9;
break; break;
default: default:
minStarterValue = 1; minStarterValue = 1;
maxStarterValue = 3; maxStarterValue = 3;
break; break;
} }
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
@ -469,14 +469,14 @@ export class Egg {
private rollShiny(): boolean { private rollShiny(): boolean {
let shinyChance = GACHA_DEFAULT_SHINY_RATE; let shinyChance = GACHA_DEFAULT_SHINY_RATE;
switch (this._sourceType) { switch (this._sourceType) {
case EggSourceType.GACHA_SHINY: case EggSourceType.GACHA_SHINY:
shinyChance = GACHA_SHINY_UP_SHINY_RATE; shinyChance = GACHA_SHINY_UP_SHINY_RATE;
break; break;
case EggSourceType.SAME_SPECIES_EGG: case EggSourceType.SAME_SPECIES_EGG:
shinyChance = SAME_SPECIES_EGG_SHINY_RATE; shinyChance = SAME_SPECIES_EGG_SHINY_RATE;
break; break;
default: default:
break; break;
} }
return !Utils.randSeedInt(shinyChance); return !Utils.randSeedInt(shinyChance);
@ -523,15 +523,15 @@ export class Egg {
return; return;
} }
switch (this.tier) { switch (this.tier) {
case EggTier.RARE: case EggTier.RARE:
scene.gameData.gameStats.rareEggsPulled++; scene.gameData.gameStats.rareEggsPulled++;
break; break;
case EggTier.EPIC: case EggTier.EPIC:
scene.gameData.gameStats.epicEggsPulled++; scene.gameData.gameStats.epicEggsPulled++;
break; break;
case EggTier.LEGENDARY: case EggTier.LEGENDARY:
scene.gameData.gameStats.legendaryEggsPulled++; scene.gameData.gameStats.legendaryEggsPulled++;
break; break;
} }
} }

View File

@ -28,24 +28,24 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege
let ret: integer; let ret: integer;
switch (growthRate) { switch (growthRate) {
case GrowthRate.ERRATIC: case GrowthRate.ERRATIC:
ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500;
break; break;
case GrowthRate.FAST: case GrowthRate.FAST:
ret = Math.pow(level, 3) * 4 / 5; ret = Math.pow(level, 3) * 4 / 5;
break; break;
case GrowthRate.MEDIUM_FAST: case GrowthRate.MEDIUM_FAST:
ret = Math.pow(level, 3); ret = Math.pow(level, 3);
break; break;
case GrowthRate.MEDIUM_SLOW: case GrowthRate.MEDIUM_SLOW:
ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140;
break; break;
case GrowthRate.SLOW: case GrowthRate.SLOW:
ret = Math.pow(level, 3) * 5 / 4; ret = Math.pow(level, 3) * 5 / 4;
break; break;
case GrowthRate.FLUCTUATING: case GrowthRate.FLUCTUATING:
ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level);
break; break;
} }
if (growthRate !== GrowthRate.MEDIUM_FAST) { if (growthRate !== GrowthRate.MEDIUM_FAST) {
@ -61,17 +61,17 @@ export function getLevelRelExp(level: integer, growthRate: GrowthRate): number {
export function getGrowthRateColor(growthRate: GrowthRate, shadow?: boolean) { export function getGrowthRateColor(growthRate: GrowthRate, shadow?: boolean) {
switch (growthRate) { switch (growthRate) {
case GrowthRate.ERRATIC: case GrowthRate.ERRATIC:
return !shadow ? "#f85888" : "#906060"; return !shadow ? "#f85888" : "#906060";
case GrowthRate.FAST: case GrowthRate.FAST:
return !shadow ? "#f8d030" : "#b8a038"; return !shadow ? "#f8d030" : "#b8a038";
case GrowthRate.MEDIUM_FAST: case GrowthRate.MEDIUM_FAST:
return !shadow ? "#78c850" : "#588040"; return !shadow ? "#78c850" : "#588040";
case GrowthRate.MEDIUM_SLOW: case GrowthRate.MEDIUM_SLOW:
return !shadow ? "#6890f0" : "#807870"; return !shadow ? "#6890f0" : "#807870";
case GrowthRate.SLOW: case GrowthRate.SLOW:
return !shadow ? "#f08030" : "#c03028"; return !shadow ? "#f08030" : "#c03028";
case GrowthRate.FLUCTUATING: case GrowthRate.FLUCTUATING:
return !shadow ? "#a040a0" : "#483850"; return !shadow ? "#a040a0" : "#483850";
} }
} }

View File

@ -6,20 +6,20 @@ export enum Gender {
export function getGenderSymbol(gender: Gender) { export function getGenderSymbol(gender: Gender) {
switch (gender) { switch (gender) {
case Gender.MALE: case Gender.MALE:
return "♂"; return "♂";
case Gender.FEMALE: case Gender.FEMALE:
return "♀"; return "♀";
} }
return ""; return "";
} }
export function getGenderColor(gender: Gender, shadow?: boolean) { export function getGenderColor(gender: Gender, shadow?: boolean) {
switch (gender) { switch (gender) {
case Gender.MALE: case Gender.MALE:
return shadow ? "#006090" : "#40c8f8"; return shadow ? "#006090" : "#40c8f8";
case Gender.FEMALE: case Gender.FEMALE:
return shadow ? "#984038" : "#f89890"; return shadow ? "#984038" : "#f89890";
} }
return "#ffffff"; return "#ffffff";
} }

File diff suppressed because it is too large Load Diff

View File

@ -44,32 +44,32 @@ export const ATrainersTestEncounter: MysteryEncounter =
let spriteKeys; let spriteKeys;
let trainerNameKey: string; let trainerNameKey: string;
switch (randSeedInt(5)) { switch (randSeedInt(5)) {
default: default:
case 0: case 0:
trainerType = TrainerType.BUCK; trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL); spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck"; trainerNameKey = "buck";
break; break;
case 1: case 1:
trainerType = TrainerType.CHERYL; trainerType = TrainerType.CHERYL;
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY); spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
trainerNameKey = "cheryl"; trainerNameKey = "cheryl";
break; break;
case 2: case 2:
trainerType = TrainerType.MARLEY; trainerType = TrainerType.MARLEY;
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE); spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
trainerNameKey = "marley"; trainerNameKey = "marley";
break; break;
case 3: case 3:
trainerType = TrainerType.MIRA; trainerType = TrainerType.MIRA;
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1); spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
trainerNameKey = "mira"; trainerNameKey = "mira";
break; break;
case 4: case 4:
trainerType = TrainerType.RILEY; trainerType = TrainerType.RILEY;
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1); spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
trainerNameKey = "riley"; trainerNameKey = "riley";
break; break;
} }
// Dialogue and tokens for trainer // Dialogue and tokens for trainer

View File

@ -1,4 +1,4 @@
import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -14,6 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/anOfferYouCantRefuse"; const namespace = "mysteryEncounters/anOfferYouCantRefuse";
@ -98,6 +99,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
} }
} }
const shinyCharm = generateModifierType(scene, modifierTypes.SHINY_CHARM);
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName()); encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName());
return true; return true;
@ -123,7 +126,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
return true; return true;
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Give the player a Shiny charm // Give the player a Shiny Charm
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
}) })
@ -132,9 +135,11 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES), CombinationPokemonRequirement.Some(
new AbilityRequirement(EXTORTION_ABILITIES)) new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true)
)
) )
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,

View File

@ -42,6 +42,8 @@ import {
AttackTypeBoosterModifier, AttackTypeBoosterModifier,
BypassSpeedChanceModifier, BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier, ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier,
MegaEvolutionAccessModifier,
PokemonHeldItemModifier PokemonHeldItemModifier
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
@ -191,12 +193,14 @@ const WAVE_LEVEL_BREAKPOINTS = [ 30, 50, 70, 100, 120, 140, 160 ];
export const BugTypeSuperfanEncounter: MysteryEncounter = export const BugTypeSuperfanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team CombinationPokemonRequirement.Some(
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1), new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
new TypeRequirement(Type.BUG, false, 1) new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
)) new TypeRequirement(Type.BUG, false, 1)
)
)
.withMaxAllowedEncounters(1) .withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
@ -356,10 +360,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
}, },
]; ];
} else { } else {
// If player has any evolution/form change items that are valid for their party, will spawn one of those items in addition to a Master Ball // If the player has any evolution/form change items that are valid for their party,
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ]; // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ];
const specialOptions: ModifierTypeOption[] = []; const specialOptions: ModifierTypeOption[] = [];
if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!);
}
if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!);
}
const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM); const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM);
if (nonRareEvolutionModifier) { if (nonRareEvolutionModifier) {
specialOptions.push(nonRareEvolutionModifier); specialOptions.push(nonRareEvolutionModifier);
@ -396,11 +407,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.build()) .build())
.withOption(MysteryEncounterOptionBuilder .withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
// Meets one or both of the below reqs CombinationPokemonRequirement.Some(
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), // Meets one or both of the below reqs
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1) new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
)) new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1)
)
)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -11,7 +11,7 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move"; import { MoveCategory } from "#app/data/move";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
@ -133,7 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
}, },
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON), species: getPokemonSpecies(Species.BLACEPHALON),
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), customPokemonData: new CustomPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}),
isBoss: true, isBoss: true,
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
}, },
@ -353,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
newTypes.push(secondType); newTypes.push(secondType);
// Apply the type changes (to both base and fusion, if pokemon is fused) // Apply the type changes (to both base and fusion, if pokemon is fused)
if (!pokemon.mysteryEncounterPokemonData) { if (!pokemon.customPokemonData) {
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); pokemon.customPokemonData = new CustomPokemonData();
} }
pokemon.mysteryEncounterPokemonData.types = newTypes; pokemon.customPokemonData.types = newTypes;
if (pokemon.isFusion()) { if (pokemon.isFusion()) {
if (!pokemon.fusionMysteryEncounterPokemonData) { if (!pokemon.fusionCustomPokemonData) {
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); pokemon.fusionCustomPokemonData = new CustomPokemonData();
} }
pokemon.fusionMysteryEncounterPokemonData.types = newTypes; pokemon.fusionCustomPokemonData.types = newTypes;
} }
} }
}) })
@ -425,17 +425,8 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Do ability swap // Do ability swap
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
if (pokemon.isFusion()) {
if (!pokemon.fusionMysteryEncounterPokemonData) { applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability);
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
}
pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability;
} else {
if (!pokemon.mysteryEncounterPokemonData) {
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
}
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
}
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
}; };

View File

@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -14,6 +14,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounters/darkDeal"; const namespace = "mysteryEncounters/darkDeal";
@ -141,6 +142,7 @@ export const DarkDealEncounter: MysteryEncounter =
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle // Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, true, false, true); const removedPokemon = getRandomPlayerPokemon(scene, true, false, true);
// Get all the pokemon's held items // Get all the pokemon's held items
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
scene.removePokemonFromPlayerParty(removedPokemon); scene.removePokemonFromPlayerParty(removedPokemon);
@ -160,7 +162,13 @@ export const DarkDealEncounter: MysteryEncounter =
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
// Start encounter with random legendary (7-10 starter strength) that has level additive // Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes: Type[] = encounter.misc.removedTypes; // If this is a mono-type challenge, always ensure the required type is filtered for
let bossTypes: Type[] = encounter.misc.removedTypes;
const singleTypeChallenges = scene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
if (scene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type);
}
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100); const roll = randSeedInt(100);
@ -172,7 +180,8 @@ export const DarkDealEncounter: MysteryEncounter =
isBoss: true, isBoss: true,
modifierConfigs: bossModifiers.map(m => { modifierConfigs: bossModifiers.map(m => {
return { return {
modifier: m modifier: m,
stackCount: m.getStackCount(),
}; };
}) })
}; };

View File

@ -45,10 +45,13 @@ export const DelibirdyEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least .withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn .withPrimaryPokemonRequirement(
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS), CombinationPokemonRequirement.Some(
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true) // Must also have either option 2 or 3 available to spawn
)) new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
)
)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
spriteKey: "", spriteKey: "",
@ -196,7 +199,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const modifier: BerryModifier | HealingBoosterModifier = encounter.misc.chosenModifier; const modifier: BerryModifier | HealingBoosterModifier = encounter.misc.chosenModifier;
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
if (modifier instanceof BerryModifier) { if (modifier instanceof BerryModifier) {
// Check if the player has max stacks of that Candy Jar already // Check if the player has max stacks of that Candy Jar already
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier; const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
@ -211,8 +214,8 @@ export const DelibirdyEncounter: MysteryEncounter =
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
} }
} else { } else {
// Check if the player has max stacks of that Healing Charm already // Check if the player has max stacks of that Berry Pouch already
const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // At max stacks, give the first party pokemon a Shell Bell instead
@ -221,7 +224,7 @@ export const DelibirdyEncounter: MysteryEncounter =
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
} else { } else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
} }
} }
@ -290,8 +293,8 @@ export const DelibirdyEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier; const modifier = encounter.misc.chosenModifier;
// Check if the player has max stacks of Berry Pouch already // Check if the player has max stacks of Healing Charm already
const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // At max stacks, give the first party pokemon a Shell Bell instead
@ -300,7 +303,7 @@ export const DelibirdyEncounter: MysteryEncounter =
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
} else { } else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
} }
// Remove the modifier if its stacks go to 0 // Remove the modifier if its stacks go to 0

View File

@ -4,24 +4,30 @@ import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/mod
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon"; import Pokemon, { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#app/data/weather"; import { WeatherType } from "#app/data/weather";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#app/data/status-effect"; import { StatusEffect } from "#app/data/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { Ability } from "#app/data/ability";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieryFallout"; const namespace = "mysteryEncounters/fieryFallout";
@ -62,16 +68,24 @@ export const FieryFalloutEncounter: MysteryEncounter =
{ {
species: volcaronaSpecies, species: volcaronaSpecies,
isBoss: false, isBoss: false,
gender: Gender.MALE gender: Gender.MALE,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
}, },
{ {
species: volcaronaSpecies, species: volcaronaSpecies,
isBoss: false, isBoss: false,
gender: Gender.FEMALE gender: Gender.FEMALE,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
} }
], ],
doubleBattle: true, doubleBattle: true,
disableSwitch: true disableSwitch: true,
}; };
encounter.enemyPartyConfigs = [ config ]; encounter.enemyPartyConfigs = [ config ];
@ -139,7 +153,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Pick battle // Pick battle
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene)); setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem(scene));
encounter.startOfBattleEffects.push( encounter.startOfBattleEffects.push(
{ {
@ -153,18 +167,6 @@ export const FieryFalloutEncounter: MysteryEncounter =
targets: [ BattlerIndex.PLAYER_2 ], targets: [ BattlerIndex.PLAYER_2 ],
move: new PokemonMove(Moves.FIRE_SPIN), move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [ BattlerIndex.ENEMY ],
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [ BattlerIndex.ENEMY_2 ],
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
}); });
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
} }
@ -180,7 +182,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
], ],
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Damage non-fire types and burn 1 random non-fire type member // Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE)); const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
@ -198,7 +200,11 @@ export const FieryFalloutEncounter: MysteryEncounter =
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied // Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name);
queueEncounterMessage(scene, `${namespace}:option.2.target_burned`); queueEncounterMessage(scene, `${namespace}:option.2.target_burned`);
// Also permanently change the burned Pokemon's ability to Heatproof
applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF);
} }
} }
@ -209,8 +215,12 @@ export const FieryFalloutEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically .withPrimaryPokemonRequirement(
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically CombinationPokemonRequirement.Some(
new TypeRequirement(Type.FIRE, true, 1),
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true)
)
) // Will set option3PrimaryName dialogue token automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
@ -233,26 +243,32 @@ export const FieryFalloutEncounter: MysteryEncounter =
{ fillRemaining: true }, { fillRemaining: true },
undefined, undefined,
() => { () => {
giveLeadPokemonCharcoal(scene); giveLeadPokemonAttackTypeBoostItem(scene);
}); });
const primary = encounter.options[2].primaryPokemon!; const primary = encounter.options[2].primaryPokemon!;
const secondary = encounter.options[2].secondaryPokemon![0];
setEncounterExp(scene, [ primary.id, secondary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); setEncounterExp(scene, [ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
}) })
.build() .build()
) )
.build(); .build();
function giveLeadPokemonCharcoal(scene: BattleScene) { function giveLeadPokemonAttackTypeBoostItem(scene: BattleScene) {
// Give first party pokemon Charcoal for free at end of battle // Give first party pokemon attack type boost item for free at end of battle
const leadPokemon = scene.getParty()?.[0]; const leadPokemon = scene.getParty()?.[0];
if (leadPokemon) { if (leadPokemon) {
const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; // Generate type booster held item, default to Charcoal if item fails to generate
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); let boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); if (!boosterModifierType) {
queueEncounterMessage(scene, `${namespace}:found_charcoal`); boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType;
}
applyModifierTypeToPlayerPokemon(scene, leadPokemon, boosterModifierType);
const encounter = scene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("itemName", boosterModifierType.name);
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}:found_item`);
} }
} }

View File

@ -145,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,

View File

@ -56,7 +56,13 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config // Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100 // Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
const hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); let retries = 0;
let hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
while (retries < 5 && hardTrainerType === normalTrainerType) {
// Will try to use a different trainer from the normal trainer type
hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
retries++;
}
const hardTemplate = new TrainerPartyCompoundTemplate( const hardTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true), new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
new TrainerPartyTemplate( new TrainerPartyTemplate(

View File

@ -227,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -21,7 +21,7 @@ import i18next from "i18next";
const namespace = "mysteryEncounters/shadyVitaminDealer"; const namespace = "mysteryEncounters/shadyVitaminDealer";
const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5; const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5;
const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5; const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5;
/** /**
* Shady Vitamin Dealer encounter. * Shady Vitamin Dealer encounter.
@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
newNature = randSeedInt(25) as Nature; newNature = randSeedInt(25) as Nature;
} }
chosenPokemon.nature = newNature; chosenPokemon.customPokemonData.nature = newNature;
encounter.setDialogueToken("newNature", getNatureName(newNature)); encounter.setDialogueToken("newNature", getNatureName(newNature));
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100); setEncounterExp(scene, [ chosenPokemon.id ], 100);

View File

@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounters/slumberingSnorlax"; const namespace = "mysteryEncounters/slumberingSnorlax";
@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
stackCount: 2 stackCount: 2
}, },
], ],
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
}; };
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
@ -143,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -25,6 +25,7 @@ import { achvs } from "#app/system/achv";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theExpertPokemonBreeder"; const namespace = "mysteryEncounters/theExpertPokemonBreeder";
@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
if (pokemon2CommonEggs > 0) { if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText); encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
} }
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
@ -221,7 +222,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon1; encounter.misc.chosenPokemon = pokemon1;
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions); setEncounterRewards(scene,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
@ -247,9 +251,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.onGameOver = onGameOver; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config); await initBattleWithEnemyConfig(scene, config);
}) })
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
})
.build() .build()
) )
.withOption( .withOption(
@ -273,7 +274,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon2; encounter.misc.chosenPokemon = pokemon2;
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions); setEncounterRewards(scene,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
@ -299,9 +303,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.onGameOver = onGameOver; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config); await initBattleWithEnemyConfig(scene, config);
}) })
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
})
.build() .build()
) )
.withOption( .withOption(
@ -325,7 +326,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon3; encounter.misc.chosenPokemon = pokemon3;
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions); setEncounterRewards(scene,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
@ -351,9 +355,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.onGameOver = onGameOver; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config); await initBattleWithEnemyConfig(scene, config);
}) })
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
})
.build() .build()
) )
.withOutroDialogue([ .withOutroDialogue([
@ -462,12 +463,16 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
} }
// Maximum of 30 points // Maximum of 30 points
const totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); let totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30);
// 1 Rare egg for every 6 points // First 5 points go to Common eggs
const numRares = Math.floor(totalPoints / 6); let numCommons = Math.min(totalPoints, 5);
totalPoints -= numCommons;
// Then, 1 Rare egg for every 4 points
const numRares = Math.floor(totalPoints / 4);
// 1 Common egg for every point leftover // 1 Common egg for every point leftover
const numCommons = totalPoints % 6; numCommons += totalPoints % 4;
return [ numCommons, numRares ]; return [ numCommons, numRares ];
} }
@ -521,19 +526,19 @@ function checkAchievement(scene: BattleScene) {
} }
} }
async function restorePartyAndHeldItems(scene: BattleScene) { function restorePartyAndHeldItems(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
// Restore original party // Restore original party
scene.getParty().push(...encounter.misc.originalParty); scene.getParty().push(...encounter.misc.originalParty);
// Restore held items // Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems; const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach(pokemonHeldItemsList => { originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
pokemonHeldItemsList.forEach(heldItem => { pokemonHeldItemsList.forEach(heldItem => {
scene.addModifier(heldItem, true, false, false, true); scene.addModifier(heldItem, true, false, false, true);
}); });
}); });
await scene.updateModifiers(true); scene.updateModifiers(true);
} }
function onGameOver(scene: BattleScene) { function onGameOver(scene: BattleScene) {
@ -609,13 +614,13 @@ function onGameOver(scene: BattleScene) {
return false; return false;
} }
async function doPostEncounterCleanup(scene: BattleScene) { function doPostEncounterCleanup(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
if (!encounter.misc.encounterFailed) { if (!encounter.misc.encounterFailed) {
// Give achievement if in Space biome // Give achievement if in Space biome
checkAchievement(scene); checkAchievement(scene);
// Give 20 friendship to the chosen pokemon // Give 20 friendship to the chosen pokemon
encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED);
await restorePartyAndHeldItems(scene); restorePartyAndHeldItems(scene);
} }
} }

View File

@ -14,7 +14,7 @@ import { BattlerIndex } from "#app/battle";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.SHUCKLE), species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true, isBoss: true,
bossSegments: 5, bossSegments: 5,
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
nature: Nature.BOLD, nature: Nature.BOLD,
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
modifierConfigs: [ modifierConfigs: [

View File

@ -37,6 +37,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
.withFleeAllowed(false) .withFleeAllowed(false)
.withHideWildIntroMessage(true) .withHideWildIntroMessage(true)
.withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
spriteKey: "training_session_gear", spriteKey: "training_session_gear",

View File

@ -71,7 +71,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ] moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
}; };
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
levelAdditiveModifier: 1, levelAdditiveModifier: 0.5,
pokemonConfigs: [ pokemonConfig ], pokemonConfigs: [ pokemonConfig ],
disableSwitch: true disableSwitch: true
}; };

View File

@ -210,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -4,23 +4,30 @@ import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config";
import { PartyMemberStrength } from "#enums/party-member-strength";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounters/weirdDream"; const namespace = "mysteryEncounters/weirdDream";
@ -80,10 +87,11 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [
const SUPER_LEGENDARY_BST_THRESHOLD = 600; const SUPER_LEGENDARY_BST_THRESHOLD = 600;
const NON_LEGENDARY_BST_THRESHOLD = 570; const NON_LEGENDARY_BST_THRESHOLD = 570;
const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450;
const OLD_GATEAU_STATS_UP = 20;
/** 0-100 */ /** 0-100 */
const PERCENT_LEVEL_LOSS_ON_REFUSE = 12.5; const PERCENT_LEVEL_LOSS_ON_REFUSE = 10;
/** /**
* Value ranges of the resulting species BST transformations after adding values to original species * Value ranges of the resulting species BST transformations after adding values to original species
@ -105,7 +113,8 @@ export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) // TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now.
.withSceneWaveRangeRequirement(30, 140)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
spriteKey: "weird_dream_woman", spriteKey: "weird_dream_woman",
@ -131,6 +140,15 @@ export const WeirdDreamEncounter: MysteryEncounter =
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
// Calculate all the newly transformed Pokemon and begin asset load
const teamTransformations = getTeamTransformations(scene);
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
scene.currentBattle.mysteryEncounter!.misc = {
teamTransformations,
loadAssets
};
return true; return true;
}) })
.withOnVisualsStart((scene: BattleScene) => { .withOnVisualsStart((scene: BattleScene) => {
@ -156,13 +174,10 @@ export const WeirdDreamEncounter: MysteryEncounter =
doShowDreamBackground(scene); doShowDreamBackground(scene);
}); });
// Calculate all the newly transformed Pokemon and begin asset load for (const transformation of scene.currentBattle.mysteryEncounter!.misc.teamTransformations) {
const teamTransformations = getTeamTransformations(scene); scene.removePokemonFromPlayerParty(transformation.previousPokemon, false);
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); scene.getParty().push(transformation.newPokemon);
scene.currentBattle.mysteryEncounter!.misc = { }
teamTransformations,
loadAssets
};
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue // Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
@ -193,7 +208,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
await showEncounterText(scene, `${namespace}:option.1.dream_complete`); await showEncounterText(scene, `${namespace}:option.1.dream_complete`);
await doNewTeamPostProcess(scene, transformations); await doNewTeamPostProcess(scene, transformations);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT ]}); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false });
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
}) })
.build() .build()
@ -209,7 +224,88 @@ export const WeirdDreamEncounter: MysteryEncounter =
], ],
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Reduce party levels by 20% // Battle your "future" team for some item rewards
const transformations: PokemonTransformation[] = scene.currentBattle.mysteryEncounter!.misc.teamTransformations;
// Uses the pokemon that player's party would have transformed into
const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
for (const transformation of transformations) {
const newPokemon = transformation.newPokemon;
const previousPokemon = transformation.previousPokemon;
await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true);
const dataSource = new PokemonData(newPokemon);
dataSource.player = false;
// Copy held items to new pokemon
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
for (const item of transformation.heldItems) {
newPokemonHeldItemConfigs.push({
modifier: item.clone() as PokemonHeldItemModifier,
stackCount: item.getStackCount(),
isTransferable: false
});
}
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
newPokemonHeldItemConfigs.push({
modifier: generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
});
}
const enemyConfig: EnemyPokemonConfig = {
species: transformation.newSpecies,
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
level: previousPokemon.level,
dataSource: dataSource,
modifierConfigs: newPokemonHeldItemConfigs
};
enemyPokemonConfigs.push(enemyConfig);
}
const genderIndex = scene.gameData.gender ?? PlayerGender.UNSET;
const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone();
trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
const enemyPartyConfig: EnemyPartyConfig = {
trainerConfig: trainerConfig,
pokemonConfigs: enemyPokemonConfigs,
female: genderIndex === PlayerGender.FEMALE
};
const onBeforeRewards = () => {
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
enablePassiveMon.updateInfo(true);
}
};
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards);
await showEncounterText(scene, `${namespace}:option.2.selected_2`, null, undefined, true);
await initBattleWithEnemyConfig(scene, enemyPartyConfig);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave, reduce party levels by 10%
for (const pokemon of scene.getParty()) { for (const pokemon of scene.getParty()) {
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1); pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
@ -235,7 +331,7 @@ interface PokemonTransformation {
function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
const party = scene.getParty(); const party = scene.getParty();
// Removes all pokemon from the party // Removes all pokemon from the party
const alreadyUsedSpecies: PokemonSpecies[] = []; const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species);
const pokemonTransformations: PokemonTransformation[] = party.map(p => { const pokemonTransformations: PokemonTransformation[] = party.map(p => {
return { return {
previousPokemon: p previousPokemon: p
@ -250,11 +346,11 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
// First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference // First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference
// Then, roll the remainder of the party members at a +40 to +50 BST difference // Then, roll the remainder of the party members at a +40 to +50 BST difference
const numPokemon = party.length; const numPokemon = party.length;
const removedPokemon = randSeedShuffle(party.slice(0));
for (let i = 0; i < numPokemon; i++) { for (let i = 0; i < numPokemon; i++) {
const removed = party[randSeedInt(party.length)]; const removed = removedPokemon[i];
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
scene.removePokemonFromPlayerParty(removed, false);
const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0); const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0);
let newBstRange: [number, number]; let newBstRange: [number, number];
@ -276,14 +372,13 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
pokemonTransformations[index].newSpecies = newSpecies; pokemonTransformations[index].newSpecies = newSpecies;
console.log("New species: " + JSON.stringify(newSpecies));
alreadyUsedSpecies.push(newSpecies); alreadyUsedSpecies.push(newSpecies);
} }
for (const transformation of pokemonTransformations) { for (const transformation of pokemonTransformations) {
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount()); const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); transformation.newPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
transformation.newPokemon = newPlayerPokemon;
scene.getParty().push(newPlayerPokemon);
} }
return pokemonTransformations; return pokemonTransformations;
@ -296,109 +391,20 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
const newPokemon = transformation.newPokemon; const newPokemon = transformation.newPokemon;
const speciesRootForm = newPokemon.species.getRootSpeciesId(); const speciesRootForm = newPokemon.species.getRootSpeciesId();
// Roll HA a second time if (await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, speciesRootForm)) {
if (newPokemon.species.abilityHidden) { atLeastOneNewStarter = true;
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
if (newPokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) {
newPokemon.abilityIndex = hiddenIndex;
}
}
} }
// Roll IVs a second time // Copy old items to new pokemon
newPokemon.ivs = newPokemon.ivs.map(iv => {
const newValue = randSeedInt(31);
return newValue > iv ? newValue : iv;
});
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny()) {
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (newPokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (newPokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (newPokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
atLeastOneNewStarter = true;
await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
}
}
// If the previous pokemon had pokerus, transfer to new pokemon
newPokemon.pokerus = previousPokemon.pokerus;
// Transfer previous Pokemon's luck value
newPokemon.luck = previousPokemon.getLuck();
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
});
// For pokemon that the player owns (including ones just caught), gain a candy
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
}
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species
newPokemon.generateAndPopulateMoveset();
// Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move
const newPokemonGeneratedMoveset = newPokemon.moveset;
newPokemon.moveset = previousPokemon.moveset;
const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm);
// Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset)
addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex);
// Randomize the second type of the pokemon
// If the pokemon does not normally have a second type, it will gain 1
const newTypes = [ newPokemon.getTypes()[0] ];
let newType = randSeedInt(18) as Type;
while (newType === newTypes[0]) {
newType = randSeedInt(18) as Type;
}
newTypes.push(newType);
if (!newPokemon.mysteryEncounterPokemonData) {
newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
}
newPokemon.mysteryEncounterPokemonData.types = newTypes;
for (const item of transformation.heldItems) { for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id; item.pokemonId = newPokemon.id;
await scene.addModifier(item, false, false, false, true); await scene.addModifier(item, false, false, false, true);
} }
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef if (shouldGetOldGateau(newPokemon)) {
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD) { const stats = getOldGateauBoostedStats(newPokemon);
const stats: Stat[] = [ Stat.HP ];
const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0);
// Attack or SpAtk
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
// Def or SpDef
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
.generateType(scene.getParty(), [ 20, stats ]) .generateType(scene.getParty(), [ OLD_GATEAU_STATS_UP, stats ])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon); const modifier = modType?.newModifier(newPokemon);
if (modifier) { if (modifier) {
@ -406,9 +412,6 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
} }
} }
// Enable passive if previous had it
newPokemon.passive = previousPokemon.passive;
newPokemon.calculateStats(); newPokemon.calculateStats();
await newPokemon.updateInfo(); await newPokemon.updateInfo();
} }
@ -427,6 +430,138 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
} }
} }
/**
* Applies special changes to the newly transformed pokemon, such as passing previous moves, gaining egg moves, etc.
* Returns whether the transformed pokemon unlocks a new starter for the player.
* @param scene
* @param previousPokemon
* @param newPokemon
* @param speciesRootForm
* @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player.
*/
async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<boolean> {
let isNewStarter = false;
// Roll HA a second time
if (newPokemon.species.abilityHidden) {
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
if (newPokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) {
newPokemon.abilityIndex = hiddenIndex;
}
}
}
// Roll IVs a second time
newPokemon.ivs = newPokemon.ivs.map(iv => {
const newValue = randSeedInt(31);
return newValue > iv ? newValue : iv;
});
// Roll a neutral nature
newPokemon.nature = [ Nature.HARDY, Nature.DOCILE, Nature.BASHFUL, Nature.QUIRKY, Nature.SERIOUS ][randSeedInt(5)];
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) {
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (newPokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (newPokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (newPokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
isNewStarter = true;
await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
}
}
// If the previous pokemon had pokerus, transfer to new pokemon
newPokemon.pokerus = previousPokemon.pokerus;
// Transfer previous Pokemon's luck value
newPokemon.luck = previousPokemon.getLuck();
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
});
// For pokemon that the player owns (including ones just caught), gain a candy
if (!forBattle && !!scene.gameData.dexData[speciesRootForm].caughtAttr) {
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
}
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species
newPokemon.generateAndPopulateMoveset();
// Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move
const newPokemonGeneratedMoveset = newPokemon.moveset;
newPokemon.moveset = previousPokemon.moveset.slice(0);
const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm, forBattle);
// Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset)
addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex);
// Randomize the second type of the pokemon
// If the pokemon does not normally have a second type, it will gain 1
const newTypes = [ newPokemon.getTypes()[0] ];
let newType = randSeedInt(18) as Type;
while (newType === newTypes[0]) {
newType = randSeedInt(18) as Type;
}
newTypes.push(newType);
if (!newPokemon.customPokemonData) {
newPokemon.customPokemonData = new CustomPokemonData();
}
newPokemon.customPokemonData.types = newTypes;
// Enable passive if previous had it
newPokemon.passive = previousPokemon.passive;
return isNewStarter;
}
/**
* @returns `true` if a given Pokemon has valid BST to be given an Old Gateau
*/
function shouldGetOldGateau(pokemon: Pokemon): boolean {
return pokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD;
}
/**
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
* @returns Array of 3 {@linkcode Stat}s to boost
*/
function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
const stats: Stat[] = [];
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
// HP or Speed
stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD);
// Attack or SpAtk
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
// Def or SpDef
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
return stats;
}
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
let newSpecies: PokemonSpecies | undefined; let newSpecies: PokemonSpecies | undefined;
while (isNullOrUndefined(newSpecies)) { while (isNullOrUndefined(newSpecies)) {
@ -550,7 +685,7 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo
* @param newPokemon * @param newPokemon
* @param speciesRootForm * @param speciesRootForm
*/ */
async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise<number | null> { async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<number | null> {
let eggMoveIndex: null | number = null; let eggMoveIndex: null | number = null;
const eggMoves = newPokemon.getEggMoves()?.slice(0); const eggMoves = newPokemon.getEggMoves()?.slice(0);
if (eggMoves) { if (eggMoves) {
@ -576,7 +711,7 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla
} }
// For pokemon that the player owns (including ones just caught), unlock the egg move // For pokemon that the player owns (including ones just caught), unlock the egg move
if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) {
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
} }
} }

View File

@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { allAbilities } from "#app/data/ability";
export interface EncounterRequirement { export interface EncounterRequirement {
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
@ -36,31 +37,58 @@ export abstract class EncounterSceneRequirement implements EncounterRequirement
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
} }
/**
* Combination of multiple {@linkcode EncounterSceneRequirement | EncounterSceneRequirements} (OR/AND possible. See {@linkcode isAnd})
*/
export class CombinationSceneRequirement extends EncounterSceneRequirement { export class CombinationSceneRequirement extends EncounterSceneRequirement {
orRequirements: EncounterSceneRequirement[]; /** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
private isAnd: boolean;
requirements: EncounterSceneRequirement[];
constructor(... orRequirements: EncounterSceneRequirement[]) { public static Some(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
return new CombinationSceneRequirement(false, ...requirements);
}
public static Every(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
return new CombinationSceneRequirement(true, ...requirements);
}
private constructor(isAnd: boolean, ...requirements: EncounterSceneRequirement[]) {
super(); super();
this.orRequirements = orRequirements; this.isAnd = isAnd;
this.requirements = requirements;
} }
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean { override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) { return this.isAnd
if (req.meetsRequirement(scene)) { ? this.requirements.every(req => req.meetsRequirement(scene))
return true; : this.requirements.some(req => req.meetsRequirement(scene));
}
}
return false;
} }
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) { if (this.isAnd) {
if (req.meetsRequirement(scene)) { throw new Error("Not implemented (Sorry)");
return req.getDialogueToken(scene, pokemon); } else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
} }
}
return this.orRequirements[0].getDialogueToken(scene, pokemon); return this.requirements[0].getDialogueToken(scene, pokemon);
}
} }
} }
@ -89,44 +117,74 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
} }
/**
* Combination of multiple {@linkcode EncounterPokemonRequirement | EncounterPokemonRequirements} (OR/AND possible. See {@linkcode isAnd})
*/
export class CombinationPokemonRequirement extends EncounterPokemonRequirement { export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
orRequirements: EncounterPokemonRequirement[]; /** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
private isAnd: boolean;
private requirements: EncounterPokemonRequirement[];
constructor(...orRequirements: EncounterPokemonRequirement[]) { public static Some(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
return new CombinationPokemonRequirement(false, ...requirements);
}
public static Every(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
return new CombinationPokemonRequirement(true, ...requirements);
}
private constructor(isAnd: boolean, ...requirements: EncounterPokemonRequirement[]) {
super(); super();
this.isAnd = isAnd;
this.invertQuery = false; this.invertQuery = false;
this.minNumberOfPokemon = 1; this.minNumberOfPokemon = 1;
this.orRequirements = orRequirements; this.requirements = requirements;
} }
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean { override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) { return this.isAnd
if (req.meetsRequirement(scene)) { ? this.requirements.every(req => req.meetsRequirement(scene))
return true; : this.requirements.some(req => req.meetsRequirement(scene));
}
}
return false;
} }
/**
* Queries the players party for all party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
* @param partyPokemon The party of {@linkcode PlayerPokemon}
* @returns All party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
*/
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) { if (this.isAnd) {
const result = req.queryParty(partyPokemon); return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
if (result?.length > 0) { } else {
return result; const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0);
} return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : [];
} }
return [];
} }
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) { if (this.isAnd) {
if (req.meetsRequirement(scene)) { throw new Error("Not implemented (Sorry)");
return req.getDialogueToken(scene, pokemon); } else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
} }
}
return this.orRequirements[0].getDialogueToken(scene, pokemon); return this.requirements[0].getDialogueToken(scene, pokemon);
}
} }
} }
@ -476,9 +534,11 @@ export class MoveRequirement extends EncounterPokemonRequirement {
requiredMoves: Moves[] = []; requiredMoves: Moves[] = [];
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
excludeDisallowedPokemon: boolean;
constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
super(); super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [ moves ]; this.requiredMoves = Array.isArray(moves) ? moves : [ moves ];
@ -494,10 +554,15 @@ export class MoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0); // get the Pokemon with at least one move in the required moves list
return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
} else { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves // for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0); return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
} }
} }
@ -559,9 +624,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
requiredAbilities: Abilities[]; requiredAbilities: Abilities[];
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
excludeDisallowedPokemon: boolean;
constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
super(); super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ]; this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ];
@ -577,16 +644,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability)); return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false)));
} else { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0); return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0);
} }
} }
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) { const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false));
return [ "ability", pokemon.getAbility().name ]; if (!isNullOrUndefined(matchingAbility)) {
return [ "ability", allAbilities[matchingAbility].name ];
} }
return [ "ability", "" ]; return [ "ability", "" ];
} }

View File

@ -53,6 +53,7 @@ export interface IMysteryEncounter {
hasBattleAnimationsWithoutTargets: boolean; hasBattleAnimationsWithoutTargets: boolean;
skipEnemyBattleTurns: boolean; skipEnemyBattleTurns: boolean;
skipToFightInput: boolean; skipToFightInput: boolean;
preventGameStatsUpdates: boolean;
onInit?: (scene: BattleScene) => boolean; onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean; onVisualsStart?: (scene: BattleScene) => boolean;
@ -150,6 +151,10 @@ export default class MysteryEncounter implements IMysteryEncounter {
* If true, will skip COMMAND input and go straight to FIGHT (move select) input menu * If true, will skip COMMAND input and go straight to FIGHT (move select) input menu
*/ */
skipToFightInput: boolean; skipToFightInput: boolean;
/**
* If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon
*/
preventGameStatsUpdates: boolean;
// #region Event callback functions // #region Event callback functions
@ -325,7 +330,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (activeMon.length > 0) { if (activeMon.length > 0) {
this.primaryPokemon = activeMon[0]; this.primaryPokemon = activeMon[0];
} else { } else {
this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0]; this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0];
} }
return true; return true;
} }
@ -548,6 +553,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
hasBattleAnimationsWithoutTargets: boolean = false; hasBattleAnimationsWithoutTargets: boolean = false;
skipEnemyBattleTurns: boolean = false; skipEnemyBattleTurns: boolean = false;
skipToFightInput: boolean = false; skipToFightInput: boolean = false;
preventGameStatsUpdates: boolean = false;
maxAllowedEncounters: number = 3; maxAllowedEncounters: number = 3;
expMultiplier: number = 1; expMultiplier: number = 1;
@ -735,6 +741,14 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return Object.assign(this, { skipToFightInput }); return Object.assign(this, { skipToFightInput });
} }
/**
* If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon
* Default `false`
*/
withPreventGameStatsUpdates(preventGameStatsUpdates: boolean): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
return Object.assign(this, { preventGameStatsUpdates });
}
/** /**
* Sets the maximum number of times that an encounter can spawn in a given Classic run * Sets the maximum number of times that an encounter can spawn in a given Classic run
* @param maxAllowedEncounters * @param maxAllowedEncounters

View File

@ -118,3 +118,20 @@ export const EXTORTION_ABILITIES = [
Abilities.SUCTION_CUPS, Abilities.SUCTION_CUPS,
Abilities.STICKY_HOLD Abilities.STICKY_HOLD
]; ];
/**
* Abilities that signify resistance to fire
*/
export const FIRE_RESISTANT_ABILITIES = [
Abilities.FLAME_BODY,
Abilities.FLASH_FIRE,
Abilities.WELL_BAKED_BODY,
Abilities.HEATPROOF,
Abilities.THERMAL_EXCHANGE,
Abilities.THICK_FAT,
Abilities.WATER_BUBBLE,
Abilities.MAGMA_ARMOR,
Abilities.WATER_VEIL,
Abilities.STEAM_ENGINE,
Abilities.PRIMORDIAL_SEA
];

View File

@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect";
import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
import PokemonSpecies from "#app/data/pokemon-species"; import PokemonSpecies from "#app/data/pokemon-species";
import { Egg, IEggOptions } from "#app/data/egg"; import { Egg, IEggOptions } from "#app/data/egg";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
@ -71,7 +71,7 @@ export interface EnemyPokemonConfig {
nickname?: string; nickname?: string;
bossSegments?: number; bossSegments?: number;
bossSegmentModifier?: number; // Additive to the determined segment number bossSegmentModifier?: number; // Additive to the determined segment number
mysteryEncounterPokemonData?: MysteryEncounterPokemonData; customPokemonData?: CustomPokemonData;
formIndex?: number; formIndex?: number;
abilityIndex?: number; abilityIndex?: number;
level?: number; level?: number;
@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
newTrainer.setVisible(false); newTrainer.setVisible(false);
scene.field.add(newTrainer); scene.field.add(newTrainer);
scene.currentBattle.trainer = newTrainer; scene.currentBattle.trainer = newTrainer;
loadEnemyAssets.push(newTrainer.loadAssets()); loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite()));
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
} else { } else {
@ -250,8 +250,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
} }
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { if (!isNullOrUndefined(config.customPokemonData)) {
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData; enemyPokemon.customPokemonData = config.customPokemonData;
} }
// Set Boss // Set Boss
@ -1069,19 +1069,19 @@ export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue:
const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
switch (tier) { switch (tier) {
default: default:
case BiomePoolTier.BOSS: case BiomePoolTier.BOSS:
++bossEncountersByRarity[0]; ++bossEncountersByRarity[0];
break; break;
case BiomePoolTier.BOSS_RARE: case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1]; ++bossEncountersByRarity[1];
break; break;
case BiomePoolTier.BOSS_SUPER_RARE: case BiomePoolTier.BOSS_SUPER_RARE:
++bossEncountersByRarity[2]; ++bossEncountersByRarity[2];
break; break;
case BiomePoolTier.BOSS_ULTRA_RARE: case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3]; ++bossEncountersByRarity[3];
break; break;
} }
} }

View File

@ -21,6 +21,8 @@ import { Gender } from "#app/data/gender";
import { PermanentStat } from "#enums/stat"; import { PermanentStat } from "#enums/stat";
import { VictoryPhase } from "#app/phases/victory-phase"; import { VictoryPhase } from "#app/phases/victory-phase";
import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Abilities } from "#enums/abilities";
/** Will give +1 level every 10 waves */ /** Will give +1 level every 10 waves */
export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1;
@ -833,3 +835,21 @@ export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scen
return null; return null;
} }
/**
* Permanently overrides the ability (not passive) of a pokemon.
* If the pokemon is a fusion, instead overrides the fused pokemon's ability.
*/
export function applyAbilityOverrideToPokemon(pokemon: Pokemon, ability: Abilities) {
if (pokemon.isFusion()) {
if (!pokemon.fusionCustomPokemonData) {
pokemon.fusionCustomPokemonData = new CustomPokemonData();
}
pokemon.fusionCustomPokemonData.ability = ability;
} else {
if (!pokemon.customPokemonData) {
pokemon.customPokemonData = new CustomPokemonData();
}
pokemon.customPokemonData.ability = ability;
}
}

View File

@ -37,76 +37,76 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { export function getNatureStatMultiplier(nature: Nature, stat: Stat): number {
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
switch (nature) { switch (nature) {
case Nature.LONELY: case Nature.LONELY:
case Nature.BRAVE: case Nature.BRAVE:
case Nature.ADAMANT: case Nature.ADAMANT:
case Nature.NAUGHTY: case Nature.NAUGHTY:
return 1.1; return 1.1;
case Nature.BOLD: case Nature.BOLD:
case Nature.TIMID: case Nature.TIMID:
case Nature.MODEST: case Nature.MODEST:
case Nature.CALM: case Nature.CALM:
return 0.9; return 0.9;
} }
break; break;
case Stat.DEF: case Stat.DEF:
switch (nature) { switch (nature) {
case Nature.BOLD: case Nature.BOLD:
case Nature.RELAXED: case Nature.RELAXED:
case Nature.IMPISH: case Nature.IMPISH:
case Nature.LAX: case Nature.LAX:
return 1.1; return 1.1;
case Nature.LONELY: case Nature.LONELY:
case Nature.HASTY: case Nature.HASTY:
case Nature.MILD: case Nature.MILD:
case Nature.GENTLE: case Nature.GENTLE:
return 0.9; return 0.9;
} }
break; break;
case Stat.SPATK: case Stat.SPATK:
switch (nature) { switch (nature) {
case Nature.MODEST: case Nature.MODEST:
case Nature.MILD: case Nature.MILD:
case Nature.QUIET: case Nature.QUIET:
case Nature.RASH: case Nature.RASH:
return 1.1; return 1.1;
case Nature.ADAMANT: case Nature.ADAMANT:
case Nature.IMPISH: case Nature.IMPISH:
case Nature.JOLLY: case Nature.JOLLY:
case Nature.CAREFUL: case Nature.CAREFUL:
return 0.9; return 0.9;
} }
break; break;
case Stat.SPDEF: case Stat.SPDEF:
switch (nature) { switch (nature) {
case Nature.CALM: case Nature.CALM:
case Nature.GENTLE: case Nature.GENTLE:
case Nature.SASSY: case Nature.SASSY:
case Nature.CAREFUL: case Nature.CAREFUL:
return 1.1; return 1.1;
case Nature.NAUGHTY: case Nature.NAUGHTY:
case Nature.LAX: case Nature.LAX:
case Nature.NAIVE: case Nature.NAIVE:
case Nature.RASH: case Nature.RASH:
return 0.9; return 0.9;
} }
break; break;
case Stat.SPD: case Stat.SPD:
switch (nature) { switch (nature) {
case Nature.TIMID: case Nature.TIMID:
case Nature.HASTY: case Nature.HASTY:
case Nature.JOLLY: case Nature.JOLLY:
case Nature.NAIVE: case Nature.NAIVE:
return 1.1; return 1.1;
case Nature.BRAVE: case Nature.BRAVE:
case Nature.RELAXED: case Nature.RELAXED:
case Nature.QUIET: case Nature.QUIET:
case Nature.SASSY: case Nature.SASSY:
return 0.9; return 0.9;
} }
break; break;
} }
return 1; return 1;

View File

@ -9,42 +9,42 @@ export const MAX_PER_TYPE_POKEBALLS: integer = 99;
export function getPokeballAtlasKey(type: PokeballType): string { export function getPokeballAtlasKey(type: PokeballType): string {
switch (type) { switch (type) {
case PokeballType.POKEBALL: case PokeballType.POKEBALL:
return "pb"; return "pb";
case PokeballType.GREAT_BALL: case PokeballType.GREAT_BALL:
return "gb"; return "gb";
case PokeballType.ULTRA_BALL: case PokeballType.ULTRA_BALL:
return "ub"; return "ub";
case PokeballType.ROGUE_BALL: case PokeballType.ROGUE_BALL:
return "rb"; return "rb";
case PokeballType.MASTER_BALL: case PokeballType.MASTER_BALL:
return "mb"; return "mb";
case PokeballType.LUXURY_BALL: case PokeballType.LUXURY_BALL:
return "lb"; return "lb";
} }
} }
export function getPokeballName(type: PokeballType): string { export function getPokeballName(type: PokeballType): string {
let ret: string; let ret: string;
switch (type) { switch (type) {
case PokeballType.POKEBALL: case PokeballType.POKEBALL:
ret = i18next.t("pokeball:pokeBall"); ret = i18next.t("pokeball:pokeBall");
break; break;
case PokeballType.GREAT_BALL: case PokeballType.GREAT_BALL:
ret = i18next.t("pokeball:greatBall"); ret = i18next.t("pokeball:greatBall");
break; break;
case PokeballType.ULTRA_BALL: case PokeballType.ULTRA_BALL:
ret = i18next.t("pokeball:ultraBall"); ret = i18next.t("pokeball:ultraBall");
break; break;
case PokeballType.ROGUE_BALL: case PokeballType.ROGUE_BALL:
ret = i18next.t("pokeball:rogueBall"); ret = i18next.t("pokeball:rogueBall");
break; break;
case PokeballType.MASTER_BALL: case PokeballType.MASTER_BALL:
ret = i18next.t("pokeball:masterBall"); ret = i18next.t("pokeball:masterBall");
break; break;
case PokeballType.LUXURY_BALL: case PokeballType.LUXURY_BALL:
ret = i18next.t("pokeball:luxuryBall"); ret = i18next.t("pokeball:luxuryBall");
break; break;
} }
return ret; return ret;
} }
@ -74,18 +74,18 @@ export function getPokeballCatchMultiplier(type: PokeballType, enemyPokemon?: Po
export function getPokeballTintColor(type: PokeballType): number { export function getPokeballTintColor(type: PokeballType): number {
switch (type) { switch (type) {
case PokeballType.POKEBALL: case PokeballType.POKEBALL:
return 0xd52929; return 0xd52929;
case PokeballType.GREAT_BALL: case PokeballType.GREAT_BALL:
return 0x94b4de; return 0x94b4de;
case PokeballType.ULTRA_BALL: case PokeballType.ULTRA_BALL:
return 0xe6cd31; return 0xe6cd31;
case PokeballType.ROGUE_BALL: case PokeballType.ROGUE_BALL:
return 0xd52929; return 0xd52929;
case PokeballType.MASTER_BALL: case PokeballType.MASTER_BALL:
return 0xa441bd; return 0xa441bd;
case PokeballType.LUXURY_BALL: case PokeballType.LUXURY_BALL:
return 0xffde6a; return 0xffde6a;
} }
} }

View File

@ -238,8 +238,8 @@ export abstract class PokemonSpeciesForm {
isRareRegional(): boolean { isRareRegional(): boolean {
switch (this.getRegion()) { switch (this.getRegion()) {
case Region.HISUI: case Region.HISUI:
return true; return true;
} }
return false; return false;
@ -265,14 +265,14 @@ export abstract class PokemonSpeciesForm {
getBaseExp(): number { getBaseExp(): number {
let ret = this.baseExp; let ret = this.baseExp;
switch (this.getFormSpriteKey()) { switch (this.getFormSpriteKey()) {
case SpeciesFormKey.MEGA: case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X: case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y: case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.PRIMAL: case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.GIGANTAMAX: case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.ETERNAMAX: case SpeciesFormKey.ETERNAMAX:
ret *= 1.5; ret *= 1.5;
break; break;
} }
return ret; return ret;
} }
@ -346,29 +346,29 @@ export abstract class PokemonSpeciesForm {
} }
switch (this.speciesId) { switch (this.speciesId) {
case Species.HIPPOPOTAS: case Species.HIPPOPOTAS:
case Species.HIPPOWDON: case Species.HIPPOWDON:
case Species.UNFEZANT: case Species.UNFEZANT:
case Species.FRILLISH: case Species.FRILLISH:
case Species.JELLICENT: case Species.JELLICENT:
case Species.PYROAR: case Species.PYROAR:
ret += female ? "-f" : ""; ret += female ? "-f" : "";
break; break;
} }
let formSpriteKey = this.getFormSpriteKey(formIndex); let formSpriteKey = this.getFormSpriteKey(formIndex);
if (formSpriteKey) { if (formSpriteKey) {
switch (this.speciesId) { switch (this.speciesId) {
case Species.DUDUNSPARCE: case Species.DUDUNSPARCE:
break; break;
case Species.ZACIAN: case Species.ZACIAN:
case Species.ZAMAZENTA: case Species.ZAMAZENTA:
if (formSpriteKey.startsWith("behemoth")) { if (formSpriteKey.startsWith("behemoth")) {
formSpriteKey = "crowned"; formSpriteKey = "crowned";
} }
default: default:
ret += `-${formSpriteKey}`; ret += `-${formSpriteKey}`;
break; break;
} }
} }
@ -383,15 +383,15 @@ export abstract class PokemonSpeciesForm {
let speciesId = this.speciesId; let speciesId = this.speciesId;
if (this.speciesId > 2000) { if (this.speciesId > 2000) {
switch (this.speciesId) { switch (this.speciesId) {
case Species.GALAR_SLOWPOKE: case Species.GALAR_SLOWPOKE:
break; break;
case Species.ETERNAL_FLOETTE: case Species.ETERNAL_FLOETTE:
break; break;
case Species.BLOODMOON_URSALUNA: case Species.BLOODMOON_URSALUNA:
break; break;
default: default:
speciesId = speciesId % 2000; speciesId = speciesId % 2000;
break; break;
} }
} }
let ret = speciesId.toString(); let ret = speciesId.toString();
@ -403,44 +403,44 @@ export abstract class PokemonSpeciesForm {
} }
const formKey = forms[formIndex || 0].formKey; const formKey = forms[formIndex || 0].formKey;
switch (formKey) { switch (formKey) {
case SpeciesFormKey.MEGA: case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X: case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y: case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.GIGANTAMAX: case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.GIGANTAMAX_SINGLE: case SpeciesFormKey.GIGANTAMAX_SINGLE:
case SpeciesFormKey.GIGANTAMAX_RAPID: case SpeciesFormKey.GIGANTAMAX_RAPID:
case "white": case "white":
case "black": case "black":
case "therian": case "therian":
case "sky": case "sky":
case "gorging": case "gorging":
case "gulping": case "gulping":
case "no-ice": case "no-ice":
case "hangry": case "hangry":
case "crowned": case "crowned":
case "eternamax": case "eternamax":
case "four": case "four":
case "droopy": case "droopy":
case "stretchy": case "stretchy":
case "hero": case "hero":
case "roaming": case "roaming":
case "complete": case "complete":
case "10-complete": case "10-complete":
case "10": case "10":
case "10-pc": case "10-pc":
case "super": case "super":
case "unbound": case "unbound":
case "pau": case "pau":
case "pompom": case "pompom":
case "sensu": case "sensu":
case "dusk": case "dusk":
case "midnight": case "midnight":
case "school": case "school":
case "dawn-wings": case "dawn-wings":
case "dusk-mane": case "dusk-mane":
case "ultra": case "ultra":
ret += `-${formKey}`; ret += `-${formKey}`;
break; break;
} }
} }
return ret; return ret;
@ -636,19 +636,19 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const form = this.forms[formIndex]; const form = this.forms[formIndex];
let key: string | null; let key: string | null;
switch (form.formKey) { switch (form.formKey) {
case SpeciesFormKey.MEGA: case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL: case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.ETERNAMAX: case SpeciesFormKey.ETERNAMAX:
case SpeciesFormKey.MEGA_X: case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y: case SpeciesFormKey.MEGA_Y:
key = form.formKey; key = form.formKey;
break; break;
default: default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax"; key = "gigantamax";
} else { } else {
key = null; key = null;
} }
} }
if (key) { if (key) {
@ -690,18 +690,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
*/ */
private getStrengthLevelDiff(strength: PartyMemberStrength): integer { private getStrengthLevelDiff(strength: PartyMemberStrength): integer {
switch (Math.min(strength, PartyMemberStrength.STRONGER)) { switch (Math.min(strength, PartyMemberStrength.STRONGER)) {
case PartyMemberStrength.WEAKEST: case PartyMemberStrength.WEAKEST:
return 60; return 60;
case PartyMemberStrength.WEAKER: case PartyMemberStrength.WEAKER:
return 40; return 40;
case PartyMemberStrength.WEAK: case PartyMemberStrength.WEAK:
return 20; return 20;
case PartyMemberStrength.AVERAGE: case PartyMemberStrength.AVERAGE:
return 8; return 8;
case PartyMemberStrength.STRONG: case PartyMemberStrength.STRONG:
return 4; return 4;
default: default:
return 0; return 0;
} }
} }
@ -2580,11 +2580,11 @@ export function initSpecies() {
new PokemonSpecies(Species.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", Type.STEEL, Type.POISON, 1, 35, Abilities.OVERCOAT, Abilities.NONE, Abilities.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", Type.STEEL, Type.POISON, 1, 35, Abilities.OVERCOAT, Abilities.NONE, Abilities.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false,
new PokemonForm("Normal", "", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), new PokemonForm("Normal", "", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true),
new PokemonForm("Segin Starmobile", "segin-starmobile", Type.STEEL, Type.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), new PokemonForm("Segin Starmobile", "segin-starmobile", Type.STEEL, Type.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175),
new PokemonForm("Schedar Starmobile", "schedar-starmobile", Type.STEEL, Type.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), new PokemonForm("Schedar Starmobile", "schedar-starmobile", Type.STEEL, Type.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175),
new PokemonForm("Navi Starmobile", "navi-starmobile", Type.STEEL, Type.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), new PokemonForm("Navi Starmobile", "navi-starmobile", Type.STEEL, Type.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175),
new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", Type.STEEL, Type.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", Type.STEEL, Type.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175),
new PokemonForm("Caph Starmobile", "caph-starmobile", Type.STEEL, Type.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), new PokemonForm("Caph Starmobile", "caph-starmobile", Type.STEEL, Type.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175),
), ),
new PokemonSpecies(Species.CYCLIZAR, 9, false, false, false, "Mount Pokémon", Type.DRAGON, Type.NORMAL, 1.6, 63, Abilities.SHED_SKIN, Abilities.NONE, Abilities.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.CYCLIZAR, 9, false, false, false, "Mount Pokémon", Type.DRAGON, Type.NORMAL, 1.6, 63, Abilities.SHED_SKIN, Abilities.NONE, Abilities.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false),
new PokemonSpecies(Species.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", Type.STEEL, null, 2.5, 310, Abilities.EARTH_EATER, Abilities.NONE, Abilities.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", Type.STEEL, null, 2.5, 310, Abilities.EARTH_EATER, Abilities.NONE, Abilities.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false),

View File

@ -1,4 +1,4 @@
import * as Utils from "../utils"; import { randIntRange } from "#app/utils";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next, { ParseKeys } from "i18next"; import i18next, { ParseKeys } from "i18next";
@ -6,17 +6,21 @@ export { StatusEffect };
export class Status { export class Status {
public effect: StatusEffect; public effect: StatusEffect;
public turnCount: integer; /** Toxic damage is `1/16 max HP * toxicTurnCount` */
public cureTurn: integer | null; public toxicTurnCount: number = 0;
public sleepTurnsRemaining?: number;
constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) { constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) {
this.effect = effect; this.effect = effect;
this.turnCount = turnCount === undefined ? 0 : turnCount; this.toxicTurnCount = toxicTurnCount;
this.cureTurn = cureTurn!; // TODO: is this bang correct? this.sleepTurnsRemaining = sleepTurnsRemaining;
} }
incrementTurn(): void { incrementTurn(): void {
this.turnCount++; this.toxicTurnCount++;
if (this.sleepTurnsRemaining) {
this.sleepTurnsRemaining--;
}
} }
isPostTurn(): boolean { isPostTurn(): boolean {
@ -26,20 +30,20 @@ export class Status {
function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string { function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string {
switch (statusEffect) { switch (statusEffect) {
case StatusEffect.POISON: case StatusEffect.POISON:
return "statusEffect:poison"; return "statusEffect:poison";
case StatusEffect.TOXIC: case StatusEffect.TOXIC:
return "statusEffect:toxic"; return "statusEffect:toxic";
case StatusEffect.PARALYSIS: case StatusEffect.PARALYSIS:
return "statusEffect:paralysis"; return "statusEffect:paralysis";
case StatusEffect.SLEEP: case StatusEffect.SLEEP:
return "statusEffect:sleep"; return "statusEffect:sleep";
case StatusEffect.FREEZE: case StatusEffect.FREEZE:
return "statusEffect:freeze"; return "statusEffect:freeze";
case StatusEffect.BURN: case StatusEffect.BURN:
return "statusEffect:burn"; return "statusEffect:burn";
default: default:
return "statusEffect:none"; return "statusEffect:none";
} }
} }
@ -90,14 +94,14 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect): string {
export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number { export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number {
switch (statusEffect) { switch (statusEffect) {
case StatusEffect.POISON: case StatusEffect.POISON:
case StatusEffect.TOXIC: case StatusEffect.TOXIC:
case StatusEffect.PARALYSIS: case StatusEffect.PARALYSIS:
case StatusEffect.BURN: case StatusEffect.BURN:
return 1.5; return 1.5;
case StatusEffect.SLEEP: case StatusEffect.SLEEP:
case StatusEffect.FREEZE: case StatusEffect.FREEZE:
return 2.5; return 2.5;
} }
return 1; return 1;
@ -107,7 +111,7 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect):
* Returns a random non-volatile StatusEffect * Returns a random non-volatile StatusEffect
*/ */
export function generateRandomStatusEffect(): StatusEffect { export function generateRandomStatusEffect(): StatusEffect {
return Utils.randIntRange(1, 6); return randIntRange(1, 6);
} }
/** /**
@ -123,7 +127,7 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB
return statusEffectA; return statusEffectA;
} }
return Utils.randIntRange(0, 2) ? statusEffectA : statusEffectB; return randIntRange(0, 2) ? statusEffectA : statusEffectB;
} }
/** /**
@ -140,7 +144,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
} }
return Utils.randIntRange(0, 2) ? statusA : statusB; return randIntRange(0, 2) ? statusA : statusB;
} }
/** /**

View File

@ -34,21 +34,21 @@ export class Terrain {
getAttackTypeMultiplier(attackType: Type): number { getAttackTypeMultiplier(attackType: Type): number {
switch (this.terrainType) { switch (this.terrainType) {
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
if (attackType === Type.ELECTRIC) { if (attackType === Type.ELECTRIC) {
return 1.3; return 1.3;
} }
break; break;
case TerrainType.GRASSY: case TerrainType.GRASSY:
if (attackType === Type.GRASS) { if (attackType === Type.GRASS) {
return 1.3; return 1.3;
} }
break; break;
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
if (attackType === Type.PSYCHIC) { if (attackType === Type.PSYCHIC) {
return 1.3; return 1.3;
} }
break; break;
} }
return 1; return 1;
@ -56,13 +56,13 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) { switch (this.terrainType) {
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) { if (!move.hasAttr(ProtectAttr)) {
const priority = new Utils.IntegerHolder(move.priority); const priority = new Utils.IntegerHolder(move.priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority);
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
} }
} }
return false; return false;
@ -71,14 +71,14 @@ export class Terrain {
export function getTerrainName(terrainType: TerrainType): string { export function getTerrainName(terrainType: TerrainType): string {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:misty"); return i18next.t("terrain:misty");
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
return i18next.t("terrain:electric"); return i18next.t("terrain:electric");
case TerrainType.GRASSY: case TerrainType.GRASSY:
return i18next.t("terrain:grassy"); return i18next.t("terrain:grassy");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychic"); return i18next.t("terrain:psychic");
} }
return ""; return "";
@ -87,14 +87,14 @@ export function getTerrainName(terrainType: TerrainType): string {
export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return [ 232, 136, 200 ]; return [ 232, 136, 200 ];
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
return [ 248, 248, 120 ]; return [ 248, 248, 120 ];
case TerrainType.GRASSY: case TerrainType.GRASSY:
return [ 120, 200, 80 ]; return [ 120, 200, 80 ];
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return [ 160, 64, 160 ]; return [ 160, 64, 160 ];
} }
return [ 0, 0, 0 ]; return [ 0, 0, 0 ];

View File

@ -299,65 +299,65 @@ export class TrainerConfig {
getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType { getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType {
let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType; let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType;
switch (trainerType) { switch (trainerType) {
case TrainerType.RIVAL_2: case TrainerType.RIVAL_2:
case TrainerType.RIVAL_3: case TrainerType.RIVAL_3:
case TrainerType.RIVAL_4: case TrainerType.RIVAL_4:
case TrainerType.RIVAL_5: case TrainerType.RIVAL_5:
case TrainerType.RIVAL_6: case TrainerType.RIVAL_6:
trainerType = TrainerType.RIVAL; trainerType = TrainerType.RIVAL;
break; break;
case TrainerType.LANCE_CHAMPION: case TrainerType.LANCE_CHAMPION:
trainerType = TrainerType.LANCE; trainerType = TrainerType.LANCE;
break; break;
case TrainerType.LARRY_ELITE: case TrainerType.LARRY_ELITE:
trainerType = TrainerType.LARRY; trainerType = TrainerType.LARRY;
break; break;
case TrainerType.ROCKET_BOSS_GIOVANNI_1: case TrainerType.ROCKET_BOSS_GIOVANNI_1:
case TrainerType.ROCKET_BOSS_GIOVANNI_2: case TrainerType.ROCKET_BOSS_GIOVANNI_2:
trainerType = TrainerType.GIOVANNI; trainerType = TrainerType.GIOVANNI;
break; break;
case TrainerType.MAXIE_2: case TrainerType.MAXIE_2:
trainerType = TrainerType.MAXIE; trainerType = TrainerType.MAXIE;
break; break;
case TrainerType.ARCHIE_2: case TrainerType.ARCHIE_2:
trainerType = TrainerType.ARCHIE; trainerType = TrainerType.ARCHIE;
break; break;
case TrainerType.CYRUS_2: case TrainerType.CYRUS_2:
trainerType = TrainerType.CYRUS; trainerType = TrainerType.CYRUS;
break; break;
case TrainerType.GHETSIS_2: case TrainerType.GHETSIS_2:
trainerType = TrainerType.GHETSIS; trainerType = TrainerType.GHETSIS;
break; break;
case TrainerType.LYSANDRE_2: case TrainerType.LYSANDRE_2:
trainerType = TrainerType.LYSANDRE; trainerType = TrainerType.LYSANDRE;
break; break;
case TrainerType.LUSAMINE_2: case TrainerType.LUSAMINE_2:
trainerType = TrainerType.LUSAMINE; trainerType = TrainerType.LUSAMINE;
break; break;
case TrainerType.GUZMA_2: case TrainerType.GUZMA_2:
trainerType = TrainerType.GUZMA; trainerType = TrainerType.GUZMA;
break; break;
case TrainerType.ROSE_2: case TrainerType.ROSE_2:
trainerType = TrainerType.ROSE; trainerType = TrainerType.ROSE;
break; break;
case TrainerType.PENNY_2: case TrainerType.PENNY_2:
trainerType = TrainerType.PENNY; trainerType = TrainerType.PENNY;
break; break;
case TrainerType.MARNIE_ELITE: case TrainerType.MARNIE_ELITE:
trainerType = TrainerType.MARNIE; trainerType = TrainerType.MARNIE;
break; break;
case TrainerType.NESSA_ELITE: case TrainerType.NESSA_ELITE:
trainerType = TrainerType.NESSA; trainerType = TrainerType.NESSA;
break; break;
case TrainerType.BEA_ELITE: case TrainerType.BEA_ELITE:
trainerType = TrainerType.BEA; trainerType = TrainerType.BEA;
break; break;
case TrainerType.ALLISTER_ELITE: case TrainerType.ALLISTER_ELITE:
trainerType = TrainerType.ALLISTER; trainerType = TrainerType.ALLISTER;
break; break;
case TrainerType.RAIHAN_ELITE: case TrainerType.RAIHAN_ELITE:
trainerType = TrainerType.RAIHAN; trainerType = TrainerType.RAIHAN;
break; break;
} }
return trainerType; return trainerType;
@ -564,104 +564,104 @@ export class TrainerConfig {
speciesPoolPerEvilTeamAdmin(team): TrainerTierPools { speciesPoolPerEvilTeamAdmin(team): TrainerTierPools {
team = team.toLowerCase(); team = team.toLowerCase();
switch (team) { switch (team) {
case "rocket": { case "rocket": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ],
[TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ],
[TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ]
}; };
} }
case "magma": { case "magma": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
}; };
} }
case "aqua": { case "aqua": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ], [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ],
[TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ],
[TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ]
}; };
} }
case "galactic": { case "galactic": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ],
[TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ],
[TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ]
}; };
} }
case "plasma": { case "plasma": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ],
[TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ],
[TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ]
}; };
} }
case "flare": { case "flare": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ],
[TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ]
}; };
} }
case "aether": { case "aether": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ],
[TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ],
[TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ]
}; };
} }
case "skull": { case "skull": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ],
[TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ],
[TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ]
}; };
} }
case "macro": { case "macro": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ],
[TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ],
[TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ]
}; };
} }
case "star_1": { case "star_1": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ],
[TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ],
[TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ]
}; };
} }
case "star_2": { case "star_2": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ],
[TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ],
[TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ]
}; };
} }
case "star_3": { case "star_3": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
[TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ],
[TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
}; };
} }
case "star_4": { case "star_4": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
[TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ],
[TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
}; };
} }
case "star_5": { case "star_5": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ],
[TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ],
[TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ]
}; };
} }
} }
console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`); console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`);
@ -2500,6 +2500,22 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) [TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER)
.setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)), .setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)),
[TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER).setLocalizedName("Expert Pokemon Breeder") [TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER).setLocalizedName("Expert Pokemon Breeder")
.setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG)) .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE)),
[TrainerType.FUTURE_SELF_M]: new TrainerConfig(++t)
.setMoneyMultiplier(0)
.setEncounterBgm("mystery_encounter_weird_dream")
.setBattleBgm("mystery_encounter_weird_dream")
.setMixedBattleBgm("mystery_encounter_weird_dream")
.setVictoryBgm("mystery_encounter_weird_dream")
.setLocalizedName("Future Self M")
.setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)),
[TrainerType.FUTURE_SELF_F]: new TrainerConfig(++t)
.setMoneyMultiplier(0)
.setEncounterBgm("mystery_encounter_weird_dream")
.setBattleBgm("mystery_encounter_weird_dream")
.setMixedBattleBgm("mystery_encounter_weird_dream")
.setVictoryBgm("mystery_encounter_weird_dream")
.setLocalizedName("Future Self F")
.setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG))
}; };

View File

@ -29,260 +29,260 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa
} }
switch (defType) { switch (defType) {
case Type.NORMAL:
switch (attackType) {
case Type.FIGHTING:
return 2;
case Type.GHOST:
return 0;
default:
return 1;
}
case Type.FIGHTING:
switch (attackType) {
case Type.FLYING:
case Type.PSYCHIC:
case Type.FAIRY:
return 2;
case Type.ROCK:
case Type.BUG:
case Type.DARK:
return 0.5;
default:
return 1;
}
case Type.FLYING:
switch (attackType) {
case Type.ROCK:
case Type.ELECTRIC:
case Type.ICE:
return 2;
case Type.FIGHTING:
case Type.BUG:
case Type.GRASS:
return 0.5;
case Type.GROUND:
return 0;
default:
return 1;
}
case Type.POISON:
switch (attackType) {
case Type.GROUND:
case Type.PSYCHIC:
return 2;
case Type.FIGHTING:
case Type.POISON:
case Type.BUG:
case Type.GRASS:
case Type.FAIRY:
return 0.5;
default:
return 1;
}
case Type.GROUND:
switch (attackType) {
case Type.WATER:
case Type.GRASS:
case Type.ICE:
return 2;
case Type.POISON:
case Type.ROCK:
return 0.5;
case Type.ELECTRIC:
return 0;
default:
return 1;
}
case Type.ROCK:
switch (attackType) {
case Type.FIGHTING:
case Type.GROUND:
case Type.STEEL:
case Type.WATER:
case Type.GRASS:
return 2;
case Type.NORMAL: case Type.NORMAL:
case Type.FLYING: switch (attackType) {
case Type.POISON: case Type.FIGHTING:
case Type.FIRE: return 2;
return 0.5; case Type.GHOST:
default: return 0;
return 1; default:
} return 1;
case Type.BUG: }
switch (attackType) {
case Type.FLYING:
case Type.ROCK:
case Type.FIRE:
return 2;
case Type.FIGHTING: case Type.FIGHTING:
switch (attackType) {
case Type.FLYING:
case Type.PSYCHIC:
case Type.FAIRY:
return 2;
case Type.ROCK:
case Type.BUG:
case Type.DARK:
return 0.5;
default:
return 1;
}
case Type.FLYING:
switch (attackType) {
case Type.ROCK:
case Type.ELECTRIC:
case Type.ICE:
return 2;
case Type.FIGHTING:
case Type.BUG:
case Type.GRASS:
return 0.5;
case Type.GROUND:
return 0;
default:
return 1;
}
case Type.POISON:
switch (attackType) {
case Type.GROUND:
case Type.PSYCHIC:
return 2;
case Type.FIGHTING:
case Type.POISON:
case Type.BUG:
case Type.GRASS:
case Type.FAIRY:
return 0.5;
default:
return 1;
}
case Type.GROUND: case Type.GROUND:
case Type.GRASS: switch (attackType) {
return 0.5; case Type.WATER:
default: case Type.GRASS:
return 1; case Type.ICE:
} return 2;
case Type.GHOST: case Type.POISON:
switch (attackType) { case Type.ROCK:
return 0.5;
case Type.ELECTRIC:
return 0;
default:
return 1;
}
case Type.ROCK:
switch (attackType) {
case Type.FIGHTING:
case Type.GROUND:
case Type.STEEL:
case Type.WATER:
case Type.GRASS:
return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.FIRE:
return 0.5;
default:
return 1;
}
case Type.BUG:
switch (attackType) {
case Type.FLYING:
case Type.ROCK:
case Type.FIRE:
return 2;
case Type.FIGHTING:
case Type.GROUND:
case Type.GRASS:
return 0.5;
default:
return 1;
}
case Type.GHOST: case Type.GHOST:
case Type.DARK: switch (attackType) {
return 2; case Type.GHOST:
case Type.POISON: case Type.DARK:
case Type.BUG: return 2;
return 0.5; case Type.POISON:
case Type.NORMAL: case Type.BUG:
case Type.FIGHTING: return 0.5;
return 0; case Type.NORMAL:
default: case Type.FIGHTING:
return 1; return 0;
} default:
case Type.STEEL: return 1;
switch (attackType) { }
case Type.FIGHTING:
case Type.GROUND:
case Type.FIRE:
return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.ROCK:
case Type.BUG:
case Type.STEEL: case Type.STEEL:
switch (attackType) {
case Type.FIGHTING:
case Type.GROUND:
case Type.FIRE:
return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.ROCK:
case Type.BUG:
case Type.STEEL:
case Type.GRASS:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.FAIRY:
return 0.5;
case Type.POISON:
return 0;
default:
return 1;
}
case Type.FIRE:
switch (attackType) {
case Type.GROUND:
case Type.ROCK:
case Type.WATER:
return 2;
case Type.BUG:
case Type.STEEL:
case Type.FIRE:
case Type.GRASS:
case Type.ICE:
case Type.FAIRY:
return 0.5;
default:
return 1;
}
case Type.WATER:
switch (attackType) {
case Type.GRASS:
case Type.ELECTRIC:
return 2;
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.ICE:
return 0.5;
default:
return 1;
}
case Type.GRASS: case Type.GRASS:
switch (attackType) {
case Type.FLYING:
case Type.POISON:
case Type.BUG:
case Type.FIRE:
case Type.ICE:
return 2;
case Type.GROUND:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
return 0.5;
default:
return 1;
}
case Type.ELECTRIC:
switch (attackType) {
case Type.GROUND:
return 2;
case Type.FLYING:
case Type.STEEL:
case Type.ELECTRIC:
return 0.5;
default:
return 1;
}
case Type.PSYCHIC: case Type.PSYCHIC:
switch (attackType) {
case Type.BUG:
case Type.GHOST:
case Type.DARK:
return 2;
case Type.FIGHTING:
case Type.PSYCHIC:
return 0.5;
default:
return 1;
}
case Type.ICE: case Type.ICE:
switch (attackType) {
case Type.FIGHTING:
case Type.ROCK:
case Type.STEEL:
case Type.FIRE:
return 2;
case Type.ICE:
return 0.5;
default:
return 1;
}
case Type.DRAGON: case Type.DRAGON:
case Type.FAIRY: switch (attackType) {
return 0.5; case Type.ICE:
case Type.POISON: case Type.DRAGON:
return 0; case Type.FAIRY:
default: return 2;
return 1; case Type.FIRE:
} case Type.WATER:
case Type.FIRE: case Type.GRASS:
switch (attackType) { case Type.ELECTRIC:
case Type.GROUND: return 0.5;
case Type.ROCK: default:
case Type.WATER: return 1;
return 2; }
case Type.BUG:
case Type.STEEL:
case Type.FIRE:
case Type.GRASS:
case Type.ICE:
case Type.FAIRY:
return 0.5;
default:
return 1;
}
case Type.WATER:
switch (attackType) {
case Type.GRASS:
case Type.ELECTRIC:
return 2;
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.ICE:
return 0.5;
default:
return 1;
}
case Type.GRASS:
switch (attackType) {
case Type.FLYING:
case Type.POISON:
case Type.BUG:
case Type.FIRE:
case Type.ICE:
return 2;
case Type.GROUND:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
return 0.5;
default:
return 1;
}
case Type.ELECTRIC:
switch (attackType) {
case Type.GROUND:
return 2;
case Type.FLYING:
case Type.STEEL:
case Type.ELECTRIC:
return 0.5;
default:
return 1;
}
case Type.PSYCHIC:
switch (attackType) {
case Type.BUG:
case Type.GHOST:
case Type.DARK: case Type.DARK:
return 2; switch (attackType) {
case Type.FIGHTING: case Type.FIGHTING:
case Type.PSYCHIC: case Type.BUG:
return 0.5; case Type.FAIRY:
default: return 2;
return 1; case Type.GHOST:
} case Type.DARK:
case Type.ICE: return 0.5;
switch (attackType) { case Type.PSYCHIC:
case Type.FIGHTING: return 0;
case Type.ROCK: default:
case Type.STEEL: return 1;
case Type.FIRE: }
return 2;
case Type.ICE:
return 0.5;
default:
return 1;
}
case Type.DRAGON:
switch (attackType) {
case Type.ICE:
case Type.DRAGON:
case Type.FAIRY: case Type.FAIRY:
return 2; switch (attackType) {
case Type.FIRE: case Type.POISON:
case Type.WATER: case Type.STEEL:
case Type.GRASS: return 2;
case Type.ELECTRIC: case Type.FIGHTING:
return 0.5; case Type.BUG:
default: case Type.DARK:
return 0.5;
case Type.DRAGON:
return 0;
default:
return 1;
}
case Type.STELLAR:
return 1; return 1;
}
case Type.DARK:
switch (attackType) {
case Type.FIGHTING:
case Type.BUG:
case Type.FAIRY:
return 2;
case Type.GHOST:
case Type.DARK:
return 0.5;
case Type.PSYCHIC:
return 0;
default:
return 1;
}
case Type.FAIRY:
switch (attackType) {
case Type.POISON:
case Type.STEEL:
return 2;
case Type.FIGHTING:
case Type.BUG:
case Type.DARK:
return 0.5;
case Type.DRAGON:
return 0;
default:
return 1;
}
case Type.STELLAR:
return 1;
} }
return 1; return 1;
@ -295,86 +295,86 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa
export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined {
if (side === "offense") { if (side === "offense") {
switch (multiplier) { switch (multiplier) {
case 0: case 0:
return "#929292"; return "#929292";
case 0.125: case 0.125:
return "#FF5500"; return "#FF5500";
case 0.25: case 0.25:
return "#FF7400"; return "#FF7400";
case 0.5: case 0.5:
return "#FE8E00"; return "#FE8E00";
case 1: case 1:
return undefined; return undefined;
case 2: case 2:
return "#4AA500"; return "#4AA500";
case 4: case 4:
return "#4BB400"; return "#4BB400";
case 8: case 8:
return "#52C200"; return "#52C200";
} }
} else if (side === "defense") { } else if (side === "defense") {
switch (multiplier) { switch (multiplier) {
case 0: case 0:
return "#B1B100"; return "#B1B100";
case 0.125: case 0.125:
return "#2DB4FF"; return "#2DB4FF";
case 0.25: case 0.25:
return "#00A4FF"; return "#00A4FF";
case 0.5: case 0.5:
return "#0093FF"; return "#0093FF";
case 1: case 1:
return undefined; return undefined;
case 2: case 2:
return "#FE8E00"; return "#FE8E00";
case 4: case 4:
return "#FF7400"; return "#FF7400";
case 8: case 8:
return "#FF5500"; return "#FF5500";
} }
} }
} }
export function getTypeRgb(type: Type): [ integer, integer, integer ] { export function getTypeRgb(type: Type): [ integer, integer, integer ] {
switch (type) { switch (type) {
case Type.NORMAL: case Type.NORMAL:
return [ 168, 168, 120 ]; return [ 168, 168, 120 ];
case Type.FIGHTING: case Type.FIGHTING:
return [ 192, 48, 40 ]; return [ 192, 48, 40 ];
case Type.FLYING: case Type.FLYING:
return [ 168, 144, 240 ]; return [ 168, 144, 240 ];
case Type.POISON: case Type.POISON:
return [ 160, 64, 160 ]; return [ 160, 64, 160 ];
case Type.GROUND: case Type.GROUND:
return [ 224, 192, 104 ]; return [ 224, 192, 104 ];
case Type.ROCK: case Type.ROCK:
return [ 184, 160, 56 ]; return [ 184, 160, 56 ];
case Type.BUG: case Type.BUG:
return [ 168, 184, 32 ]; return [ 168, 184, 32 ];
case Type.GHOST: case Type.GHOST:
return [ 112, 88, 152 ]; return [ 112, 88, 152 ];
case Type.STEEL: case Type.STEEL:
return [ 184, 184, 208 ]; return [ 184, 184, 208 ];
case Type.FIRE: case Type.FIRE:
return [ 240, 128, 48 ]; return [ 240, 128, 48 ];
case Type.WATER: case Type.WATER:
return [ 104, 144, 240 ]; return [ 104, 144, 240 ];
case Type.GRASS: case Type.GRASS:
return [ 120, 200, 80 ]; return [ 120, 200, 80 ];
case Type.ELECTRIC: case Type.ELECTRIC:
return [ 248, 208, 48 ]; return [ 248, 208, 48 ];
case Type.PSYCHIC: case Type.PSYCHIC:
return [ 248, 88, 136 ]; return [ 248, 88, 136 ];
case Type.ICE: case Type.ICE:
return [ 152, 216, 216 ]; return [ 152, 216, 216 ];
case Type.DRAGON: case Type.DRAGON:
return [ 112, 56, 248 ]; return [ 112, 56, 248 ];
case Type.DARK: case Type.DARK:
return [ 112, 88, 72 ]; return [ 112, 88, 72 ];
case Type.FAIRY: case Type.FAIRY:
return [ 232, 136, 200 ]; return [ 232, 136, 200 ];
case Type.STELLAR: case Type.STELLAR:
return [ 255, 255, 255 ]; return [ 255, 255, 255 ];
default: default:
return [ 0, 0, 0 ]; return [ 0, 0, 0 ];
} }
} }

View File

@ -10,22 +10,22 @@ export const variantColorCache = {};
export function getVariantTint(variant: Variant): integer { export function getVariantTint(variant: Variant): integer {
switch (variant) { switch (variant) {
case 0: case 0:
return 0xf8c020; return 0xf8c020;
case 1: case 1:
return 0x20f8f0; return 0x20f8f0;
case 2: case 2:
return 0xe81048; return 0xe81048;
} }
} }
export function getVariantIcon(variant: Variant): integer { export function getVariantIcon(variant: Variant): integer {
switch (variant) { switch (variant) {
case 0: case 0:
return VariantTier.STANDARD; return VariantTier.STANDARD;
case 1: case 1:
return VariantTier.RARE; return VariantTier.RARE;
case 2: case 2:
return VariantTier.EPIC; return VariantTier.EPIC;
} }
} }

View File

@ -33,10 +33,10 @@ export class Weather {
isImmutable(): boolean { isImmutable(): boolean {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
case WeatherType.STRONG_WINDS: case WeatherType.STRONG_WINDS:
return true; return true;
} }
return false; return false;
@ -44,9 +44,9 @@ export class Weather {
isDamaging(): boolean { isDamaging(): boolean {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
case WeatherType.HAIL: case WeatherType.HAIL:
return true; return true;
} }
return false; return false;
@ -54,10 +54,10 @@ export class Weather {
isTypeDamageImmune(type: Type): boolean { isTypeDamageImmune(type: Type): boolean {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL;
case WeatherType.HAIL: case WeatherType.HAIL:
return type === Type.ICE; return type === Type.ICE;
} }
return false; return false;
@ -65,24 +65,24 @@ export class Weather {
getAttackTypeMultiplier(attackType: Type): number { getAttackTypeMultiplier(attackType: Type): number {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
if (attackType === Type.FIRE) { if (attackType === Type.FIRE) {
return 1.5; return 1.5;
} }
if (attackType === Type.WATER) { if (attackType === Type.WATER) {
return 0.5; return 0.5;
} }
break; break;
case WeatherType.RAIN: case WeatherType.RAIN:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
if (attackType === Type.FIRE) { if (attackType === Type.FIRE) {
return 0.5; return 0.5;
} }
if (attackType === Type.WATER) { if (attackType === Type.WATER) {
return 1.5; return 1.5;
} }
break; break;
} }
return 1; return 1;
@ -92,10 +92,10 @@ export class Weather {
const moveType = user.getMoveType(move); const moveType = user.getMoveType(move);
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return move instanceof AttackMove && moveType === Type.WATER; return move instanceof AttackMove && moveType === Type.WATER;
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return move instanceof AttackMove && moveType === Type.FIRE; return move instanceof AttackMove && moveType === Type.FIRE;
} }
return false; return false;
@ -120,24 +120,24 @@ export class Weather {
export function getWeatherStartMessage(weatherType: WeatherType): string | null { export function getWeatherStartMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyStartMessage"); return i18next.t("weather:sunnyStartMessage");
case WeatherType.RAIN: case WeatherType.RAIN:
return i18next.t("weather:rainStartMessage"); return i18next.t("weather:rainStartMessage");
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormStartMessage"); return i18next.t("weather:sandstormStartMessage");
case WeatherType.HAIL: case WeatherType.HAIL:
return i18next.t("weather:hailStartMessage"); return i18next.t("weather:hailStartMessage");
case WeatherType.SNOW: case WeatherType.SNOW:
return i18next.t("weather:snowStartMessage"); return i18next.t("weather:snowStartMessage");
case WeatherType.FOG: case WeatherType.FOG:
return i18next.t("weather:fogStartMessage"); return i18next.t("weather:fogStartMessage");
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainStartMessage"); return i18next.t("weather:heavyRainStartMessage");
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return i18next.t("weather:harshSunStartMessage"); return i18next.t("weather:harshSunStartMessage");
case WeatherType.STRONG_WINDS: case WeatherType.STRONG_WINDS:
return i18next.t("weather:strongWindsStartMessage"); return i18next.t("weather:strongWindsStartMessage");
} }
return null; return null;
@ -145,24 +145,24 @@ export function getWeatherStartMessage(weatherType: WeatherType): string | null
export function getWeatherLapseMessage(weatherType: WeatherType): string | null { export function getWeatherLapseMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyLapseMessage"); return i18next.t("weather:sunnyLapseMessage");
case WeatherType.RAIN: case WeatherType.RAIN:
return i18next.t("weather:rainLapseMessage"); return i18next.t("weather:rainLapseMessage");
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormLapseMessage"); return i18next.t("weather:sandstormLapseMessage");
case WeatherType.HAIL: case WeatherType.HAIL:
return i18next.t("weather:hailLapseMessage"); return i18next.t("weather:hailLapseMessage");
case WeatherType.SNOW: case WeatherType.SNOW:
return i18next.t("weather:snowLapseMessage"); return i18next.t("weather:snowLapseMessage");
case WeatherType.FOG: case WeatherType.FOG:
return i18next.t("weather:fogLapseMessage"); return i18next.t("weather:fogLapseMessage");
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainLapseMessage"); return i18next.t("weather:heavyRainLapseMessage");
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return i18next.t("weather:harshSunLapseMessage"); return i18next.t("weather:harshSunLapseMessage");
case WeatherType.STRONG_WINDS: case WeatherType.STRONG_WINDS:
return i18next.t("weather:strongWindsLapseMessage"); return i18next.t("weather:strongWindsLapseMessage");
} }
return null; return null;
@ -170,10 +170,10 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null { export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
case WeatherType.HAIL: case WeatherType.HAIL:
return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
} }
return null; return null;
@ -181,24 +181,24 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem
export function getWeatherClearMessage(weatherType: WeatherType): string | null { export function getWeatherClearMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyClearMessage"); return i18next.t("weather:sunnyClearMessage");
case WeatherType.RAIN: case WeatherType.RAIN:
return i18next.t("weather:rainClearMessage"); return i18next.t("weather:rainClearMessage");
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormClearMessage"); return i18next.t("weather:sandstormClearMessage");
case WeatherType.HAIL: case WeatherType.HAIL:
return i18next.t("weather:hailClearMessage"); return i18next.t("weather:hailClearMessage");
case WeatherType.SNOW: case WeatherType.SNOW:
return i18next.t("weather:snowClearMessage"); return i18next.t("weather:snowClearMessage");
case WeatherType.FOG: case WeatherType.FOG:
return i18next.t("weather:fogClearMessage"); return i18next.t("weather:fogClearMessage");
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainClearMessage"); return i18next.t("weather:heavyRainClearMessage");
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return i18next.t("weather:harshSunClearMessage"); return i18next.t("weather:harshSunClearMessage");
case WeatherType.STRONG_WINDS: case WeatherType.STRONG_WINDS:
return i18next.t("weather:strongWindsClearMessage"); return i18next.t("weather:strongWindsClearMessage");
} }
return null; return null;
@ -206,33 +206,33 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null
export function getTerrainStartMessage(terrainType: TerrainType): string | null { export function getTerrainStartMessage(terrainType: TerrainType): string | null {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyStartMessage"); return i18next.t("terrain:mistyStartMessage");
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
return i18next.t("terrain:electricStartMessage"); return i18next.t("terrain:electricStartMessage");
case TerrainType.GRASSY: case TerrainType.GRASSY:
return i18next.t("terrain:grassyStartMessage"); return i18next.t("terrain:grassyStartMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicStartMessage"); return i18next.t("terrain:psychicStartMessage");
default: default:
console.warn("getTerrainStartMessage not defined. Using default null"); console.warn("getTerrainStartMessage not defined. Using default null");
return null; return null;
} }
} }
export function getTerrainClearMessage(terrainType: TerrainType): string | null { export function getTerrainClearMessage(terrainType: TerrainType): string | null {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyClearMessage"); return i18next.t("terrain:mistyClearMessage");
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
return i18next.t("terrain:electricClearMessage"); return i18next.t("terrain:electricClearMessage");
case TerrainType.GRASSY: case TerrainType.GRASSY:
return i18next.t("terrain:grassyClearMessage"); return i18next.t("terrain:grassyClearMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicClearMessage"); return i18next.t("terrain:psychicClearMessage");
default: default:
console.warn("getTerrainClearMessage not defined. Using default null"); console.warn("getTerrainClearMessage not defined. Using default null");
return null; return null;
} }
} }
@ -252,126 +252,126 @@ export function getRandomWeatherType(arena: any /* Importing from arena causes a
let weatherPool: WeatherPoolEntry[] = []; let weatherPool: WeatherPoolEntry[] = [];
const hasSun = arena.getTimeOfDay() < 2; const hasSun = arena.getTimeOfDay() < 2;
switch (arena.biomeType) { switch (arena.biomeType) {
case Biome.GRASS: case Biome.GRASS:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 7 } { weatherType: WeatherType.NONE, weight: 7 }
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 });
} }
break; break;
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 5 }, { weatherType: WeatherType.RAIN, weight: 5 },
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 });
} }
break; break;
case Biome.FOREST: case Biome.FOREST:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 5 } { weatherType: WeatherType.RAIN, weight: 5 }
]; ];
break; break;
case Biome.SEA: case Biome.SEA:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.RAIN, weight: 12 } { weatherType: WeatherType.RAIN, weight: 12 }
]; ];
break; break;
case Biome.SWAMP: case Biome.SWAMP:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.RAIN, weight: 4 }, { weatherType: WeatherType.RAIN, weight: 4 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 }
]; ];
break; break;
case Biome.BEACH: case Biome.BEACH:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 3 } { weatherType: WeatherType.RAIN, weight: 3 }
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
} }
break; break;
case Biome.LAKE: case Biome.LAKE:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 10 }, { weatherType: WeatherType.NONE, weight: 10 },
{ weatherType: WeatherType.RAIN, weight: 5 }, { weatherType: WeatherType.RAIN, weight: 5 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 }
]; ];
break; break;
case Biome.SEABED: case Biome.SEABED:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.RAIN, weight: 1 } { weatherType: WeatherType.RAIN, weight: 1 }
]; ];
break; break;
case Biome.BADLANDS: case Biome.BADLANDS:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.SANDSTORM, weight: 2 } { weatherType: WeatherType.SANDSTORM, weight: 2 }
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
} }
break; break;
case Biome.DESERT: case Biome.DESERT:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.SANDSTORM, weight: 2 } { weatherType: WeatherType.SANDSTORM, weight: 2 }
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
break; break;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.SNOW, weight: 4 }, { weatherType: WeatherType.SNOW, weight: 4 },
{ weatherType: WeatherType.HAIL, weight: 1 } { weatherType: WeatherType.HAIL, weight: 1 }
]; ];
break; break;
case Biome.MEADOW: case Biome.MEADOW:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 2 } { weatherType: WeatherType.NONE, weight: 2 }
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
case Biome.VOLCANO: case Biome.VOLCANO:
weatherPool = [ weatherPool = [
{ weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 }
]; ];
break; break;
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 }
]; ];
break; break;
case Biome.JUNGLE: case Biome.JUNGLE:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 2 } { weatherType: WeatherType.RAIN, weight: 2 }
]; ];
break; break;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.SNOW, weight: 7 }, { weatherType: WeatherType.SNOW, weight: 7 },
{ weatherType: WeatherType.HAIL, weight: 1 } { weatherType: WeatherType.HAIL, weight: 1 }
]; ];
break; break;
case Biome.ISLAND: case Biome.ISLAND:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 5 }, { weatherType: WeatherType.NONE, weight: 5 },
{ weatherType: WeatherType.RAIN, weight: 1 }, { weatherType: WeatherType.RAIN, weight: 1 },
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
break; break;
} }
if (weatherPool.length > 1) { if (weatherPool.length > 1) {

View File

@ -116,6 +116,8 @@ export enum TrainerType {
VITO, VITO,
BUG_TYPE_SUPERFAN, BUG_TYPE_SUPERFAN,
EXPERT_POKEMON_BREEDER, EXPERT_POKEMON_BREEDER,
FUTURE_SELF_M,
FUTURE_SELF_F,
BROCK = 200, BROCK = 200,
MISTY, MISTY,

View File

@ -4,21 +4,21 @@ import * as Utils from "../utils";
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
switch (pokeballType) { switch (pokeballType) {
case PokeballType.POKEBALL: case PokeballType.POKEBALL:
doDefaultPbOpenParticles(scene, x, y, 48); doDefaultPbOpenParticles(scene, x, y, 48);
break; break;
case PokeballType.GREAT_BALL: case PokeballType.GREAT_BALL:
doDefaultPbOpenParticles(scene, x, y, 96); doDefaultPbOpenParticles(scene, x, y, 96);
break; break;
case PokeballType.ULTRA_BALL: case PokeballType.ULTRA_BALL:
doUbOpenParticles(scene, x, y, 8); doUbOpenParticles(scene, x, y, 8);
break; break;
case PokeballType.ROGUE_BALL: case PokeballType.ROGUE_BALL:
doUbOpenParticles(scene, x, y, 10); doUbOpenParticles(scene, x, y, 10);
break; break;
case PokeballType.MASTER_BALL: case PokeballType.MASTER_BALL:
doMbOpenParticles(scene, x, y); doMbOpenParticles(scene, x, y);
break; break;
} }
} }

View File

@ -129,18 +129,18 @@ export class Arena {
if (ret.subLegendary || ret.legendary || ret.mythical) { if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) { switch (true) {
case (ret.baseTotal >= 720): case (ret.baseTotal >= 720):
regen = level < 90; regen = level < 90;
break; break;
case (ret.baseTotal >= 670): case (ret.baseTotal >= 670):
regen = level < 70; regen = level < 70;
break; break;
case (ret.baseTotal >= 580): case (ret.baseTotal >= 580):
regen = level < 50; regen = level < 50;
break; break;
default: default:
regen = level < 30; regen = level < 30;
break; break;
} }
} }
} }
@ -177,41 +177,41 @@ export class Arena {
getSpeciesFormIndex(species: PokemonSpecies): integer { getSpeciesFormIndex(species: PokemonSpecies): integer {
switch (species.speciesId) { switch (species.speciesId) {
case Species.BURMY: case Species.BURMY:
case Species.WORMADAM: case Species.WORMADAM:
switch (this.biomeType) { switch (this.biomeType) {
case Biome.BEACH: case Biome.BEACH:
return 1; return 1;
case Biome.SLUM: case Biome.SLUM:
return 2; return 2;
} }
break; break;
case Species.ROTOM: case Species.ROTOM:
switch (this.biomeType) { switch (this.biomeType) {
case Biome.VOLCANO: case Biome.VOLCANO:
return 1; return 1;
case Biome.SEA: case Biome.SEA:
return 2; return 2;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
return 3; return 3;
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
return 4; return 4;
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
return 5; return 5;
} }
break; break;
case Species.LYCANROC: case Species.LYCANROC:
const timeOfDay = this.getTimeOfDay(); const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) { switch (timeOfDay) {
case TimeOfDay.DAY: case TimeOfDay.DAY:
case TimeOfDay.DAWN: case TimeOfDay.DAWN:
return 0; return 0;
case TimeOfDay.DUSK: case TimeOfDay.DUSK:
return 2; return 2;
case TimeOfDay.NIGHT: case TimeOfDay.NIGHT:
return 1; return 1;
} }
break; break;
} }
return 0; return 0;
@ -219,70 +219,70 @@ export class Arena {
getTypeForBiome() { getTypeForBiome() {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.TOWN: case Biome.TOWN:
case Biome.PLAINS: case Biome.PLAINS:
case Biome.METROPOLIS: case Biome.METROPOLIS:
return Type.NORMAL; return Type.NORMAL;
case Biome.GRASS: case Biome.GRASS:
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
return Type.GRASS; return Type.GRASS;
case Biome.FOREST: case Biome.FOREST:
case Biome.JUNGLE: case Biome.JUNGLE:
return Type.BUG; return Type.BUG;
case Biome.SLUM: case Biome.SLUM:
case Biome.SWAMP: case Biome.SWAMP:
return Type.POISON; return Type.POISON;
case Biome.SEA: case Biome.SEA:
case Biome.BEACH: case Biome.BEACH:
case Biome.LAKE: case Biome.LAKE:
case Biome.SEABED: case Biome.SEABED:
return Type.WATER; return Type.WATER;
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
return Type.FLYING; return Type.FLYING;
case Biome.BADLANDS: case Biome.BADLANDS:
return Type.GROUND; return Type.GROUND;
case Biome.CAVE: case Biome.CAVE:
case Biome.DESERT: case Biome.DESERT:
return Type.ROCK; return Type.ROCK;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return Type.ICE; return Type.ICE;
case Biome.MEADOW: case Biome.MEADOW:
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
case Biome.ISLAND: case Biome.ISLAND:
return Type.FAIRY; return Type.FAIRY;
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
return Type.ELECTRIC; return Type.ELECTRIC;
case Biome.VOLCANO: case Biome.VOLCANO:
return Type.FIRE; return Type.FIRE;
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
case Biome.TEMPLE: case Biome.TEMPLE:
return Type.GHOST; return Type.GHOST;
case Biome.DOJO: case Biome.DOJO:
case Biome.CONSTRUCTION_SITE: case Biome.CONSTRUCTION_SITE:
return Type.FIGHTING; return Type.FIGHTING;
case Biome.FACTORY: case Biome.FACTORY:
case Biome.LABORATORY: case Biome.LABORATORY:
return Type.STEEL; return Type.STEEL;
case Biome.RUINS: case Biome.RUINS:
case Biome.SPACE: case Biome.SPACE:
return Type.PSYCHIC; return Type.PSYCHIC;
case Biome.WASTELAND: case Biome.WASTELAND:
case Biome.END: case Biome.END:
return Type.DRAGON; return Type.DRAGON;
case Biome.ABYSS: case Biome.ABYSS:
return Type.DARK; return Type.DARK;
default: default:
return Type.UNKNOWN; return Type.UNKNOWN;
} }
} }
getBgTerrainColorRatioForBiome(): number { getBgTerrainColorRatioForBiome(): number {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.SPACE: case Biome.SPACE:
return 1; return 1;
case Biome.END: case Biome.END:
return 0; return 0;
} }
return 131 / 180; return 131 / 180;
@ -424,52 +424,52 @@ export class Arena {
*/ */
getTrainerChance(): integer { getTrainerChance(): integer {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.METROPOLIS: case Biome.METROPOLIS:
return 2; return 2;
case Biome.SLUM: case Biome.SLUM:
case Biome.BEACH: case Biome.BEACH:
case Biome.DOJO: case Biome.DOJO:
case Biome.CONSTRUCTION_SITE: case Biome.CONSTRUCTION_SITE:
return 4; return 4;
case Biome.PLAINS: case Biome.PLAINS:
case Biome.GRASS: case Biome.GRASS:
case Biome.LAKE: case Biome.LAKE:
case Biome.CAVE: case Biome.CAVE:
return 6; return 6;
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
case Biome.FOREST: case Biome.FOREST:
case Biome.SEA: case Biome.SEA:
case Biome.SWAMP: case Biome.SWAMP:
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
case Biome.BADLANDS: case Biome.BADLANDS:
case Biome.DESERT: case Biome.DESERT:
case Biome.MEADOW: case Biome.MEADOW:
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
case Biome.FACTORY: case Biome.FACTORY:
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return 8; return 8;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
case Biome.VOLCANO: case Biome.VOLCANO:
case Biome.RUINS: case Biome.RUINS:
case Biome.WASTELAND: case Biome.WASTELAND:
case Biome.JUNGLE: case Biome.JUNGLE:
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
return 12; return 12;
case Biome.SEABED: case Biome.SEABED:
case Biome.ABYSS: case Biome.ABYSS:
case Biome.SPACE: case Biome.SPACE:
case Biome.TEMPLE: case Biome.TEMPLE:
return 16; return 16;
default: default:
return 0; return 0;
} }
} }
getTimeOfDay(): TimeOfDay { getTimeOfDay(): TimeOfDay {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.ABYSS: case Biome.ABYSS:
return TimeOfDay.NIGHT; return TimeOfDay.NIGHT;
} }
const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40;
@ -491,35 +491,35 @@ export class Arena {
isOutside(): boolean { isOutside(): boolean {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.SEABED: case Biome.SEABED:
case Biome.CAVE: case Biome.CAVE:
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
case Biome.DOJO: case Biome.DOJO:
case Biome.FACTORY: case Biome.FACTORY:
case Biome.ABYSS: case Biome.ABYSS:
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
case Biome.TEMPLE: case Biome.TEMPLE:
case Biome.LABORATORY: case Biome.LABORATORY:
return false; return false;
default: default:
return true; return true;
} }
} }
overrideTint(): [integer, integer, integer] { overrideTint(): [integer, integer, integer] {
switch (Overrides.ARENA_TINT_OVERRIDE) { switch (Overrides.ARENA_TINT_OVERRIDE) {
case TimeOfDay.DUSK: case TimeOfDay.DUSK:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
break; break;
case (TimeOfDay.NIGHT): case (TimeOfDay.NIGHT):
return [ 64, 64, 64 ]; return [ 64, 64, 64 ];
break; break;
case TimeOfDay.DAWN: case TimeOfDay.DAWN:
case TimeOfDay.DAY: case TimeOfDay.DAY:
default: default:
return [ 128, 128, 128 ]; return [ 128, 128, 128 ];
break; break;
} }
} }
@ -528,10 +528,10 @@ export class Arena {
return this.overrideTint(); return this.overrideTint();
} }
switch (this.biomeType) { switch (this.biomeType) {
case Biome.ABYSS: case Biome.ABYSS:
return [ 64, 64, 64 ]; return [ 64, 64, 64 ];
default: default:
return [ 128, 128, 128 ]; return [ 128, 128, 128 ];
} }
} }
@ -544,8 +544,8 @@ export class Arena {
} }
switch (this.biomeType) { switch (this.biomeType) {
default: default:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
} }
} }
@ -554,10 +554,10 @@ export class Arena {
return this.overrideTint(); return this.overrideTint();
} }
switch (this.biomeType) { switch (this.biomeType) {
case Biome.ABYSS: case Biome.ABYSS:
case Biome.SPACE: case Biome.SPACE:
case Biome.END: case Biome.END:
return this.getDayTint(); return this.getDayTint();
} }
if (!this.isOutside()) { if (!this.isOutside()) {
@ -565,8 +565,8 @@ export class Arena {
} }
switch (this.biomeType) { switch (this.biomeType) {
default: default:
return [ 48, 48, 98 ]; return [ 48, 48, 98 ];
} }
} }
@ -747,77 +747,77 @@ export class Arena {
getBgmLoopPoint(): number { getBgmLoopPoint(): number {
switch (this.biomeType) { switch (this.biomeType) {
case Biome.TOWN: case Biome.TOWN:
return 7.288; return 7.288;
case Biome.PLAINS: case Biome.PLAINS:
return 17.485; return 17.485;
case Biome.GRASS: case Biome.GRASS:
return 1.995; return 1.995;
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
return 9.608; return 9.608;
case Biome.METROPOLIS: case Biome.METROPOLIS:
return 141.470; return 141.470;
case Biome.FOREST: case Biome.FOREST:
return 4.294; return 4.294;
case Biome.SEA: case Biome.SEA:
return 0.024; return 0.024;
case Biome.SWAMP: case Biome.SWAMP:
return 4.461; return 4.461;
case Biome.BEACH: case Biome.BEACH:
return 3.462; return 3.462;
case Biome.LAKE: case Biome.LAKE:
return 7.215; return 7.215;
case Biome.SEABED: case Biome.SEABED:
return 2.600; return 2.600;
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
return 4.018; return 4.018;
case Biome.BADLANDS: case Biome.BADLANDS:
return 17.790; return 17.790;
case Biome.CAVE: case Biome.CAVE:
return 14.240; return 14.240;
case Biome.DESERT: case Biome.DESERT:
return 1.143; return 1.143;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
return 0.000; return 0.000;
case Biome.MEADOW: case Biome.MEADOW:
return 3.891; return 3.891;
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
return 9.447; return 9.447;
case Biome.VOLCANO: case Biome.VOLCANO:
return 17.637; return 17.637;
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
return 3.232; return 3.232;
case Biome.DOJO: case Biome.DOJO:
return 6.205; return 6.205;
case Biome.FACTORY: case Biome.FACTORY:
return 4.985; return 4.985;
case Biome.RUINS: case Biome.RUINS:
return 0.000; return 0.000;
case Biome.WASTELAND: case Biome.WASTELAND:
return 6.336; return 6.336;
case Biome.ABYSS: case Biome.ABYSS:
return 5.130; return 5.130;
case Biome.SPACE: case Biome.SPACE:
return 20.036; return 20.036;
case Biome.CONSTRUCTION_SITE: case Biome.CONSTRUCTION_SITE:
return 1.222; return 1.222;
case Biome.JUNGLE: case Biome.JUNGLE:
return 0.000; return 0.000;
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
return 4.542; return 4.542;
case Biome.TEMPLE: case Biome.TEMPLE:
return 2.547; return 2.547;
case Biome.ISLAND: case Biome.ISLAND:
return 2.751; return 2.751;
case Biome.LABORATORY: case Biome.LABORATORY:
return 114.862; return 114.862;
case Biome.SLUM: case Biome.SLUM:
return 0.000; return 0.000;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return 3.047; return 3.047;
default: default:
console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`);
return 0; return 0;
} }
} }
} }
@ -828,32 +828,32 @@ export function getBiomeKey(biome: Biome): string {
export function getBiomeHasProps(biomeType: Biome): boolean { export function getBiomeHasProps(biomeType: Biome): boolean {
switch (biomeType) { switch (biomeType) {
case Biome.METROPOLIS: case Biome.METROPOLIS:
case Biome.BEACH: case Biome.BEACH:
case Biome.LAKE: case Biome.LAKE:
case Biome.SEABED: case Biome.SEABED:
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
case Biome.BADLANDS: case Biome.BADLANDS:
case Biome.CAVE: case Biome.CAVE:
case Biome.DESERT: case Biome.DESERT:
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
case Biome.MEADOW: case Biome.MEADOW:
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
case Biome.VOLCANO: case Biome.VOLCANO:
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
case Biome.FACTORY: case Biome.FACTORY:
case Biome.RUINS: case Biome.RUINS:
case Biome.WASTELAND: case Biome.WASTELAND:
case Biome.ABYSS: case Biome.ABYSS:
case Biome.CONSTRUCTION_SITE: case Biome.CONSTRUCTION_SITE:
case Biome.JUNGLE: case Biome.JUNGLE:
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
case Biome.TEMPLE: case Biome.TEMPLE:
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
case Biome.ISLAND: case Biome.ISLAND:
case Biome.LABORATORY: case Biome.LABORATORY:
case Biome.END: case Biome.END:
return true; return true;
} }
return false; return false;

View File

@ -29,21 +29,21 @@ export default class DamageNumberHandler {
let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ]; let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ];
switch (result) { switch (result) {
case HitResult.SUPER_EFFECTIVE: case HitResult.SUPER_EFFECTIVE:
[ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ];
break; break;
case HitResult.NOT_VERY_EFFECTIVE: case HitResult.NOT_VERY_EFFECTIVE:
[ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; [ textColor, shadowColor ] = [ "#f08030", "#c03028" ];
break; break;
case HitResult.ONE_HIT_KO: case HitResult.ONE_HIT_KO:
[ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; [ textColor, shadowColor ] = [ "#a040a0", "#483850" ];
break; break;
case HitResult.HEAL: case HitResult.HEAL:
[ textColor, shadowColor ] = [ "#78c850", "#588040" ]; [ textColor, shadowColor ] = [ "#78c850", "#588040" ];
break; break;
default: default:
[ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; [ textColor, shadowColor ] = [ "#ffffff", "#636363" ];
break; break;
} }
if (textColor) { if (textColor) {

View File

@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "#app/battle-scene";
import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives"; import { starterPassiveAbilities } from "#app/data/balance/passives";
@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
import { WeatherType } from "#app/data/weather"; import { WeatherType } from "#app/data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr } from "#app/data/ability";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
@ -93,7 +93,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public stats: integer[]; public stats: integer[];
public ivs: integer[]; public ivs: integer[];
public nature: Nature; public nature: Nature;
public natureOverride: Nature | -1;
public moveset: (PokemonMove | null)[]; public moveset: (PokemonMove | null)[];
public status: Status | null; public status: Status | null;
public friendship: integer; public friendship: integer;
@ -114,7 +113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionVariant: Variant; public fusionVariant: Variant;
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: integer; public fusionLuck: integer;
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null; public fusionCustomPokemonData: CustomPokemonData | null;
private summonDataPrimer: PokemonSummonData | null; private summonDataPrimer: PokemonSummonData | null;
@ -122,7 +121,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public battleData: PokemonBattleData; public battleData: PokemonBattleData;
public battleSummonData: PokemonBattleSummonData; public battleSummonData: PokemonBattleSummonData;
public turnData: PokemonTurnData; public turnData: PokemonTurnData;
public mysteryEncounterPokemonData: MysteryEncounterPokemonData; public customPokemonData: CustomPokemonData;
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */ /** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
@ -193,7 +192,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.nature = dataSource.nature || 0 as Nature; this.nature = dataSource.nature || 0 as Nature;
this.nickname = dataSource.nickname; this.nickname = dataSource.nickname;
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
this.moveset = dataSource.moveset; this.moveset = dataSource.moveset;
this.status = dataSource.status!; // TODO: is this bang correct? this.status = dataSource.status!; // TODO: is this bang correct?
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
@ -212,9 +210,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = dataSource.fusionVariant || 0; this.fusionVariant = dataSource.fusionVariant || 0;
this.fusionGender = dataSource.fusionGender; this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck; this.fusionLuck = dataSource.fusionLuck;
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData; this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData;
this.usedTMs = dataSource.usedTMs ?? []; this.usedTMs = dataSource.usedTMs ?? [];
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData); this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData);
} else { } else {
this.id = Utils.randSeedInt(4294967296); this.id = Utils.randSeedInt(4294967296);
this.ivs = ivs || Utils.getIvsFromId(this.id); this.ivs = ivs || Utils.getIvsFromId(this.id);
@ -235,7 +233,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.variant = this.shiny ? this.generateVariant() : 0; this.variant = this.shiny ? this.generateVariant() : 0;
} }
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); this.customPokemonData = new CustomPokemonData();
if (nature !== undefined) { if (nature !== undefined) {
this.setNature(nature); this.setNature(nature);
@ -243,8 +241,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.generateNature(); this.generateNature();
} }
this.natureOverride = -1;
this.friendship = species.baseFriendship; this.friendship = species.baseFriendship;
this.metLevel = level; this.metLevel = level;
this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1;
@ -593,8 +589,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const formKey = this.getFormKey(); const formKey = this.getFormKey();
if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") { if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") {
return 1.5; return 1.5;
} else if (this.mysteryEncounterPokemonData.spriteScale > 0) { } else if (this.customPokemonData.spriteScale > 0) {
return this.mysteryEncounterPokemonData.spriteScale; return this.customPokemonData.spriteScale;
} }
return 1; return 1;
} }
@ -680,12 +676,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getFieldPositionOffset(): [ number, number ] { getFieldPositionOffset(): [ number, number ] {
switch (this.fieldPosition) { switch (this.fieldPosition) {
case FieldPosition.CENTER: case FieldPosition.CENTER:
return [ 0, 0 ]; return [ 0, 0 ];
case FieldPosition.LEFT: case FieldPosition.LEFT:
return [ -32, -8 ]; return [ -32, -8 ];
case FieldPosition.RIGHT: case FieldPosition.RIGHT:
return [ 32, 0 ]; return [ 32, 0 ];
} }
} }
@ -917,39 +913,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated); let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated);
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
if (this.getTag(BattlerTagType.SLOW_START)) { if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1; ret >>= 1;
} }
break; break;
case Stat.DEF: case Stat.DEF:
if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) {
ret *= 1.5; ret *= 1.5;
} }
break; break;
case Stat.SPATK: case Stat.SPATK:
break; break;
case Stat.SPDEF: case Stat.SPDEF:
if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
ret *= 1.5; ret *= 1.5;
} }
break; break;
case Stat.SPD: case Stat.SPD:
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) {
ret *= 2; ret *= 2;
} }
if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) {
ret >>= 2; ret >>= 2;
} }
if (this.getTag(BattlerTagType.SLOW_START)) { if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1; ret >>= 1;
} }
if (this.status && this.status.effect === StatusEffect.PARALYSIS) { if (this.status && this.status.effect === StatusEffect.PARALYSIS) {
ret >>= 1; ret >>= 1;
} }
break; break;
} }
const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag; const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag;
@ -1023,7 +1019,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getNature(): Nature { getNature(): Nature {
return this.natureOverride !== -1 ? this.natureOverride : this.nature; return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature;
} }
setNature(nature: Nature): void { setNature(nature: Nature): void {
@ -1198,15 +1194,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!types.length || !includeTeraType) { if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) { if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
this.summonData.types.forEach(t => types.push(t)); this.summonData.types.forEach(t => types.push(t));
} else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) { } else if (this.customPokemonData.types && this.customPokemonData.types.length > 0) {
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters // "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
types.push(this.mysteryEncounterPokemonData.types[0]); types.push(this.customPokemonData.types[0]);
// Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal // Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
if (fusionSpeciesForm) { if (fusionSpeciesForm) {
// Check if the fusion Pokemon also had "permanently changed" types // Check if the fusion Pokemon also had "permanently changed" types
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; const fusionMETypes = this.fusionCustomPokemonData?.types;
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
types.push(fusionMETypes[1]); types.push(fusionMETypes[1]);
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
@ -1218,8 +1214,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) { if (types.length === 1 && this.customPokemonData.types.length >= 2) {
types.push(this.mysteryEncounterPokemonData.types[1]); types.push(this.customPokemonData.types[1]);
} }
} else { } else {
const speciesForm = this.getSpeciesForm(ignoreOverride); const speciesForm = this.getSpeciesForm(ignoreOverride);
@ -1230,7 +1226,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (fusionSpeciesForm) { if (fusionSpeciesForm) {
// Check if the fusion Pokemon also had "permanently changed" types // Check if the fusion Pokemon also had "permanently changed" types
// Otherwise, use standard fusion type logic // Otherwise, use standard fusion type logic
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; const fusionMETypes = this.fusionCustomPokemonData?.types;
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
types.push(fusionMETypes[1]); types.push(fusionMETypes[1]);
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
@ -1262,6 +1258,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
if (types.length > 1 && types[0] === types[1]) {
types.splice(0, 1);
}
return types; return types;
} }
@ -1288,14 +1289,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
} }
if (this.isFusion()) { if (this.isFusion()) {
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) { if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) {
return allAbilities[this.fusionMysteryEncounterPokemonData.ability]; return allAbilities[this.fusionCustomPokemonData.ability];
} else { } else {
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
} }
} }
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) { if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.ability]; 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) { if (abilityId === Abilities.NONE) {
@ -1318,8 +1319,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
} }
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) { if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.passive]; return allAbilities[this.customPokemonData.passive];
} }
let starterSpeciesId = this.species.speciesId; let starterSpeciesId = this.species.speciesId;
@ -2018,7 +2019,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = 0; this.fusionVariant = 0;
this.fusionGender = 0; this.fusionGender = 0;
this.fusionLuck = 0; this.fusionLuck = 0;
this.fusionMysteryEncounterPokemonData = null; this.fusionCustomPokemonData = null;
this.generateName(); this.generateName();
this.calculateStats(); this.calculateStats();
@ -2119,7 +2120,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Trainers get a weight bump to stat buffing moves // 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) ]); movePool = movePool.map(m => [ m[0], 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 // Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [ m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1) ]); movePool = movePool.map(m => [ m[0], m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1) ]);
} }
// Weight towards higher power moves, by reducing the power of moves below the highest power. // Weight towards higher power moves, by reducing the power of moves below the highest power.
@ -2187,7 +2188,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.moveset.push(new PokemonMove(movePool[index][0], 0, 0)); this.moveset.push(new PokemonMove(movePool[index][0], 0, 0));
} }
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes
if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) {
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger);
}
} }
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
@ -2285,6 +2289,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
} }
/**
* Compares if `this` and {@linkcode target} are on the same team.
* @param target the {@linkcode Pokemon} to compare against.
* @returns `true` if the two pokemon are allies, `false` otherwise
*/
public isOpponent(target: Pokemon): boolean {
return this.isPlayer() !== target.isPlayer();
}
getOpponent(targetIndex: integer): Pokemon | null { getOpponent(targetIndex: integer): Pokemon | null {
const ret = this.getOpponents()[targetIndex]; const ret = this.getOpponents()[targetIndex];
if (ret.summonData) { if (ret.summonData) {
@ -2338,14 +2351,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (opponent) { if (opponent) {
if (isCritical) { if (isCritical) {
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
case Stat.SPATK: case Stat.SPATK:
statStage.value = Math.max(statStage.value, 0); statStage.value = Math.max(statStage.value, 0);
break; break;
case Stat.DEF: case Stat.DEF:
case Stat.SPDEF: case Stat.SPDEF:
statStage.value = Math.min(statStage.value, 0); statStage.value = Math.min(statStage.value, 0);
break; break;
} }
} }
if (!ignoreOppAbility) { if (!ignoreOppAbility) {
@ -2605,7 +2618,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
const screenMultiplier = new Utils.NumberHolder(1); const screenMultiplier = new Utils.NumberHolder(1);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier); this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier);
/** /**
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
@ -2795,15 +2808,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// want to include is.Fainted() in case multi hit move ends early, still want to render message // want to include is.Fainted() in case multi hit move ends early, still want to render message
if (source.turnData.hitsLeft === 1 || this.isFainted()) { if (source.turnData.hitsLeft === 1 || this.isFainted()) {
switch (result) { switch (result) {
case HitResult.SUPER_EFFECTIVE: case HitResult.SUPER_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective"));
break; break;
case HitResult.NOT_VERY_EFFECTIVE: case HitResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective"));
break; break;
case HitResult.ONE_HIT_KO: case HitResult.ONE_HIT_KO:
this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO"));
break; break;
} }
} }
@ -3347,61 +3360,60 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
const types = this.getTypes(true, true); if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
return false; return false;
} }
switch (effect) { const types = this.getTypes(true, true);
case StatusEffect.POISON:
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
if (defType !== Type.POISON && defType !== Type.STEEL) {
return false;
}
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity switch (effect) {
const cancelImmunity = new Utils.BooleanHolder(false); case StatusEffect.POISON:
if (sourcePokemon) { case StatusEffect.TOXIC:
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity
if (cancelImmunity.value) { const poisonImmunity = types.map(defType => {
// Check if the Pokemon is not immune to Poison/Toxic
if (defType !== Type.POISON && defType !== Type.STEEL) {
return false;
}
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
const cancelImmunity = new Utils.BooleanHolder(false);
if (sourcePokemon) {
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType);
if (cancelImmunity.value) {
return false;
}
}
return true;
});
if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) {
if (poisonImmunity.includes(true)) {
return false; return false;
} }
} }
break;
return true; case StatusEffect.PARALYSIS:
}); if (this.isOfType(Type.ELECTRIC)) {
if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) {
if (poisonImmunity.includes(true)) {
return false; return false;
} }
} break;
break; case StatusEffect.SLEEP:
case StatusEffect.PARALYSIS: if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) {
if (this.isOfType(Type.ELECTRIC)) { return false;
return false; }
} break;
break; case StatusEffect.FREEZE:
case StatusEffect.SLEEP: if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) {
if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { return false;
return false; }
} break;
break; case StatusEffect.BURN:
case StatusEffect.FREEZE: if (this.isOfType(Type.FIRE)) {
if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { return false;
return false; }
} break;
break;
case StatusEffect.BURN:
if (this.isOfType(Type.FIRE)) {
return false;
}
break;
} }
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
@ -3417,7 +3429,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true; return true;
} }
trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean { trySetStatus(effect?: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, turnsRemaining: number = 0, sourceText: string | null = null): boolean {
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
return false; return false;
} }
@ -3431,15 +3443,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { if (asPhase) {
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon)); this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon));
return true; return true;
} }
let statusCureTurn: Utils.IntegerHolder; let sleepTurnsRemaining: Utils.NumberHolder;
if (effect === StatusEffect.SLEEP) { if (effect === StatusEffect.SLEEP) {
statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4)); sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4));
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn);
this.setFrameRate(4); this.setFrameRate(4);
@ -3459,9 +3470,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
statusCureTurn = statusCureTurn!; // tell TS compiler it's defined sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
this.status = new Status(effect, 0, statusCureTurn?.value); this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
@ -3499,6 +3510,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
/**
* Checks if this Pokemon is protected by Safeguard
* @param attacker the {@linkcode Pokemon} inflicting status on this Pokemon
* @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise.
*/
isSafeguarded(attacker: Pokemon): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
const bypassed = new Utils.BooleanHolder(false);
if (attacker) {
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
}
return !bypassed.value;
}
return false;
}
primeSummonData(summonDataPrimer: PokemonSummonData): void { primeSummonData(summonDataPrimer: PokemonSummonData): void {
this.summonDataPrimer = summonDataPrimer; this.summonDataPrimer = summonDataPrimer;
} }
@ -3993,16 +4021,20 @@ export class PlayerPokemon extends Pokemon {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (Overrides.STATUS_OVERRIDE) { if (Overrides.STATUS_OVERRIDE) {
this.status = new Status(Overrides.STATUS_OVERRIDE); this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4);
} }
if (Overrides.SHINY_OVERRIDE) { if (Overrides.SHINY_OVERRIDE) {
this.shiny = true; this.shiny = true;
this.initShinySparkle(); this.initShinySparkle();
if (Overrides.VARIANT_OVERRIDE) { } else if (Overrides.SHINY_OVERRIDE === false) {
this.variant = Overrides.VARIANT_OVERRIDE; this.shiny = false;
}
} }
if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) {
this.variant = Overrides.VARIANT_OVERRIDE;
}
if (!dataSource) { if (!dataSource) {
if (this.scene.gameMode.isDaily) { if (this.scene.gameMode.isDaily) {
this.generateAndPopulateMoveset(); this.generateAndPopulateMoveset();
@ -4272,7 +4304,6 @@ export class PlayerPokemon extends Pokemon {
if (newEvolution.condition?.predicate(this)) { if (newEvolution.condition?.predicate(this)) {
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature);
newPokemon.natureOverride = this.natureOverride;
newPokemon.passive = this.passive; newPokemon.passive = this.passive;
newPokemon.moveset = this.moveset.slice(); newPokemon.moveset = this.moveset.slice();
newPokemon.moveset = this.copyMoveset(); newPokemon.moveset = this.copyMoveset();
@ -4314,12 +4345,33 @@ export class PlayerPokemon extends Pokemon {
changeForm(formChange: SpeciesFormChange): Promise<void> { changeForm(formChange: SpeciesFormChange): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const previousFormIndex = this.formIndex;
this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0); this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0);
this.generateName(); this.generateName();
const abilityCount = this.getSpeciesForm().getAbilityCount(); const abilityCount = this.getSpeciesForm().getAbilityCount();
if (this.abilityIndex >= abilityCount) { // Shouldn't happen if (this.abilityIndex >= abilityCount) { // Shouldn't happen
this.abilityIndex = abilityCount - 1; this.abilityIndex = abilityCount - 1;
} }
// In cases where a form change updates the type of a Pokemon from its previous form (Arceus, Silvally, Castform, etc.),
// persist that type change in customPokemonData if necessary
const baseForm = this.species.forms[previousFormIndex];
const baseFormTypes = [ baseForm.type1, baseForm.type2 ];
if (this.customPokemonData.types.length > 0) {
if (this.getSpeciesForm().type1 !== baseFormTypes[0]) {
this.customPokemonData.types[0] = this.getSpeciesForm().type1;
}
const type2 = this.getSpeciesForm().type2;
if (!isNullOrUndefined(type2) && type2 !== baseFormTypes[1]) {
if (this.customPokemonData.types.length > 1) {
this.customPokemonData.types[1] = type2;
} else {
this.customPokemonData.types.push(type2);
}
}
}
this.compatibleTms.splice(0, this.compatibleTms.length); this.compatibleTms.splice(0, this.compatibleTms.length);
this.generateCompatibleTms(); this.generateCompatibleTms();
const updateAndResolve = () => { const updateAndResolve = () => {
@ -4356,7 +4408,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionVariant = pokemon.variant; this.fusionVariant = pokemon.variant;
this.fusionGender = pokemon.gender; this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck; this.fusionLuck = pokemon.luck;
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData; this.fusionCustomPokemonData = pokemon.customPokemonData;
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
this.pauseEvolutions = true; this.pauseEvolutions = true;
} }
@ -4448,7 +4500,7 @@ export class EnemyPokemon extends Pokemon {
} }
if (Overrides.OPP_STATUS_OVERRIDE) { if (Overrides.OPP_STATUS_OVERRIDE) {
this.status = new Status(Overrides.OPP_STATUS_OVERRIDE); this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4);
} }
if (Overrides.OPP_GENDER_OVERRIDE) { if (Overrides.OPP_GENDER_OVERRIDE) {
@ -4457,9 +4509,11 @@ export class EnemyPokemon extends Pokemon {
const speciesId = this.species.speciesId; const speciesId = this.species.speciesId;
if (speciesId in Overrides.OPP_FORM_OVERRIDES if (
speciesId in Overrides.OPP_FORM_OVERRIDES
&& Overrides.OPP_FORM_OVERRIDES[speciesId] && Overrides.OPP_FORM_OVERRIDES[speciesId]
&& this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]) { && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]
) {
this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0; this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0;
} }
@ -4470,10 +4524,13 @@ export class EnemyPokemon extends Pokemon {
if (Overrides.OPP_SHINY_OVERRIDE) { if (Overrides.OPP_SHINY_OVERRIDE) {
this.shiny = true; this.shiny = true;
this.initShinySparkle(); this.initShinySparkle();
} else if (Overrides.OPP_SHINY_OVERRIDE === false) {
this.shiny = false;
} }
if (this.shiny) { if (this.shiny) {
this.variant = this.generateVariant(); this.variant = this.generateVariant();
if (Overrides.OPP_VARIANT_OVERRIDE) { if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
this.variant = Overrides.OPP_VARIANT_OVERRIDE; this.variant = Overrides.OPP_VARIANT_OVERRIDE;
} }
} }
@ -4523,35 +4580,35 @@ export class EnemyPokemon extends Pokemon {
generateAndPopulateMoveset(formIndex?: integer): void { generateAndPopulateMoveset(formIndex?: integer): void {
switch (true) { switch (true) {
case (this.species.speciesId === Species.SMEARGLE): case (this.species.speciesId === Species.SMEARGLE):
this.moveset = [ this.moveset = [
new PokemonMove(Moves.SKETCH), new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH), new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH), new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH) new PokemonMove(Moves.SKETCH)
];
break;
case (this.species.speciesId === Species.ETERNATUS):
this.moveset = (formIndex !== undefined ? formIndex : this.formIndex)
? [
new PokemonMove(Moves.DYNAMAX_CANNON),
new PokemonMove(Moves.CROSS_POISON),
new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.RECOVER, 0, -4)
]
: [
new PokemonMove(Moves.ETERNABEAM),
new PokemonMove(Moves.SLUDGE_BOMB),
new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.COSMIC_POWER)
]; ];
if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { break;
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); case (this.species.speciesId === Species.ETERNATUS):
} this.moveset = (formIndex !== undefined ? formIndex : this.formIndex)
break; ? [
default: new PokemonMove(Moves.DYNAMAX_CANNON),
super.generateAndPopulateMoveset(); new PokemonMove(Moves.CROSS_POISON),
break; new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.RECOVER, 0, -4)
]
: [
new PokemonMove(Moves.ETERNABEAM),
new PokemonMove(Moves.SLUDGE_BOMB),
new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.COSMIC_POWER)
];
if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) {
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT);
}
break;
default:
super.generateAndPopulateMoveset();
break;
} }
} }
@ -4591,135 +4648,135 @@ export class EnemyPokemon extends Pokemon {
} }
} }
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: // No enemy should spawn with this AI type in-game case AiType.RANDOM: // No enemy should spawn with this AI type in-game
const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct?
return { move: moveId, targets: this.getNextTargets(moveId) }; return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM: case AiType.SMART_RANDOM:
case AiType.SMART: case AiType.SMART:
/** /**
* Search this Pokemon's move pool for moves that will KO an opposing target. * 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), * If there are any moves that can KO an opponent (i.e. a player Pokemon),
* those moves are the only ones considered for selection on this turn. * those moves are the only ones considered for selection on this turn.
*/ */
const koMoves = movePool.filter(pkmnMove => { const koMoves = movePool.filter(pkmnMove => {
if (!pkmnMove) { if (!pkmnMove) {
return false; return false;
} }
const move = pkmnMove.getMove()!; const move = pkmnMove.getMove()!;
if (move.moveTarget === MoveTarget.ATTACKER) { if (move.moveTarget === MoveTarget.ATTACKER) {
return false; return false;
} }
const fieldPokemon = this.scene.getField(); const fieldPokemon = this.scene.getField();
const moveTargets = getMoveTargets(this, move.id).targets const moveTargets = getMoveTargets(this, move.id).targets
.map(ind => fieldPokemon[ind]) .map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer()); .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 // 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 return move.category !== MoveCategory.STATUS
&& moveTargets.some(p => { && moveTargets.some(p => {
const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id); const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id);
return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp; return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp;
}); });
}, this); }, this);
if (koMoves.length > 0) { if (koMoves.length > 0) {
movePool = koMoves; movePool = koMoves;
} }
/** /**
* Move selection is based on the move's calculated "benefit score" against the * Move selection is based on the move's calculated "benefit score" against the
* best possible target(s) (as determined by {@linkcode getNextTargets}). * best possible target(s) (as determined by {@linkcode getNextTargets}).
* For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
*/ */
const moveScores = movePool.map(() => 0); const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct?
for (const m in movePool) { for (const m in movePool) {
const pokemonMove = movePool[m]!; // TODO: is the bang correct? const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
let moveScore = moveScores[m]; let moveScore = moveScores[m];
const targetScores: integer[] = []; const targetScores: integer[] = [];
for (const mt of moveTargets[move.id]) { for (const mt of moveTargets[move.id]) {
// Prevent a target score from being calculated when the target is whoever attacks the user // Prevent a target score from being calculated when the target is whoever attacks the user
if (mt === BattlerIndex.ATTACKER) { if (mt === BattlerIndex.ATTACKER) {
break; break;
} }
const target = this.scene.getField()[mt]; const target = this.scene.getField()[mt];
/** /**
* The "target score" of a move is given by the move's user benefit score + the move's target benefit score. * The "target score" of a move is given by the move's user benefit score + the move's target benefit score.
* If the target is an ally, the target benefit score is multiplied by -1. * If the target is an ally, the target benefit score is multiplied by -1.
*/ */
let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
if (Number.isNaN(targetScore)) { if (Number.isNaN(targetScore)) {
console.error(`Move ${move.name} returned score of NaN`); console.error(`Move ${move.name} returned score of NaN`);
targetScore = 0; targetScore = 0;
} }
/** /**
* If this move is unimplemented, or the move is known to fail when used, set its * If this move is unimplemented, or the move is known to fail when used, set its
* target score to -20 * 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)) { if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) {
targetScore = -20; targetScore = -20;
} else if (move instanceof AttackMove) { } else if (move instanceof AttackMove) {
/** /**
* Attack moves are given extra multipliers to their base benefit score based on * 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. * the move's type effectiveness against the target and whether the move is a STAB move.
*/ */
const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed);
if (target.isPlayer() !== this.isPlayer()) { if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness; targetScore *= effectiveness;
if (this.isOfType(move.type)) { if (this.isOfType(move.type)) {
targetScore *= 1.5; targetScore *= 1.5;
}
} else if (effectiveness) {
targetScore /= effectiveness;
if (this.isOfType(move.type)) {
targetScore /= 1.5;
}
} }
} else if (effectiveness) { /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */
targetScore /= effectiveness; if (!targetScore) {
if (this.isOfType(move.type)) { targetScore = -20;
targetScore /= 1.5;
} }
} }
/** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ targetScores.push(targetScore);
if (!targetScore) {
targetScore = -20;
}
} }
targetScores.push(targetScore); // When a move has multiple targets, its score is equal to the maximum target score across all targets
moveScore += Math.max(...targetScores);
// could make smarter by checking opponent def/spdef
moveScores[m] = moveScore;
} }
// When a move has multiple targets, its score is equal to the maximum target score across all targets
moveScore += Math.max(...targetScores);
// could make smarter by checking opponent def/spdef console.log(moveScores);
moveScores[m] = moveScore;
}
console.log(moveScores); // Sort the move pool in decreasing order of move score
const sortedMovePool = movePool.slice(0);
// Sort the move pool in decreasing order of move score sortedMovePool.sort((a, b) => {
const sortedMovePool = movePool.slice(0); const scoreA = moveScores[movePool.indexOf(a)];
sortedMovePool.sort((a, b) => { const scoreB = moveScores[movePool.indexOf(b)];
const scoreA = moveScores[movePool.indexOf(a)]; return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
const scoreB = moveScores[movePool.indexOf(b)]; });
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; let r = 0;
}); if (this.aiType === AiType.SMART_RANDOM) {
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) // 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 && this.scene.randBattleSeedInt(8) >= 5) { while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) {
r++; r++;
} }
} else if (this.aiType === AiType.SMART) { } 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. // 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])]) >= 0 while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0
&& this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) {
r++; r++;
}
} }
} console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct?
console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
} }
} }
@ -4867,10 +4924,10 @@ export class EnemyPokemon extends Pokemon {
} }
switch (this.scene.currentBattle.battleSpec) { switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
if (!this.formIndex && this.bossSegmentIndex < 1) { if (!this.formIndex && this.bossSegmentIndex < 1) {
damage = Math.min(damage, this.hp - 1); damage = Math.min(damage, this.hp - 1);
} }
} }
const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);

View File

@ -65,16 +65,16 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
switch (this.variant) { switch (this.variant) {
case TrainerVariant.FEMALE: case TrainerVariant.FEMALE:
if (!this.config.hasGenders) { if (!this.config.hasGenders) {
variant = TrainerVariant.DEFAULT; variant = TrainerVariant.DEFAULT;
} }
break; break;
case TrainerVariant.DOUBLE: case TrainerVariant.DOUBLE:
if (!this.config.hasDouble) { if (!this.config.hasDouble) {
variant = TrainerVariant.DEFAULT; variant = TrainerVariant.DEFAULT;
} }
break; break;
} }
console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]);
@ -229,21 +229,21 @@ export default class Trainer extends Phaser.GameObjects.Container {
const strength = partyTemplate.getStrength(i); const strength = partyTemplate.getStrength(i);
switch (strength) { switch (strength) {
case PartyMemberStrength.WEAKER: case PartyMemberStrength.WEAKER:
multiplier = 0.95; multiplier = 0.95;
break; break;
case PartyMemberStrength.WEAK: case PartyMemberStrength.WEAK:
multiplier = 1.0; multiplier = 1.0;
break; break;
case PartyMemberStrength.AVERAGE: case PartyMemberStrength.AVERAGE:
multiplier = 1.1; multiplier = 1.1;
break; break;
case PartyMemberStrength.STRONG: case PartyMemberStrength.STRONG:
multiplier = 1.2; multiplier = 1.2;
break; break;
case PartyMemberStrength.STRONGER: case PartyMemberStrength.STRONGER:
multiplier = 1.25; multiplier = 1.25;
break; break;
} }
let levelOffset = 0; let levelOffset = 0;
@ -515,19 +515,19 @@ export default class Trainer extends Phaser.GameObjects.Container {
getPartyMemberModifierChanceMultiplier(index: integer): number { getPartyMemberModifierChanceMultiplier(index: integer): number {
switch (this.getPartyTemplate().getStrength(index)) { switch (this.getPartyTemplate().getStrength(index)) {
case PartyMemberStrength.WEAKER: case PartyMemberStrength.WEAKER:
return 0.75; return 0.75;
case PartyMemberStrength.WEAK: case PartyMemberStrength.WEAK:
return 0.675; return 0.675;
case PartyMemberStrength.AVERAGE: case PartyMemberStrength.AVERAGE:
return 0.5625; return 0.5625;
case PartyMemberStrength.STRONG: case PartyMemberStrength.STRONG:
return 0.45; return 0.45;
case PartyMemberStrength.STRONGER: case PartyMemberStrength.STRONGER:
return 0.375; return 0.375;
default: default:
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
return 0; return 0;
} }
} }

View File

@ -92,10 +92,10 @@ export class GameMode implements GameModeConfig {
return Overrides.STARTING_LEVEL_OVERRIDE; return Overrides.STARTING_LEVEL_OVERRIDE;
} }
switch (this.modeId) { switch (this.modeId) {
case GameModes.DAILY: case GameModes.DAILY:
return 20; return 20;
default: default:
return 5; return 5;
} }
} }
@ -117,19 +117,19 @@ export class GameMode implements GameModeConfig {
*/ */
getStartingBiome(scene: BattleScene): Biome { getStartingBiome(scene: BattleScene): Biome {
switch (this.modeId) { switch (this.modeId) {
case GameModes.DAILY: case GameModes.DAILY:
return scene.generateRandomBiome(this.getWaveForDifficulty(1)); return scene.generateRandomBiome(this.getWaveForDifficulty(1));
default: default:
return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN;
} }
} }
getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer { getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer {
switch (this.modeId) { switch (this.modeId) {
case GameModes.DAILY: case GameModes.DAILY:
return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0);
default: default:
return waveIndex; return waveIndex;
} }
} }
@ -186,10 +186,10 @@ export class GameMode implements GameModeConfig {
isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean { isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean {
switch (this.modeId) { switch (this.modeId) {
case GameModes.DAILY: case GameModes.DAILY:
return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10);
default: default:
return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex));
} }
} }
@ -211,14 +211,14 @@ export class GameMode implements GameModeConfig {
*/ */
isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean {
switch (modeId) { switch (modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return waveIndex === 200; return waveIndex === 200;
case GameModes.ENDLESS: case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
return !(waveIndex % 250); return !(waveIndex % 250);
case GameModes.DAILY: case GameModes.DAILY:
return waveIndex === 50; return waveIndex === 50;
} }
} }
@ -287,40 +287,40 @@ export class GameMode implements GameModeConfig {
getClearScoreBonus(): integer { getClearScoreBonus(): integer {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return 5000; return 5000;
case GameModes.DAILY: case GameModes.DAILY:
return 2500; return 2500;
default: default:
return 0; return 0;
} }
} }
getEnemyModifierChance(isBoss: boolean): integer { getEnemyModifierChance(isBoss: boolean): integer {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
case GameModes.DAILY: case GameModes.DAILY:
return !isBoss ? 18 : 6; return !isBoss ? 18 : 6;
case GameModes.ENDLESS: case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
return !isBoss ? 12 : 4; return !isBoss ? 12 : 4;
} }
} }
getName(): string { getName(): string {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
return i18next.t("gameMode:classic"); return i18next.t("gameMode:classic");
case GameModes.ENDLESS: case GameModes.ENDLESS:
return i18next.t("gameMode:endless"); return i18next.t("gameMode:endless");
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
return i18next.t("gameMode:endlessSpliced"); return i18next.t("gameMode:endlessSpliced");
case GameModes.DAILY: case GameModes.DAILY:
return i18next.t("gameMode:dailyRun"); return i18next.t("gameMode:dailyRun");
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return i18next.t("gameMode:challenge"); return i18next.t("gameMode:challenge");
} }
} }
@ -329,42 +329,42 @@ export class GameMode implements GameModeConfig {
*/ */
getMysteryEncounterLegalWaves(): [number, number] { getMysteryEncounterLegalWaves(): [number, number] {
switch (this.modeId) { switch (this.modeId) {
default: default:
return [ 0, 0 ]; return [ 0, 0 ];
case GameModes.CLASSIC: case GameModes.CLASSIC:
return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES;
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES;
} }
} }
static getModeName(modeId: GameModes): string { static getModeName(modeId: GameModes): string {
switch (modeId) { switch (modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
return i18next.t("gameMode:classic"); return i18next.t("gameMode:classic");
case GameModes.ENDLESS: case GameModes.ENDLESS:
return i18next.t("gameMode:endless"); return i18next.t("gameMode:endless");
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
return i18next.t("gameMode:endlessSpliced"); return i18next.t("gameMode:endlessSpliced");
case GameModes.DAILY: case GameModes.DAILY:
return i18next.t("gameMode:dailyRun"); return i18next.t("gameMode:dailyRun");
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return i18next.t("gameMode:challenge"); return i18next.t("gameMode:challenge");
} }
} }
} }
export function getGameMode(gameMode: GameModes): GameMode { export function getGameMode(gameMode: GameModes): GameMode {
switch (gameMode) { switch (gameMode) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles);
case GameModes.ENDLESS: case GameModes.ENDLESS:
return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true });
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true });
case GameModes.DAILY: case GameModes.DAILY:
return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true });
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles);
} }
} }

View File

@ -165,6 +165,8 @@ export class LoadingScene extends SceneBase {
this.loadImage("discord", "ui"); this.loadImage("discord", "ui");
this.loadImage("google", "ui"); this.loadImage("google", "ui");
this.loadImage("settings_icon", "ui"); this.loadImage("settings_icon", "ui");
this.loadImage("link_icon", "ui");
this.loadImage("unlink_icon", "ui");
this.loadImage("default_bg", "arenas"); this.loadImage("default_bg", "arenas");
// Load arena images // Load arena images
@ -477,18 +479,18 @@ export class LoadingScene extends SceneBase {
this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => { this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => {
assetText.setText(i18next.t("menu:loadingAsset", { assetName: key })); assetText.setText(i18next.t("menu:loadingAsset", { assetName: key }));
switch (key) { switch (key) {
case "loading_bg": case "loading_bg":
bg.setTexture("loading_bg"); bg.setTexture("loading_bg");
if (mobile) { if (mobile) {
bg.setVisible(true); bg.setVisible(true);
} }
break; break;
case "logo": case "logo":
logo.setTexture("logo"); logo.setTexture("logo");
if (mobile) { if (mobile) {
logo.setVisible(true); logo.setVisible(true);
} }
break; break;
} }
}); });

View File

@ -13,21 +13,21 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string {
} }
switch (pokemon.scene.currentBattle.battleSpec) { switch (pokemon.scene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT: case BattleSpec.DEFAULT:
return !pokemon.isPlayer() return !pokemon.isPlayer()
? pokemon.hasTrainer() ? pokemon.hasTrainer()
? i18next.t("battle:foePokemonWithAffix", { ? i18next.t("battle:foePokemonWithAffix", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(),
}) })
: i18next.t("battle:wildPokemonWithAffix", { : i18next.t("battle:wildPokemonWithAffix", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(),
}) })
: pokemon.getNameToRender(); : pokemon.getNameToRender();
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
return !pokemon.isPlayer() return !pokemon.isPlayer()
? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() })
: pokemon.getNameToRender(); : pokemon.getNameToRender();
default: default:
return pokemon.getNameToRender(); return pokemon.getNameToRender();
} }
} }

View File

@ -10,7 +10,9 @@ import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier } from "#app/modifier/modifier"; import {
AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier
} from "#app/modifier/modifier";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
@ -78,18 +80,18 @@ export class ModifierType {
} }
let poolTypes: ModifierPoolType[]; let poolTypes: ModifierPoolType[];
switch (poolType) { switch (poolType) {
case ModifierPoolType.PLAYER: case ModifierPoolType.PLAYER:
poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ]; poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ];
break; break;
case ModifierPoolType.WILD: case ModifierPoolType.WILD:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ]; poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ];
break; break;
case ModifierPoolType.TRAINER: case ModifierPoolType.TRAINER:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ]; poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ];
break; break;
default: default:
poolTypes = [ poolType ]; poolTypes = [ poolType ];
break; break;
} }
// Try multiple pool types in case of stolen items // Try multiple pool types in case of stolen items
for (const type of poolTypes) { for (const type of poolTypes) {
@ -520,42 +522,42 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge
function getAttackTypeBoosterItemName(type: Type) { function getAttackTypeBoosterItemName(type: Type) {
switch (type) { switch (type) {
case Type.NORMAL: case Type.NORMAL:
return "Silk Scarf"; return "Silk Scarf";
case Type.FIGHTING: case Type.FIGHTING:
return "Black Belt"; return "Black Belt";
case Type.FLYING: case Type.FLYING:
return "Sharp Beak"; return "Sharp Beak";
case Type.POISON: case Type.POISON:
return "Poison Barb"; return "Poison Barb";
case Type.GROUND: case Type.GROUND:
return "Soft Sand"; return "Soft Sand";
case Type.ROCK: case Type.ROCK:
return "Hard Stone"; return "Hard Stone";
case Type.BUG: case Type.BUG:
return "Silver Powder"; return "Silver Powder";
case Type.GHOST: case Type.GHOST:
return "Spell Tag"; return "Spell Tag";
case Type.STEEL: case Type.STEEL:
return "Metal Coat"; return "Metal Coat";
case Type.FIRE: case Type.FIRE:
return "Charcoal"; return "Charcoal";
case Type.WATER: case Type.WATER:
return "Mystic Water"; return "Mystic Water";
case Type.GRASS: case Type.GRASS:
return "Miracle Seed"; return "Miracle Seed";
case Type.ELECTRIC: case Type.ELECTRIC:
return "Magnet"; return "Magnet";
case Type.PSYCHIC: case Type.PSYCHIC:
return "Twisted Spoon"; return "Twisted Spoon";
case Type.ICE: case Type.ICE:
return "Never-Melt Ice"; return "Never-Melt Ice";
case Type.DRAGON: case Type.DRAGON:
return "Dragon Fang"; return "Dragon Fang";
case Type.DARK: case Type.DARK:
return "Black Glasses"; return "Black Glasses";
case Type.FAIRY: case Type.FAIRY:
return "Fairy Feather"; return "Fairy Feather";
} }
} }
@ -1144,7 +1146,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
} }
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
constructor(rare: boolean) { constructor(isRareFormChangeItem: boolean) {
super((party: Pokemon[], pregenArgs?: any[]) => { super((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) {
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
@ -1167,15 +1169,15 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
foundN_SOLAR = false; foundN_SOLAR = false;
formChangeItemTriggers.forEach((fc, i) => { formChangeItemTriggers.forEach((fc, i) => {
switch (fc.item) { switch (fc.item) {
case FormChangeItem.ULTRANECROZIUM_Z: case FormChangeItem.ULTRANECROZIUM_Z:
foundULTRA_Z = true; foundULTRA_Z = true;
break; break;
case FormChangeItem.N_LUNARIZER: case FormChangeItem.N_LUNARIZER:
foundN_LUNA = true; foundN_LUNA = true;
break; break;
case FormChangeItem.N_SOLARIZER: case FormChangeItem.N_SOLARIZER:
foundN_SOLAR = true; foundN_SOLAR = true;
break; break;
} }
}); });
if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) { if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) {
@ -1185,7 +1187,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
} }
return formChangeItemTriggers; return formChangeItemTriggers;
}).flat()) }).flat())
].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare); ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === isRareFormChangeItem);
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party. // convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
if (!formChangeItemPool.length) { if (!formChangeItemPool.length) {
@ -1579,6 +1581,7 @@ export const modifierTypes = {
VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1), VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1),
GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new ExtraModifierModifier(type), undefined, "se/pb_bounce_1"), GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new ExtraModifierModifier(type), undefined, "se/pb_bounce_1"),
SILVER_POKEBALL: () => new ModifierType("modifierType:ModifierType.SILVER_POKEBALL", "pb_silver", (type, _args) => new TempExtraModifierModifier(type, 100), undefined, "se/pb_bounce_1"),
ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new EnemyDamageBoosterModifier(type, 5)), ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new EnemyDamageBoosterModifier(type, 5)),
ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new EnemyDamageReducerModifier(type, 2.5)), ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new EnemyDamageReducerModifier(type, 2.5)),
@ -1595,13 +1598,13 @@ export const modifierTypes = {
if (pregenArgs) { if (pregenArgs) {
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number);
} }
return new PokemonBaseStatTotalModifierType(randSeedInt(20)); return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1));
}), }),
MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) { if (pregenArgs) {
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]);
} }
return new PokemonBaseStatFlatModifierType(randSeedInt(20), [ Stat.HP, Stat.ATK, Stat.DEF ]); return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [ Stat.HP, Stat.ATK, Stat.DEF ]);
}), }),
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) { if (pregenArgs) {
@ -1993,21 +1996,21 @@ let enemyBuffIgnoredPoolIndexes = {}; // eslint-disable-line @typescript-eslint/
export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool {
let pool: ModifierPool; let pool: ModifierPool;
switch (poolType) { switch (poolType) {
case ModifierPoolType.PLAYER: case ModifierPoolType.PLAYER:
pool = modifierPool; pool = modifierPool;
break; break;
case ModifierPoolType.WILD: case ModifierPoolType.WILD:
pool = wildModifierPool; pool = wildModifierPool;
break; break;
case ModifierPoolType.TRAINER: case ModifierPoolType.TRAINER:
pool = trainerModifierPool; pool = trainerModifierPool;
break; break;
case ModifierPoolType.ENEMY_BUFF: case ModifierPoolType.ENEMY_BUFF:
pool = enemyBuffModifierPool; pool = enemyBuffModifierPool;
break; break;
case ModifierPoolType.DAILY_STARTER: case ModifierPoolType.DAILY_STARTER:
pool = dailyStarterModifierPool; pool = dailyStarterModifierPool;
break; break;
} }
return pool; return pool;
} }
@ -2078,23 +2081,23 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod
console.table(modifierTableData); console.table(modifierTableData);
} }
switch (poolType) { switch (poolType) {
case ModifierPoolType.PLAYER: case ModifierPoolType.PLAYER:
modifierPoolThresholds = thresholds; modifierPoolThresholds = thresholds;
ignoredPoolIndexes = ignoredIndexes; ignoredPoolIndexes = ignoredIndexes;
break; break;
case ModifierPoolType.WILD: case ModifierPoolType.WILD:
case ModifierPoolType.TRAINER: case ModifierPoolType.TRAINER:
enemyModifierPoolThresholds = thresholds; enemyModifierPoolThresholds = thresholds;
enemyIgnoredPoolIndexes = ignoredIndexes; enemyIgnoredPoolIndexes = ignoredIndexes;
break; break;
case ModifierPoolType.ENEMY_BUFF: case ModifierPoolType.ENEMY_BUFF:
enemyBuffModifierPoolThresholds = thresholds; enemyBuffModifierPoolThresholds = thresholds;
enemyBuffIgnoredPoolIndexes = ignoredIndexes; enemyBuffIgnoredPoolIndexes = ignoredIndexes;
break; break;
case ModifierPoolType.DAILY_STARTER: case ModifierPoolType.DAILY_STARTER:
dailyStarterModifierPoolThresholds = thresholds; dailyStarterModifierPoolThresholds = thresholds;
ignoredDailyStarterPoolIndexes = ignoredIndexes; ignoredDailyStarterPoolIndexes = ignoredIndexes;
break; break;
} }
} }
@ -2271,15 +2274,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier {
let tierStackCount: number; let tierStackCount: number;
switch (tier) { switch (tier) {
case ModifierTier.ULTRA: case ModifierTier.ULTRA:
tierStackCount = 5; tierStackCount = 5;
break; break;
case ModifierTier.GREAT: case ModifierTier.GREAT:
tierStackCount = 3; tierStackCount = 3;
break; break;
default: default:
tierStackCount = 1; tierStackCount = 1;
break; break;
} }
const retryCount = 50; const retryCount = 50;
@ -2345,21 +2348,21 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
const pool = getModifierPoolForType(poolType); const pool = getModifierPoolForType(poolType);
let thresholds: object; let thresholds: object;
switch (poolType) { switch (poolType) {
case ModifierPoolType.PLAYER: case ModifierPoolType.PLAYER:
thresholds = modifierPoolThresholds; thresholds = modifierPoolThresholds;
break; break;
case ModifierPoolType.WILD: case ModifierPoolType.WILD:
thresholds = enemyModifierPoolThresholds; thresholds = enemyModifierPoolThresholds;
break; break;
case ModifierPoolType.TRAINER: case ModifierPoolType.TRAINER:
thresholds = enemyModifierPoolThresholds; thresholds = enemyModifierPoolThresholds;
break; break;
case ModifierPoolType.ENEMY_BUFF: case ModifierPoolType.ENEMY_BUFF:
thresholds = enemyBuffModifierPoolThresholds; thresholds = enemyBuffModifierPoolThresholds;
break; break;
case ModifierPoolType.DAILY_STARTER: case ModifierPoolType.DAILY_STARTER:
thresholds = dailyStarterModifierPoolThresholds; thresholds = dailyStarterModifierPoolThresholds;
break; break;
} }
if (tier === undefined) { if (tier === undefined) {
const tierValue = randSeedInt(1024); const tierValue = randSeedInt(1024);

View File

@ -404,6 +404,14 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
this.battleCount = this.maxBattles; this.battleCount = this.maxBattles;
} }
/**
* Updates an existing modifier with a new `maxBattles` and `battleCount`.
*/
setNewBattleCount(count: number): void {
this.maxBattles = count;
this.battleCount = count;
}
getMaxBattles(): number { getMaxBattles(): number {
return this.maxBattles; return this.maxBattles;
} }
@ -960,7 +968,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
this.stackCount = pokemon this.stackCount = pokemon
? pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length ? pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length
: this.stackCount; : this.stackCount;
const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11);
@ -975,7 +983,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
getMaxHeldItemCount(pokemon: Pokemon): number { getMaxHeldItemCount(pokemon: Pokemon): number {
this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length; + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length;
return 999; return 999;
} }
} }
@ -1695,12 +1703,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
super(type, pokemonId, stackCount); super(type, pokemonId, stackCount);
switch (type.id) { switch (type.id) {
case "TOXIC_ORB": case "TOXIC_ORB":
this.effect = StatusEffect.TOXIC; this.effect = StatusEffect.TOXIC;
break; break;
case "FLAME_ORB": case "FLAME_ORB":
this.effect = StatusEffect.BURN; this.effect = StatusEffect.BURN;
break; break;
} }
} }
@ -2181,7 +2189,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
* @returns * @returns
*/ */
override apply(playerPokemon: PlayerPokemon): boolean { override apply(playerPokemon: PlayerPokemon): boolean {
playerPokemon.natureOverride = this.nature; playerPokemon.customPokemonData.nature = this.nature;
let speciesId = playerPokemon.species.speciesId; let speciesId = playerPokemon.species.speciesId;
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
@ -2684,15 +2692,15 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
count.value *= (this.getStackCount() + 1); count.value *= (this.getStackCount() + 1);
switch (this.getStackCount()) { switch (this.getStackCount()) {
case 1: case 1:
power.value *= 0.4; power.value *= 0.4;
break; break;
case 2: case 2:
power.value *= 0.25; power.value *= 0.25;
break; break;
case 3: case 3:
power.value *= 0.175; power.value *= 0.175;
break; break;
} }
return true; return true;
@ -3288,6 +3296,60 @@ export class ExtraModifierModifier extends PersistentModifier {
} }
} }
/**
* Modifier used for timed boosts to the player's shop item rewards.
* @extends LapsingPersistentModifier
* @see {@linkcode apply}
*/
export class TempExtraModifierModifier extends LapsingPersistentModifier {
constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) {
super(type, maxBattles, battleCount, stackCount);
}
/**
* Goes through existing modifiers for any that match Silver Pokeball,
* which will then add the max count of the new item to the existing count of the current item.
* If no existing Silver Pokeballs are found, will add a new one.
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
* @param _virtual N/A
* @param scene
* @returns true if the modifier was successfully added or applied, false otherwise
*/
add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean {
for (const modifier of modifiers) {
if (this.match(modifier)) {
const modifierInstance = modifier as TempExtraModifierModifier;
const newBattleCount = this.getMaxBattles() + modifierInstance.getBattleCount();
modifierInstance.setNewBattleCount(newBattleCount);
scene.playSound("se/restore");
return true;
}
}
modifiers.push(this);
return true;
}
clone() {
return new TempExtraModifierModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
}
match(modifier: Modifier): boolean {
return (modifier instanceof TempExtraModifierModifier);
}
/**
* Increases the current rewards in the battle by the `stackCount`.
* @returns `true` if the shop reward number modifier applies successfully
* @param count {@linkcode NumberHolder} that holds the resulting shop item reward count
*/
apply(count: NumberHolder): boolean {
count.value += this.getStackCount();
return true;
}
}
export abstract class EnemyPersistentModifier extends PersistentModifier { export abstract class EnemyPersistentModifier extends PersistentModifier {
constructor(type: ModifierType, stackCount?: number) { constructor(type: ModifierType, stackCount?: number) {
super(type, stackCount); super(type, stackCount);

View File

@ -75,6 +75,8 @@ class DefaultOverrides {
readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = []; readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = [];
/** Set to `true` to show all tutorials */ /** Set to `true` to show all tutorials */
readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false; readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false;
/** Set to `true` to force Paralysis and Freeze to always activate, or `false` to force them to not activate */
readonly STATUS_ACTIVATION_OVERRIDE: boolean | null = null;
// ---------------- // ----------------
// PLAYER OVERRIDES // PLAYER OVERRIDES
@ -113,8 +115,8 @@ class DefaultOverrides {
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly GENDER_OVERRIDE: Gender | null = null; readonly GENDER_OVERRIDE: Gender | null = null;
readonly MOVESET_OVERRIDE: Moves | Array<Moves> = []; readonly MOVESET_OVERRIDE: Moves | Array<Moves> = [];
readonly SHINY_OVERRIDE: boolean = false; readonly SHINY_OVERRIDE: boolean | null = null;
readonly VARIANT_OVERRIDE: Variant = 0; readonly VARIANT_OVERRIDE: Variant | null = null;
// -------------------------- // --------------------------
// OPPONENT / ENEMY OVERRIDES // OPPONENT / ENEMY OVERRIDES
@ -134,8 +136,8 @@ class DefaultOverrides {
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly OPP_GENDER_OVERRIDE: Gender | null = null; readonly OPP_GENDER_OVERRIDE: Gender | null = null;
readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = []; readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = [];
readonly OPP_SHINY_OVERRIDE: boolean = false; readonly OPP_SHINY_OVERRIDE: boolean | null = null;
readonly OPP_VARIANT_OVERRIDE: Variant = 0; readonly OPP_VARIANT_OVERRIDE: Variant | null = null;
readonly OPP_IVS_OVERRIDE: number | number[] = []; readonly OPP_IVS_OVERRIDE: number | number[] = [];
readonly OPP_FORM_OVERRIDES: Partial<Record<Species, number>> = {}; readonly OPP_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
/** /**

View File

@ -30,6 +30,15 @@ export class CommandPhase extends FieldPhase {
start() { start() {
super.start(); super.start();
const commandUiHandler = this.scene.ui.handlers[Mode.COMMAND];
if (commandUiHandler) {
if (this.scene.currentBattle.turn === 1 || commandUiHandler.getCursor() === Command.POKEMON) {
commandUiHandler.setCursor(Command.FIGHT);
} else {
commandUiHandler.setCursor(commandUiHandler.getCursor());
}
}
if (this.fieldIndex) { if (this.fieldIndex) {
// If we somehow are attempting to check the right pokemon but there's only one pokemon out // If we somehow are attempting to check the right pokemon but there's only one pokemon out
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
@ -84,170 +93,170 @@ export class CommandPhase extends FieldPhase {
let success: boolean; let success: boolean;
switch (command) { switch (command) {
case Command.FIGHT: case Command.FIGHT:
let useStruggle = false; let useStruggle = false;
if (cursor === -1 || if (cursor === -1 ||
playerPokemon.trySelectMove(cursor, args[0] as boolean) || playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) {
const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct?
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
if (!moveId) { if (!moveId) {
turnCommand.targets = [ this.fieldIndex ]; turnCommand.targets = [ this.fieldIndex ];
} }
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
if (moveTargets.targets.length > 1 && moveTargets.multiple) { if (moveTargets.targets.length > 1 && moveTargets.multiple) {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
} }
if (moveTargets.targets.length <= 1 || moveTargets.multiple) { if (moveTargets.targets.length <= 1 || moveTargets.multiple) {
turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here?
} else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) {
turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here?
} else { } else {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
} }
this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true; success = true;
} else if (cursor < playerPokemon.getMoveset().length) { } else if (cursor < playerPokemon.getMoveset().length) {
const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct?
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
// Decides between a Disabled, Not Implemented, or No PP translation message // Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage = const errorMessage =
playerPokemon.isMoveRestricted(move.moveId, playerPokemon) playerPokemon.isMoveRestricted(move.moveId, playerPokemon)
? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId) ? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId)
: move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP";
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => {
this.scene.ui.clearText(); this.scene.ui.clearText();
this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex);
}, null, true); }, null, true);
} }
break; break;
case Command.BALL: case Command.BALL:
const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1);
if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex());
if (targets.length > 1) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => {
this.scene.ui.showText("", 0); this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); }, null, true);
} else if (cursor < 5) { } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex());
if (targets.length > 1) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => {
this.scene.ui.showText("", 0); this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); }, null, true);
} else { } else if (cursor < 5) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true));
if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor };
this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets;
if (this.fieldIndex) { if (this.fieldIndex) {
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
} }
success = true; success = true;
}
} }
} }
} break;
break; case Command.POKEMON:
case Command.POKEMON: case Command.RUN:
case Command.RUN: const isSwitch = command === Command.POKEMON;
const isSwitch = command === Command.POKEMON; const { currentBattle, arena } = this.scene;
const { currentBattle, arena } = this.scene; const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) {
if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); this.scene.ui.setMode(Mode.MESSAGE);
} else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
success = true;
if (!isSwitch && this.fieldIndex) {
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
}
} else if (trappedAbMessages.length > 0) {
if (!isSwitch) {
this.scene.ui.setMode(Mode.MESSAGE);
}
this.scene.ui.showText(trappedAbMessages[0], null, () => {
this.scene.ui.showText("", 0); this.scene.ui.showText("", 0);
if (!isSwitch) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true);
} } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); }, null, true);
} else { } else {
const trapTag = playerPokemon.getTag(TrappedTag); const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
// trapTag should be defined at this point, but just in case... if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
if (!trapTag) {
currentBattle.turnCommands[this.fieldIndex] = isSwitch currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args } ? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN }; : { command: Command.RUN };
break; success = true;
} if (!isSwitch && this.fieldIndex) {
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
if (!isSwitch) { }
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); } else if (trappedAbMessages.length > 0) {
this.scene.ui.setMode(Mode.MESSAGE); if (!isSwitch) {
} this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText( }
i18next.t("battle:noEscapePokemon", { this.scene.ui.showText(trappedAbMessages[0], null, () => {
pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "",
moveName: trapTag.getMoveName(),
escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee")
}),
null,
() => {
this.scene.ui.showText("", 0); this.scene.ui.showText("", 0);
if (!isSwitch) { if (!isSwitch) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
} }
}, null, true); }, null, true);
} else {
const trapTag = playerPokemon.getTag(TrappedTag);
// trapTag should be defined at this point, but just in case...
if (!trapTag) {
currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
break;
}
if (!isSwitch) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
}
this.scene.ui.showText(
i18next.t("battle:noEscapePokemon", {
pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "",
moveName: trapTag.getMoveName(),
escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee")
}),
null,
() => {
this.scene.ui.showText("", 0);
if (!isSwitch) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}
}, null, true);
}
} }
} break;
break;
} }
if (success!) { // TODO: is the bang correct? if (success!) { // TODO: is the bang correct?

View File

@ -41,16 +41,16 @@ export class DamagePhase extends PokemonPhase {
applyDamage() { applyDamage() {
switch (this.damageResult) { switch (this.damageResult) {
case HitResult.EFFECTIVE: case HitResult.EFFECTIVE:
this.scene.playSound("se/hit"); this.scene.playSound("se/hit");
break; break;
case HitResult.SUPER_EFFECTIVE: case HitResult.SUPER_EFFECTIVE:
case HitResult.ONE_HIT_KO: case HitResult.ONE_HIT_KO:
this.scene.playSound("se/hit_strong"); this.scene.playSound("se/hit_strong");
break; break;
case HitResult.NOT_VERY_EFFECTIVE: case HitResult.NOT_VERY_EFFECTIVE:
this.scene.playSound("se/hit_weak"); this.scene.playSound("se/hit_weak");
break; break;
} }
if (this.amount) { if (this.amount) {

View File

@ -35,6 +35,7 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
export class EncounterPhase extends BattlePhase { export class EncounterPhase extends BattlePhase {
private loaded: boolean; private loaded: boolean;
@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase {
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
const currentSessionEncounterType = battle.mysteryEncounterType; const currentSessionEncounterType = battle.mysteryEncounterType;
battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType);
}, battle.waveIndex << 4); }, battle.waveIndex * 16);
} }
const mysteryEncounter = battle.mysteryEncounter; const mysteryEncounter = battle.mysteryEncounter;
if (mysteryEncounter) { if (mysteryEncounter) {
@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase {
this.scene.updateModifiers(true); this.scene.updateModifiers(true);
}*/ }*/
const { battleType, waveIndex } = this.scene.currentBattle;
if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) {
// Increment ME spawn chance if an ME could have spawned but did not
// Only do this AFTER session has been saved to avoid duplicating increments
this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
for (const pokemon of this.scene.getParty()) { for (const pokemon of this.scene.getParty()) {
if (pokemon) { if (pokemon) {
pokemon.resetBattleData(); pokemon.resetBattleData();
@ -494,31 +502,31 @@ export class EncounterPhase extends BattlePhase {
tryOverrideForBattleSpec(): boolean { tryOverrideForBattleSpec(): boolean {
switch (this.scene.currentBattle.battleSpec) { switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
const enemy = this.scene.getEnemyPokemon(); const enemy = this.scene.getEnemyPokemon();
this.scene.ui.showText(this.getEncounterMessage(), null, () => { this.scene.ui.showText(this.getEncounterMessage(), null, () => {
const localizationKey = "battleSpecDialogue:encounter"; const localizationKey = "battleSpecDialogue:encounter";
if (this.scene.ui.shouldSkipDialogue(localizationKey)) { if (this.scene.ui.shouldSkipDialogue(localizationKey)) {
// Logging mirrors logging found in dialogue-ui-handler // Logging mirrors logging found in dialogue-ui-handler
console.log(`Dialogue ${localizationKey} skipped`); console.log(`Dialogue ${localizationKey} skipped`);
this.doEncounterCommon(false);
} else {
const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed;
// The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not.
const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : "";
const cycleCount = count.toLocaleString() + ordinalUsed;
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
const genderStr = PlayerGender[genderIndex].toLowerCase();
const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount });
if (!this.scene.gameData.getSeenDialogues()[localizationKey]) {
this.scene.gameData.saveSeenDialogue(localizationKey);
}
this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => {
this.doEncounterCommon(false); this.doEncounterCommon(false);
}); } else {
} const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed;
}, 1500, true); // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not.
return true; const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : "";
const cycleCount = count.toLocaleString() + ordinalUsed;
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
const genderStr = PlayerGender[genderIndex].toLowerCase();
const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount });
if (!this.scene.gameData.getSeenDialogues()[localizationKey]) {
this.scene.gameData.saveSeenDialogue(localizationKey);
}
this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => {
this.doEncounterCommon(false);
});
}
}, 1500, true);
return true;
} }
return false; return false;
} }

View File

@ -179,19 +179,19 @@ export class FaintPhase extends PokemonPhase {
tryOverrideForBattleSpec(): boolean { tryOverrideForBattleSpec(): boolean {
switch (this.scene.currentBattle.battleSpec) { switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
if (!this.player) { if (!this.player) {
const enemy = this.getPokemon(); const enemy = this.getPokemon();
if (enemy.formIndex) { if (enemy.formIndex) {
this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint());
} else { } else {
// Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase
enemy.hp++; enemy.hp++;
this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER));
this.end(); this.end();
}
return true;
} }
return true;
}
} }
return false; return false;

View File

@ -170,13 +170,16 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
pokemon.setMove(index, this.moveId); pokemon.setMove(index, this.moveId);
initMoveAnim(this.scene, this.moveId).then(() => { initMoveAnim(this.scene, this.moveId).then(() => {
loadMoveAnimAssets(this.scene, [ this.moveId ], true); loadMoveAnimAssets(this.scene, [ this.moveId ], true);
this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is
}); });
this.scene.ui.setMode(this.messageMode); this.scene.ui.setMode(this.messageMode);
const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }); const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name });
textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText; if (textMessage) {
await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); await this.scene.ui.showTextPromise(textMessage);
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); }
this.end(); this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is
this.scene.ui.showText(learnMoveText, null, () => {
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true);
this.end();
}, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true);
} }
} }

View File

@ -0,0 +1,84 @@
import BattleScene from "#app/battle-scene";
import { BattlerIndex } from "#app/battle";
import { MoveChargeAnim } from "#app/data/battle-anims";
import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move";
import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon";
import { BooleanHolder } from "#app/utils";
import { MovePhase } from "#app/phases/move-phase";
import { PokemonPhase } from "#app/phases/pokemon-phase";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveEndPhase } from "#app/phases/move-end-phase";
/**
* Phase for the "charging turn" of two-turn moves (e.g. Dig).
* @extends {@linkcode PokemonPhase}
*/
export class MoveChargePhase extends PokemonPhase {
/** The move instance that this phase applies */
public move: PokemonMove;
/** The field index targeted by the move (Charging moves assume single target) */
public targetIndex: BattlerIndex;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) {
super(scene, battlerIndex);
this.move = move;
this.targetIndex = targetIndex;
}
public override start() {
super.start();
const user = this.getUserPokemon();
const target = this.getTargetPokemon();
const move = this.move.getMove();
// If the target is somehow not defined, or the move is somehow not a ChargingMove,
// immediately end this phase.
if (!target || !(move.isChargingMove())) {
console.warn("Invalid parameters for MoveChargePhase");
return super.end();
}
new MoveChargeAnim(move.chargeAnim, move.id, user).play(this.scene, false, () => {
move.showChargeText(user, target);
applyMoveChargeAttrs(MoveEffectAttr, user, target, move).then(() => {
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
this.end();
});
});
}
/** Checks the move's instant charge conditions, then ends this phase. */
public override end() {
const user = this.getUserPokemon();
const move = this.move.getMove();
if (move.isChargingMove()) {
const instantCharge = new BooleanHolder(false);
applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge);
if (instantCharge.value) {
// this MoveEndPhase will be duplicated by the queued MovePhase if not removed
this.scene.tryRemovePhase((phase) => phase instanceof MoveEndPhase && phase.getPokemon() === user);
// queue a new MovePhase for this move's attack phase
this.scene.unshiftPhase(new MovePhase(this.scene, user, [ this.targetIndex ], this.move, false));
} else {
user.getMoveQueue().push({ move: move.id, targets: [ this.targetIndex ]});
}
// Add this move's charging phase to the user's move history
user.pushMoveHistory({ move: this.move.moveId, targets: [ this.targetIndex ], result: MoveResult.OTHER });
}
super.end();
}
public getUserPokemon(): Pokemon {
return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex];
}
public getTargetPokemon(): Pokemon | undefined {
return this.scene.getField(true).find((p) => this.targetIndex === p.getBattlerIndex());
}
}

View File

@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr,
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
import { MoveAnim } from "#app/data/battle-anims"; import { MoveAnim } from "#app/data/battle-anims";
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move";
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
@ -24,10 +24,10 @@ export class MoveEffectPhase extends PokemonPhase {
super(scene, battlerIndex); super(scene, battlerIndex);
this.move = move; this.move = move;
/** /**
* In double battles, if the right Pokemon selects a spread move and the left Pokemon dies * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
* with no party members available to switch in, then the right Pokemon takes the index * with no party members available to switch in, then the right Pokemon takes the index
* of the left Pokemon and gets hit unless this is checked. * of the left Pokemon and gets hit unless this is checked.
*/ */
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
const i = targets.indexOf(battlerIndex); const i = targets.indexOf(battlerIndex);
targets.splice(i, i + 1); targets.splice(i, i + 1);
@ -49,9 +49,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Does an effect from this move override other effects on this turn? * Does an effect from this move override other effects on this turn?
* e.g. Charging moves (Fly, etc.) on their first turn of use. * e.g. Charging moves (Fly, etc.) on their first turn of use.
*/ */
const overridden = new Utils.BooleanHolder(false); const overridden = new Utils.BooleanHolder(false);
/** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */
const move = this.move.getMove(); const move = this.move.getMove();
@ -66,10 +66,10 @@ export class MoveEffectPhase extends PokemonPhase {
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
/** /**
* If this phase is for the first hit of the invoked move, * If this phase is for the first hit of the invoked move,
* resolve the move's total hit count. This block combines the * resolve the move's total hit count. This block combines the
* effects of the move itself, Parental Bond, and Multi-Lens to do so. * effects of the move itself, Parental Bond, and Multi-Lens to do so.
*/ */
if (user.turnData.hitsLeft === -1) { if (user.turnData.hitsLeft === -1) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
@ -86,16 +86,16 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Log to be entered into the user's move history once the move result is resolved. * Log to be entered into the user's move history once the move result is resolved.
* Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully
* used in the sense of "Does it have an effect on the user?". * used in the sense of "Does it have an effect on the user?".
*/ */
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
/** /**
* Stores results of hit checks of the invoked move against all targets, organized by battler index. * Stores results of hit checks of the invoked move against all targets, organized by battler index.
* @see {@linkcode hitCheck} * @see {@linkcode hitCheck}
*/ */
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const hasActiveTargets = targets.some(t => t.isActive(true)); const hasActiveTargets = targets.some(t => t.isActive(true));
@ -104,11 +104,10 @@ export class MoveEffectPhase extends PokemonPhase {
&& !targets[0].getTag(SemiInvulnerableTag); && !targets[0].getTag(SemiInvulnerableTag);
/** /**
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target * If no targets are left for the move to hit (FAIL), or the invoked move is single-target
* (and not random target) and failed the hit check against its target (MISS), log the move * (and not random target) and failed the hit check against its target (MISS), log the move
* as FAILed or MISSed (depending on the conditions above) and end this phase. * as FAILed or MISSed (depending on the conditions above) and end this phase.
*/ */
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) {
this.stopMultiHit(); this.stopMultiHit();
if (hasActiveTargets) { if (hasActiveTargets) {
@ -154,9 +153,9 @@ export class MoveEffectPhase extends PokemonPhase {
&& !target.getTag(SemiInvulnerableTag); && !target.getTag(SemiInvulnerableTag);
/** /**
* If the move missed a target, stop all future hits against that target * If the move missed a target, stop all future hits against that target
* and move on to the next target (if there is one). * and move on to the next target (if there is one).
*/ */
if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) { if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) {
this.stopMultiHit(target); this.stopMultiHit(target);
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
@ -177,23 +176,23 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Since all fail/miss checks have applied, the move is considered successfully applied. * Since all fail/miss checks have applied, the move is considered successfully applied.
* It's worth noting that if the move has no effect or is protected against, this assignment * It's worth noting that if the move has no effect or is protected against, this assignment
* is overwritten and the move is logged as a FAIL. * is overwritten and the move is logged as a FAIL.
*/ */
moveHistoryEntry.result = MoveResult.SUCCESS; moveHistoryEntry.result = MoveResult.SUCCESS;
/** /**
* Stores the result of applying the invoked move to the target. * Stores the result of applying the invoked move to the target.
* If the target is protected, the result is always `NO_EFFECT`. * If the target is protected, the result is always `NO_EFFECT`.
* Otherwise, the hit result is based on type effectiveness, immunities, * Otherwise, the hit result is based on type effectiveness, immunities,
* and other factors that may negate the attack or status application. * and other factors that may negate the attack or status application.
* *
* Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated
* (for attack moves) and the target's HP is updated. However, this isn't * (for attack moves) and the target's HP is updated. However, this isn't
* made visible to the user until the resulting {@linkcode DamagePhase} * made visible to the user until the resulting {@linkcode DamagePhase}
* is invoked. * is invoked.
*/ */
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
@ -211,9 +210,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* If the move has no effect on the target (i.e. the target is protected or immune), * If the move has no effect on the target (i.e. the target is protected or immune),
* change the logged move result to FAIL. * change the logged move result to FAIL.
*/ */
if (hitResult === HitResult.NO_EFFECT) { if (hitResult === HitResult.NO_EFFECT) {
moveHistoryEntry.result = MoveResult.FAIL; moveHistoryEntry.result = MoveResult.FAIL;
} }
@ -222,43 +221,41 @@ export class MoveEffectPhase extends PokemonPhase {
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
/** /**
* If the user can change forms by using the invoked move, * If the user can change forms by using the invoked move,
* it only changes forms after the move's last hit * it only changes forms after the move's last hit
* (see Relic Song's interaction with Parental Bond when used by Meloetta). * (see Relic Song's interaction with Parental Bond when used by Meloetta).
*/ */
if (lastHit) { if (lastHit) {
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
} }
/** /**
* Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs.
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
* type requires different conditions to be met with respect to the move's hit result. * type requires different conditions to be met with respect to the move's hit result.
*/ */
applyAttrs.push(new Promise(resolve => { applyAttrs.push(new Promise(resolve => {
// Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move)
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT,
user, target, move).then(() => { user, target, move).then(() => {
// All other effects require the move to not have failed or have been cancelled to trigger // All other effects require the move to not have failed or have been cancelled to trigger
if (hitResult !== HitResult.FAIL) { if (hitResult !== HitResult.FAIL) {
/** Are the move's effects tied to the first turn of a charge move? */
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move));
/** /**
* If the invoked move's effects are meant to trigger during the move's "charge turn," * If the invoked move's effects are meant to trigger during the move's "charge turn,"
* ignore all effects after this point. * ignore all effects after this point.
* Otherwise, apply all self-targeted POST_APPLY effects. * Otherwise, apply all self-targeted POST_APPLY effects.
*/ */
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move).then(() => {
// All effects past this point require the move to have hit the target // All effects past this point require the move to have hit the target
if (hitResult !== HitResult.NO_EFFECT) { if (hitResult !== HitResult.NO_EFFECT) {
// Apply all non-self-targeted POST_APPLY effects // Apply all non-self-targeted POST_APPLY effects
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
/** /**
* If the move hit, and the target doesn't have Shield Dust, * If the move hit, and the target doesn't have Shield Dust,
* apply the chance to flinch the target gained from King's Rock * apply the chance to flinch the target gained from King's Rock
*/ */
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !move.hitsSubstitute(user, target)) { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !move.hitsSubstitute(user, target)) {
const flinched = new Utils.BooleanHolder(false); const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -267,7 +264,7 @@ export class MoveEffectPhase extends PokemonPhase {
} }
} }
// If the move was not protected against, apply all HIT effects // If the move was not protected against, apply all HIT effects
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT Utils.executeIf(!isProtected, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT
&& (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => {
// Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them)
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
@ -280,17 +277,15 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
} }
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); target.lapseTags(BattlerTagLapseType.AFTER_HIT);
if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) {
target.lapseTag(BattlerTagType.SHELL_TRAP);
}
})).then(() => { })).then(() => {
// Apply the user's post-attack ability effects // Apply the user's post-attack ability effects
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
/** /**
* If the invoked move is an attack, apply the user's chance to * If the invoked move is an attack, apply the user's chance to
* steal an item from the target granted by Grip Claw * steal an item from the target granted by Grip Claw
*/ */
if (this.move.getMove() instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
} }
@ -345,12 +340,12 @@ export class MoveEffectPhase extends PokemonPhase {
end() { end() {
const user = this.getUserPokemon(); const user = this.getUserPokemon();
/** /**
* If this phase isn't for the invoked move's last strike, * If this phase isn't for the invoked move's last strike,
* unshift another MoveEffectPhase for the next strike. * unshift another MoveEffectPhase for the next strike.
* Otherwise, queue a message indicating the number of times the move has struck * Otherwise, queue a message indicating the number of times the move has struck
* (if the move has struck more than once), then apply the heal from Shell Bell * (if the move has struck more than once), then apply the heal from Shell Bell
* to the user. * to the user.
*/ */
if (user) { if (user) {
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
this.scene.unshiftPhase(this.getNewHitPhase()); this.scene.unshiftPhase(this.getNewHitPhase());
@ -449,9 +444,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Removes the given {@linkcode Pokemon} from this phase's target list * Removes the given {@linkcode Pokemon} from this phase's target list
* @param target {@linkcode Pokemon} the Pokemon to be removed * @param target {@linkcode Pokemon} the Pokemon to be removed
*/ */
removeTarget(target: Pokemon): void { removeTarget(target: Pokemon): void {
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
if (targetIndex !== -1) { if (targetIndex !== -1) {
@ -460,19 +455,19 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Prevents subsequent strikes of this phase's invoked move from occurring * Prevents subsequent strikes of this phase's invoked move from occurring
* @param target {@linkcode Pokemon} if defined, only stop subsequent * @param target {@linkcode Pokemon} if defined, only stop subsequent
* strikes against this Pokemon * strikes against this Pokemon
*/ */
stopMultiHit(target?: Pokemon): void { stopMultiHit(target?: Pokemon): void {
/** If given a specific target, remove the target from subsequent strikes */ /** If given a specific target, remove the target from subsequent strikes */
if (target) { if (target) {
this.removeTarget(target); this.removeTarget(target);
} }
/** /**
* If no target specified, or the specified target was the last of this move's * If no target specified, or the specified target was the last of this move's
* targets, completely cancel all subsequent strikes. * targets, completely cancel all subsequent strikes.
*/ */
if (!target || this.targets.length === 0 ) { if (!target || this.targets.length === 0 ) {
this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here?
this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here?

View File

@ -1,16 +1,17 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability"; import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#app/data/battle-anims";
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { getTerrainBlockMessage } from "#app/data/weather"; import { getTerrainBlockMessage } from "#app/data/weather";
import { MoveUsedEvent } from "#app/events/battle-scene"; import { MoveUsedEvent } from "#app/events/battle-scene";
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
@ -22,6 +23,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
import { MoveChargePhase } from "#app/phases/move-charge-phase";
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
protected _pokemon: Pokemon; protected _pokemon: Pokemon;
@ -134,6 +136,8 @@ export class MovePhase extends BattlePhase {
if (this.cancelled || this.failed) { if (this.cancelled || this.failed) {
this.handlePreMoveFailures(); this.handlePreMoveFailures();
} else if (this.move.getMove().isChargingMove() && !this.pokemon.getTag(BattlerTagType.CHARGING)) {
this.chargeMove();
} else { } else {
this.useMove(); this.useMove();
} }
@ -167,26 +171,32 @@ export class MovePhase extends BattlePhase {
let healed = false; let healed = false;
switch (this.pokemon.status.effect) { switch (this.pokemon.status.effect) {
case StatusEffect.PARALYSIS: case StatusEffect.PARALYSIS:
if (!this.pokemon.randSeedInt(4)) { activated = (!this.pokemon.randSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) && Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
activated = true; break;
this.cancelled = true; case StatusEffect.SLEEP:
} applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
break; const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
case StatusEffect.SLEEP: applyAbAttrs(ReduceStatusEffectDurationAbAttr, this.pokemon, null, false, this.pokemon.status.effect, turnsRemaining);
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; healed = this.pokemon.status.sleepTurnsRemaining <= 0;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
this.cancelled = activated; break;
break; case StatusEffect.FREEZE:
case StatusEffect.FREEZE: healed =
healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); !!this.move.getMove().findAttr((attr) =>
activated = !healed; attr instanceof HealStatusEffectAttr
this.cancelled = activated; && attr.selfTarget
break; && attr.isOfEffect(StatusEffect.FREEZE))
|| (!this.pokemon.randSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true)
|| Overrides.STATUS_ACTIVATION_OVERRIDE === false;
activated = !healed;
break;
} }
if (activated) { if (activated) {
this.cancel();
this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)));
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1)));
} else if (healed) { } else if (healed) {
@ -219,12 +229,15 @@ export class MovePhase extends BattlePhase {
this.showMoveText(); this.showMoveText();
// TODO: Clean up implementation of two-turn moves.
if (moveQueue.length > 0) { if (moveQueue.length > 0) {
// Using .shift here clears out two turn moves once they've been used // Using .shift here clears out two turn moves once they've been used
this.ignorePp = moveQueue.shift()?.ignorePP ?? false; this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
} }
if (this.pokemon.getTag(BattlerTagType.CHARGING)?.sourceMove === this.move.moveId) {
this.pokemon.lapseTag(BattlerTagType.CHARGING);
}
// "commit" to using the move, deducting PP. // "commit" to using the move, deducting PP.
if (!this.ignorePp) { if (!this.ignorePp) {
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
@ -288,6 +301,9 @@ export class MovePhase extends BattlePhase {
} }
this.showFailedText(failedText); this.showFailedText(failedText);
// Remove the user from its semi-invulnerable state (if applicable)
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
} }
// Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`). // Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`).
@ -299,6 +315,35 @@ export class MovePhase extends BattlePhase {
} }
} }
/** Queues a {@linkcode MoveChargePhase} for this phase's invoked move. */
protected chargeMove() {
const move = this.move.getMove();
const targets = this.getActiveTargetPokemon();
if (move.applyConditions(this.pokemon, targets[0], move)) {
// Protean and Libero apply on the charging turn of charge moves
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
this.showMoveText();
this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move));
} else {
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
let failedText: string | undefined;
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false));
if (failureMessage) {
failedText = failureMessage;
}
this.showMoveText();
this.showFailedText(failedText);
// Remove the user from its semi-invulnerable state (if applicable)
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
}
}
/** /**
* Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`,
* then ends the phase. * then ends the phase.
@ -412,8 +457,6 @@ export class MovePhase extends BattlePhase {
* - Lapses `AFTER_MOVE` tags: * - Lapses `AFTER_MOVE` tags:
* - This handles the effects of {@link Moves.SUBSTITUTE Substitute} * - This handles the effects of {@link Moves.SUBSTITUTE Substitute}
* - Removes the second turn of charge moves * - Removes the second turn of charge moves
*
* TODO: handle charge moves more gracefully
*/ */
protected handlePreMoveFailures(): void { protected handlePreMoveFailures(): void {
if (this.cancelled || this.failed) { if (this.cancelled || this.failed) {
@ -445,18 +488,7 @@ export class MovePhase extends BattlePhase {
return; return;
} }
if (this.move.getMove().hasAttr(ChargeAttr)) { if (this.pokemon.getTag(BattlerTagType.RECHARGING) || this.pokemon.getTag(BattlerTagType.INTERRUPTED)) {
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
this.scene.queueMessage(i18next.t("battle:useMove", {
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
moveName: this.move.getName()
}), 500);
return;
}
}
if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) {
return; return;
} }

View File

@ -402,7 +402,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} }
} }
const availablePartyMembers = scene.getParty().filter(p => !p.isFainted()); const availablePartyMembers = scene.getParty().filter(p => p.isAllowedInBattle());
if (!availablePartyMembers[0].isOnField()) { if (!availablePartyMembers[0].isOnField()) {
scene.pushPhase(new SummonPhase(scene, 0)); scene.pushPhase(new SummonPhase(scene, 0));

View File

@ -8,26 +8,26 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
export class ObtainStatusEffectPhase extends PokemonPhase { export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect?: StatusEffect | undefined; private statusEffect?: StatusEffect;
private cureTurn?: integer | null; private turnsRemaining?: number;
private sourceText?: string | null; private sourceText?: string | null;
private sourcePokemon?: Pokemon | null; private sourcePokemon?: Pokemon | null;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) {
super(scene, battlerIndex); super(scene, battlerIndex);
this.statusEffect = statusEffect; this.statusEffect = statusEffect;
this.cureTurn = cureTurn; this.turnsRemaining = turnsRemaining;
this.sourceText = sourceText; this.sourceText = sourceText;
this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect this.sourcePokemon = sourcePokemon;
} }
start() { start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon && !pokemon.status) { if (pokemon && !pokemon.status) {
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
if (this.cureTurn) { if (this.turnsRemaining) {
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? pokemon.status!.sleepTurnsRemaining = this.turnsRemaining;
} }
pokemon.updateInfo(true); pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {

View File

@ -25,20 +25,20 @@ export class PokemonAnimPhase extends BattlePhase {
super.start(); super.start();
switch (this.key) { switch (this.key) {
case PokemonAnimType.SUBSTITUTE_ADD: case PokemonAnimType.SUBSTITUTE_ADD:
this.doSubstituteAddAnim(); this.doSubstituteAddAnim();
break; break;
case PokemonAnimType.SUBSTITUTE_PRE_MOVE: case PokemonAnimType.SUBSTITUTE_PRE_MOVE:
this.doSubstitutePreMoveAnim(); this.doSubstitutePreMoveAnim();
break; break;
case PokemonAnimType.SUBSTITUTE_POST_MOVE: case PokemonAnimType.SUBSTITUTE_POST_MOVE:
this.doSubstitutePostMoveAnim(); this.doSubstitutePostMoveAnim();
break; break;
case PokemonAnimType.SUBSTITUTE_REMOVE: case PokemonAnimType.SUBSTITUTE_REMOVE:
this.doSubstituteRemoveAnim(); this.doSubstituteRemoveAnim();
break; break;
default: default:
this.end(); this.end();
} }
} }

View File

@ -18,7 +18,7 @@ export class PostSummonPhase extends PokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon.status?.effect === StatusEffect.TOXIC) { if (pokemon.status?.effect === StatusEffect.TOXIC) {
pokemon.status.turnCount = 0; pokemon.status.toxicTurnCount = 0;
} }
this.scene.arena.applyTags(ArenaTrapTag, false, pokemon); this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);

View File

@ -26,16 +26,16 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
const damage = new Utils.NumberHolder(0); const damage = new Utils.NumberHolder(0);
switch (pokemon.status.effect) { switch (pokemon.status.effect) {
case StatusEffect.POISON: case StatusEffect.POISON:
damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
break; break;
case StatusEffect.TOXIC: case StatusEffect.TOXIC:
damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.toxicTurnCount), 1);
break; break;
case StatusEffect.BURN: case StatusEffect.BURN:
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage);
break; break;
} }
if (damage.value) { if (damage.value) {
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...

View File

@ -1,7 +1,7 @@
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type";
import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier, TempExtraModifierModifier } from "#app/modifier/modifier";
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
@ -45,6 +45,7 @@ export class SelectModifierPhase extends BattlePhase {
const modifierCount = new Utils.IntegerHolder(3); const modifierCount = new Utils.IntegerHolder(3);
if (this.isPlayer()) { if (this.isPlayer()) {
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount);
this.scene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
} }
// If custom modifiers are specified, overrides default item count // If custom modifiers are specified, overrides default item count
@ -77,78 +78,78 @@ export class SelectModifierPhase extends BattlePhase {
let cost: integer; let cost: integer;
const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); const rerollCost = this.getRerollCost(this.scene.lockModifierTiers);
switch (rowCursor) { switch (rowCursor) {
case 0:
switch (cursor) {
case 0: case 0:
if (rerollCost < 0 || this.scene.money < rerollCost) { switch (cursor) {
this.scene.ui.playError(); case 0:
return false; if (rerollCost < 0 || this.scene.money < rerollCost) {
} else { this.scene.ui.playError();
this.scene.reroll = true; return false;
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); } else {
this.scene.ui.clearText(); this.scene.reroll = true;
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { this.scene.ui.clearText();
this.scene.money -= rerollCost; this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
this.scene.updateMoneyText(); if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
this.scene.animateMoneyChanged(false); this.scene.money -= rerollCost;
} this.scene.updateMoneyText();
this.scene.playSound("se/buy"); this.scene.animateMoneyChanged(false);
} }
break; this.scene.playSound("se/buy");
case 1: }
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { break;
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { case 1:
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => {
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex]; const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else { } else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
} }
}, PartyUiHandler.FilterItemMaxStacks); }, PartyUiHandler.FilterItemMaxStacks);
break; break;
case 2: case 2:
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
}); });
break; break;
case 3: case 3:
if (rerollCost < 0) { if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled // Reroll lock button is also disabled when reroll is disabled
this.scene.ui.playError(); this.scene.ui.playError();
return false; return false;
}
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false;
} }
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false;
}
return true;
case 1:
if (this.typeOptions.length === 0) {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
super.end();
return true; return true;
} case 1:
if (this.typeOptions[cursor].type) { if (this.typeOptions.length === 0) {
modifierType = this.typeOptions[cursor].type; this.scene.ui.clearText();
} this.scene.ui.setMode(Mode.MESSAGE);
break; super.end();
default: return true;
const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); }
const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; if (this.typeOptions[cursor].type) {
if (shopOption.type) { modifierType = this.typeOptions[cursor].type;
modifierType = shopOption.type; }
} break;
// Apply Black Sludge to healing item cost default:
const healingItemCost = new NumberHolder(shopOption.cost); const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1));
this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT];
cost = healingItemCost.value; if (shopOption.type) {
break; modifierType = shopOption.type;
}
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
this.scene.applyModifier(HealShopCostModifier, true, healingItemCost);
cost = healingItemCost.value;
break;
} }
if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct?
@ -274,7 +275,13 @@ export class SelectModifierPhase extends BattlePhase {
// Otherwise, continue with custom multiplier // Otherwise, continue with custom multiplier
multiplier = this.customModifierSettings.rerollMultiplier; multiplier = this.customModifierSettings.rerollMultiplier;
} }
return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER);
const baseMultiplier = Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * (2 ** this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER);
// Apply Black Sludge to reroll cost
const modifiedRerollCost = new NumberHolder(baseMultiplier);
this.scene.applyModifier(HealShopCostModifier, true, modifiedRerollCost);
return modifiedRerollCost.value;
} }
getPoolType(): ModifierPoolType { getPoolType(): ModifierPoolType {

View File

@ -64,7 +64,8 @@ export class StatStageChangePhase extends PokemonPhase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!this.selfTarget && stages.value < 0) { if (!this.selfTarget && stages.value < 0) {
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled); // TODO: add a reference to the source of the stat change to fix Infiltrator interaction
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, false, cancelled);
} }
if (!cancelled.value && !this.selfTarget && stages.value < 0) { if (!cancelled.value && !this.selfTarget && stages.value < 0) {

View File

@ -65,8 +65,9 @@ export class SwitchSummonPhase extends SummonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
if (this.switchType === SwitchType.SWITCH) { if (this.switchType === SwitchType.SWITCH) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
const substitute = pokemon.getTag(SubstituteTag); const substitute = pokemon.getTag(SubstituteTag);
if (substitute) { if (substitute) {
this.scene.tweens.add({ this.scene.tweens.add({

View File

@ -152,55 +152,55 @@ export class TurnStartPhase extends FieldPhase {
} }
switch (turnCommand?.command) { switch (turnCommand?.command) {
case Command.FIGHT: case Command.FIGHT:
const queuedMove = turnCommand.move; const queuedMove = turnCommand.move;
pokemon.turnData.order = orderIndex++; pokemon.turnData.order = orderIndex++;
if (!queuedMove) { if (!queuedMove) {
continue; continue;
}
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr(MoveHeaderAttr)) {
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
}
if (pokemon.isPlayer()) {
if (turnCommand.cursor === -1) {
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here?
} else {
const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here?
this.scene.pushPhase(playerPhase);
} }
} else { const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? if (move.getMove().hasAttr(MoveHeaderAttr)) {
} this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
break; }
case Command.BALL: if (pokemon.isPlayer()) {
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? if (turnCommand.cursor === -1) {
break; this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here?
case Command.POKEMON:
const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH;
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()));
break;
case Command.RUN:
let runningPokemon = pokemon;
if (this.scene.currentBattle.double) {
const playerActivePokemon = field.filter(pokemon => {
if (!!pokemon) {
return pokemon.isPlayer() && pokemon.isActive();
} else { } else {
return; const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here?
this.scene.pushPhase(playerPhase);
} }
}); } else {
// if only one pokemon is alive, use that one this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here?
if (playerActivePokemon.length > 1) {
// find which active pokemon has faster speed
const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1];
// check if either active pokemon has the ability "Run Away"
const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY));
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
} }
} break;
this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); case Command.BALL:
break; this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here?
break;
case Command.POKEMON:
const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH;
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()));
break;
case Command.RUN:
let runningPokemon = pokemon;
if (this.scene.currentBattle.double) {
const playerActivePokemon = field.filter(pokemon => {
if (!!pokemon) {
return pokemon.isPlayer() && pokemon.isActive();
} else {
return;
}
});
// if only one pokemon is alive, use that one
if (playerActivePokemon.length > 1) {
// find which active pokemon has faster speed
const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1];
// check if either active pokemon has the ability "Run Away"
const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY));
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
}
}
this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex()));
break;
} }
} }

View File

@ -25,12 +25,17 @@ export class VictoryPhase extends PokemonPhase {
start() { start() {
super.start(); super.start();
this.scene.gameData.gameStats.pokemonDefeated++; const isMysteryEncounter = this.scene.currentBattle.isBattleMysteryEncounter();
// update Pokemon defeated count except for MEs that disable it
if (!isMysteryEncounter || !this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) {
this.scene.gameData.gameStats.pokemonDefeated++;
}
const expValue = this.getPokemon().getExpValue(); const expValue = this.getPokemon().getExpValue();
this.scene.applyPartyExp(expValue, true); this.scene.applyPartyExp(expValue, true);
if (this.scene.currentBattle.isBattleMysteryEncounter()) { if (isMysteryEncounter) {
handleMysteryEncounterVictory(this.scene, false, this.isExpOnly); handleMysteryEncounterVictory(this.scene, false, this.isExpOnly);
return this.end(); return this.end();
} }

View File

@ -156,133 +156,133 @@ export function getAchievementDescription(localizationKey: string): string {
const genderStr = PlayerGender[genderIndex].toLowerCase(); const genderStr = PlayerGender[genderIndex].toLowerCase();
switch (localizationKey) { switch (localizationKey) {
case "10K_MONEY": case "10K_MONEY":
return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") });
case "100K_MONEY": case "100K_MONEY":
return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") });
case "1M_MONEY": case "1M_MONEY":
return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") });
case "10M_MONEY": case "10M_MONEY":
return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") });
case "250_DMG": case "250_DMG":
return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") });
case "1000_DMG": case "1000_DMG":
return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") });
case "2500_DMG": case "2500_DMG":
return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") });
case "10000_DMG": case "10000_DMG":
return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") });
case "250_HEAL": case "250_HEAL":
return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "1000_HEAL": case "1000_HEAL":
return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "2500_HEAL": case "2500_HEAL":
return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "10000_HEAL": case "10000_HEAL":
return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "LV_100": case "LV_100":
return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level });
case "LV_250": case "LV_250":
return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level });
case "LV_1000": case "LV_1000":
return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level });
case "10_RIBBONS": case "10_RIBBONS":
return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") });
case "25_RIBBONS": case "25_RIBBONS":
return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") });
case "50_RIBBONS": case "50_RIBBONS":
return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") });
case "75_RIBBONS": case "75_RIBBONS":
return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") });
case "100_RIBBONS": case "100_RIBBONS":
return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") });
case "TRANSFER_MAX_STAT_STAGE": case "TRANSFER_MAX_STAT_STAGE":
return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr });
case "MAX_FRIENDSHIP": case "MAX_FRIENDSHIP":
return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr });
case "MEGA_EVOLVE": case "MEGA_EVOLVE":
return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr });
case "GIGANTAMAX": case "GIGANTAMAX":
return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); return i18next.t("achv:GIGANTAMAX.description", { context: genderStr });
case "TERASTALLIZE": case "TERASTALLIZE":
return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); return i18next.t("achv:TERASTALLIZE.description", { context: genderStr });
case "STELLAR_TERASTALLIZE": case "STELLAR_TERASTALLIZE":
return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr });
case "SPLICE": case "SPLICE":
return i18next.t("achv:SPLICE.description", { context: genderStr }); return i18next.t("achv:SPLICE.description", { context: genderStr });
case "MINI_BLACK_HOLE": case "MINI_BLACK_HOLE":
return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr });
case "CATCH_MYTHICAL": case "CATCH_MYTHICAL":
return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr });
case "CATCH_SUB_LEGENDARY": case "CATCH_SUB_LEGENDARY":
return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr });
case "CATCH_LEGENDARY": case "CATCH_LEGENDARY":
return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr });
case "SEE_SHINY": case "SEE_SHINY":
return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); return i18next.t("achv:SEE_SHINY.description", { context: genderStr });
case "SHINY_PARTY": case "SHINY_PARTY":
return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); return i18next.t("achv:SHINY_PARTY.description", { context: genderStr });
case "HATCH_MYTHICAL": case "HATCH_MYTHICAL":
return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr });
case "HATCH_SUB_LEGENDARY": case "HATCH_SUB_LEGENDARY":
return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr });
case "HATCH_LEGENDARY": case "HATCH_LEGENDARY":
return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr });
case "HATCH_SHINY": case "HATCH_SHINY":
return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); return i18next.t("achv:HATCH_SHINY.description", { context: genderStr });
case "HIDDEN_ABILITY": case "HIDDEN_ABILITY":
return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr });
case "PERFECT_IVS": case "PERFECT_IVS":
return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); return i18next.t("achv:PERFECT_IVS.description", { context: genderStr });
case "CLASSIC_VICTORY": case "CLASSIC_VICTORY":
return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr });
case "UNEVOLVED_CLASSIC_VICTORY": case "UNEVOLVED_CLASSIC_VICTORY":
return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr });
case "MONO_GEN_ONE": case "MONO_GEN_ONE":
return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr });
case "MONO_GEN_TWO": case "MONO_GEN_TWO":
return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr });
case "MONO_GEN_THREE": case "MONO_GEN_THREE":
return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr });
case "MONO_GEN_FOUR": case "MONO_GEN_FOUR":
return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr });
case "MONO_GEN_FIVE": case "MONO_GEN_FIVE":
return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr });
case "MONO_GEN_SIX": case "MONO_GEN_SIX":
return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr });
case "MONO_GEN_SEVEN": case "MONO_GEN_SEVEN":
return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr });
case "MONO_GEN_EIGHT": case "MONO_GEN_EIGHT":
return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr });
case "MONO_GEN_NINE": case "MONO_GEN_NINE":
return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr });
case "MONO_NORMAL": case "MONO_NORMAL":
case "MONO_FIGHTING": case "MONO_FIGHTING":
case "MONO_FLYING": case "MONO_FLYING":
case "MONO_POISON": case "MONO_POISON":
case "MONO_GROUND": case "MONO_GROUND":
case "MONO_ROCK": case "MONO_ROCK":
case "MONO_BUG": case "MONO_BUG":
case "MONO_GHOST": case "MONO_GHOST":
case "MONO_STEEL": case "MONO_STEEL":
case "MONO_FIRE": case "MONO_FIRE":
case "MONO_WATER": case "MONO_WATER":
case "MONO_GRASS": case "MONO_GRASS":
case "MONO_ELECTRIC": case "MONO_ELECTRIC":
case "MONO_PSYCHIC": case "MONO_PSYCHIC":
case "MONO_ICE": case "MONO_ICE":
case "MONO_DRAGON": case "MONO_DRAGON":
case "MONO_DARK": case "MONO_DARK":
case "MONO_FAIRY": case "MONO_FAIRY":
return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) });
case "FRESH_START": case "FRESH_START":
return i18next.t("achv:FRESH_START.description", { context: genderStr }); return i18next.t("achv:FRESH_START.description", { context: genderStr });
case "INVERSE_BATTLE": case "INVERSE_BATTLE":
return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr });
case "BREEDERS_IN_SPACE": case "BREEDERS_IN_SPACE":
return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr });
default: default:
return ""; return "";
} }
} }

View File

@ -67,22 +67,22 @@ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet n
export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string {
switch (dataType) { switch (dataType) {
case GameDataType.SYSTEM: case GameDataType.SYSTEM:
return "data"; return "data";
case GameDataType.SESSION: case GameDataType.SESSION:
let ret = "sessionData"; let ret = "sessionData";
if (slotId) { if (slotId) {
ret += slotId; ret += slotId;
} }
return ret; return ret;
case GameDataType.SETTINGS: case GameDataType.SETTINGS:
return "settings"; return "settings";
case GameDataType.TUTORIALS: case GameDataType.TUTORIALS:
return "tutorials"; return "tutorials";
case GameDataType.SEEN_DIALOGUES: case GameDataType.SEEN_DIALOGUES:
return "seenDialogues"; return "seenDialogues";
case GameDataType.RUN_HISTORY: case GameDataType.RUN_HISTORY:
return "runHistoryData"; return "runHistoryData";
} }
} }
@ -1374,9 +1374,9 @@ export class GameData {
const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
const handleData = (dataStr: string) => { const handleData = (dataStr: string) => {
switch (dataType) { switch (dataType) {
case GameDataType.SYSTEM: case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr, true); dataStr = this.convertSystemDataStr(dataStr, true);
break; break;
} }
const encryptedData = AES.encrypt(dataStr, saveKey); const encryptedData = AES.encrypt(dataStr, saveKey);
const blob = new Blob([ encryptedData.toString() ], { type: "text/json" }); const blob = new Blob([ encryptedData.toString() ], { type: "text/json" });
@ -1434,28 +1434,28 @@ export class GameData {
try { try {
dataName = GameDataType[dataType].toLowerCase(); dataName = GameDataType[dataType].toLowerCase();
switch (dataType) { switch (dataType) {
case GameDataType.SYSTEM: case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr); dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr); const systemData = this.parseSystemData(dataStr);
valid = !!systemData.dexData && !!systemData.timestamp; valid = !!systemData.dexData && !!systemData.timestamp;
break; break;
case GameDataType.SESSION: case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr); const sessionData = this.parseSessionData(dataStr);
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
break; break;
case GameDataType.RUN_HISTORY: case GameDataType.RUN_HISTORY:
const data = JSON.parse(dataStr); const data = JSON.parse(dataStr);
const keys = Object.keys(data); const keys = Object.keys(data);
dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase();
keys.forEach((key) => { keys.forEach((key) => {
const entryKeys = Object.keys(data[key]); const entryKeys = Object.keys(data[key]);
valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3;
}); });
break; break;
case GameDataType.SETTINGS: case GameDataType.SETTINGS:
case GameDataType.TUTORIALS: case GameDataType.TUTORIALS:
valid = true; valid = true;
break; break;
} }
} catch (ex) { } catch (ex) {
console.error(ex); console.error(ex);
@ -1569,6 +1569,10 @@ export class GameData {
} }
setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true, trainer: boolean = false): void { setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true, trainer: boolean = false): void {
// Some Mystery Encounters block updates to these stats
if (this.scene.currentBattle?.isBattleMysteryEncounter() && this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) {
return;
}
const dexEntry = this.dexData[pokemon.species.speciesId]; const dexEntry = this.dexData[pokemon.species.speciesId];
dexEntry.seenAttr |= pokemon.getDexAttr(); dexEntry.seenAttr |= pokemon.getDexAttr();
if (incrementCount) { if (incrementCount) {

View File

@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
export default class PokemonData { export default class PokemonData {
public id: integer; public id: integer;
@ -33,7 +33,6 @@ export default class PokemonData {
public stats: integer[]; public stats: integer[];
public ivs: integer[]; public ivs: integer[];
public nature: Nature; public nature: Nature;
public natureOverride: Nature | -1;
public moveset: (PokemonMove | null)[]; public moveset: (PokemonMove | null)[];
public status: Status | null; public status: Status | null;
public friendship: integer; public friendship: integer;
@ -54,14 +53,20 @@ export default class PokemonData {
public fusionVariant: Variant; public fusionVariant: Variant;
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: integer; public fusionLuck: integer;
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData;
public boss: boolean; public boss: boolean;
public bossSegments?: integer; public bossSegments?: integer;
public summonData: PokemonSummonData; public summonData: PokemonSummonData;
/** Data that can customize a Pokemon in non-standard ways from its Species */ /** Data that can customize a Pokemon in non-standard ways from its Species */
public mysteryEncounterPokemonData: MysteryEncounterPokemonData; public customPokemonData: CustomPokemonData;
public fusionCustomPokemonData: CustomPokemonData;
// Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments)
public natureOverride: Nature | -1;
public mysteryEncounterPokemonData: CustomPokemonData | null;
public fusionMysteryEncounterPokemonData: CustomPokemonData | null;
constructor(source: Pokemon | any, forHistory: boolean = false) { constructor(source: Pokemon | any, forHistory: boolean = false) {
const sourcePokemon = source instanceof Pokemon ? source : null; const sourcePokemon = source instanceof Pokemon ? source : null;
@ -87,7 +92,6 @@ export default class PokemonData {
this.stats = source.stats; this.stats = source.stats;
this.ivs = source.ivs; this.ivs = source.ivs;
this.nature = source.nature !== undefined ? source.nature : 0 as Nature; this.nature = source.nature !== undefined ? source.nature : 0 as Nature;
this.natureOverride = source.natureOverride !== undefined ? source.natureOverride : -1;
this.friendship = source.friendship !== undefined ? source.friendship : getPokemonSpecies(this.species).baseFriendship; this.friendship = source.friendship !== undefined ? source.friendship : getPokemonSpecies(this.species).baseFriendship;
this.metLevel = source.metLevel || 5; this.metLevel = source.metLevel || 5;
this.metBiome = source.metBiome !== undefined ? source.metBiome : -1; this.metBiome = source.metBiome !== undefined ? source.metBiome : -1;
@ -107,9 +111,15 @@ export default class PokemonData {
this.fusionVariant = source.fusionVariant; this.fusionVariant = source.fusionVariant;
this.fusionGender = source.fusionGender; this.fusionGender = source.fusionGender;
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
this.usedTMs = source.usedTMs ?? []; this.usedTMs = source.usedTMs ?? [];
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); this.customPokemonData = new CustomPokemonData(source.customPokemonData);
// Deprecated, but needed for session data migration
this.natureOverride = source.natureOverride;
this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData);
this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData);
if (!forHistory) { if (!forHistory) {
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
@ -128,7 +138,7 @@ export default class PokemonData {
this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
if (!forHistory) { if (!forHistory) {
this.status = source.status this.status = source.status
? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn) ? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining)
: null; : null;
} }

View File

@ -82,66 +82,66 @@ export const settingGamepadBlackList = [
export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean {
switch (setting) { switch (setting) {
case SettingGamepad.Gamepad_Support: case SettingGamepad.Gamepad_Support:
// if we change the value of the gamepad support, we call a method in the inputController to // if we change the value of the gamepad support, we call a method in the inputController to
// activate or deactivate the controller listener // activate or deactivate the controller listener
scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled");
break; break;
case SettingGamepad.Button_Action: case SettingGamepad.Button_Action:
case SettingGamepad.Button_Cancel: case SettingGamepad.Button_Cancel:
case SettingGamepad.Button_Menu: case SettingGamepad.Button_Menu:
case SettingGamepad.Button_Stats: case SettingGamepad.Button_Stats:
case SettingGamepad.Button_Cycle_Shiny: case SettingGamepad.Button_Cycle_Shiny:
case SettingGamepad.Button_Cycle_Form: case SettingGamepad.Button_Cycle_Form:
case SettingGamepad.Button_Cycle_Gender: case SettingGamepad.Button_Cycle_Gender:
case SettingGamepad.Button_Cycle_Ability: case SettingGamepad.Button_Cycle_Ability:
case SettingGamepad.Button_Cycle_Nature: case SettingGamepad.Button_Cycle_Nature:
case SettingGamepad.Button_Cycle_Variant: case SettingGamepad.Button_Cycle_Variant:
case SettingGamepad.Button_Speed_Up: case SettingGamepad.Button_Speed_Up:
case SettingGamepad.Button_Slow_Down: case SettingGamepad.Button_Slow_Down:
case SettingGamepad.Button_Submit: case SettingGamepad.Button_Submit:
if (value) { if (value) {
if (scene.ui) { if (scene.ui) {
const cancelHandler = (success: boolean = false) : boolean => { const cancelHandler = (success: boolean = false) : boolean => {
scene.ui.revertMode(); scene.ui.revertMode();
(scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings();
return success; return success;
}; };
scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, {
target: setting, target: setting,
cancelHandler: cancelHandler, cancelHandler: cancelHandler,
}); });
}
} }
} break;
break; case SettingGamepad.Controller:
case SettingGamepad.Controller: if (value) {
if (value) { const gp = scene.inputController.getGamepadsName();
const gp = scene.inputController.getGamepadsName(); if (scene.ui && gp) {
if (scene.ui && gp) { const cancelHandler = () => {
const cancelHandler = () => { scene.ui.revertMode();
scene.ui.revertMode(); (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true);
(scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings();
(scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); return false;
};
const changeGamepadHandler = (gamepad: string) => {
scene.inputController.setChosenGamepad(gamepad);
cancelHandler();
return true;
};
scene.ui.setOverlayMode(Mode.OPTION_SELECT, {
options: [ ...gp.map((g: string) => ({
label: truncateString(g, 30), // Truncate the gamepad name for display
handler: () => changeGamepadHandler(g)
})), {
label: "Cancel",
handler: cancelHandler,
}]
});
return false; return false;
}; }
const changeGamepadHandler = (gamepad: string) => {
scene.inputController.setChosenGamepad(gamepad);
cancelHandler();
return true;
};
scene.ui.setOverlayMode(Mode.OPTION_SELECT, {
options: [ ...gp.map((g: string) => ({
label: truncateString(g, 30), // Truncate the gamepad name for display
handler: () => changeGamepadHandler(g)
})), {
label: "Cancel",
handler: cancelHandler,
}]
});
return false;
} }
} break;
break;
} }
return true; return true;

View File

@ -135,53 +135,53 @@ export const settingKeyboardBlackList = [
export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean {
switch (setting) { switch (setting) {
case SettingKeyboard.Button_Up: case SettingKeyboard.Button_Up:
case SettingKeyboard.Button_Down: case SettingKeyboard.Button_Down:
case SettingKeyboard.Button_Left: case SettingKeyboard.Button_Left:
case SettingKeyboard.Button_Right: case SettingKeyboard.Button_Right:
case SettingKeyboard.Button_Action: case SettingKeyboard.Button_Action:
case SettingKeyboard.Button_Cancel: case SettingKeyboard.Button_Cancel:
case SettingKeyboard.Button_Menu: case SettingKeyboard.Button_Menu:
case SettingKeyboard.Button_Stats: case SettingKeyboard.Button_Stats:
case SettingKeyboard.Button_Cycle_Shiny: case SettingKeyboard.Button_Cycle_Shiny:
case SettingKeyboard.Button_Cycle_Form: case SettingKeyboard.Button_Cycle_Form:
case SettingKeyboard.Button_Cycle_Gender: case SettingKeyboard.Button_Cycle_Gender:
case SettingKeyboard.Button_Cycle_Ability: case SettingKeyboard.Button_Cycle_Ability:
case SettingKeyboard.Button_Cycle_Nature: case SettingKeyboard.Button_Cycle_Nature:
case SettingKeyboard.Button_Cycle_Variant: case SettingKeyboard.Button_Cycle_Variant:
case SettingKeyboard.Button_Speed_Up: case SettingKeyboard.Button_Speed_Up:
case SettingKeyboard.Button_Slow_Down: case SettingKeyboard.Button_Slow_Down:
case SettingKeyboard.Alt_Button_Up: case SettingKeyboard.Alt_Button_Up:
case SettingKeyboard.Alt_Button_Down: case SettingKeyboard.Alt_Button_Down:
case SettingKeyboard.Alt_Button_Left: case SettingKeyboard.Alt_Button_Left:
case SettingKeyboard.Alt_Button_Right: case SettingKeyboard.Alt_Button_Right:
case SettingKeyboard.Alt_Button_Action: case SettingKeyboard.Alt_Button_Action:
case SettingKeyboard.Alt_Button_Cancel: case SettingKeyboard.Alt_Button_Cancel:
case SettingKeyboard.Alt_Button_Menu: case SettingKeyboard.Alt_Button_Menu:
case SettingKeyboard.Alt_Button_Stats: case SettingKeyboard.Alt_Button_Stats:
case SettingKeyboard.Alt_Button_Cycle_Shiny: case SettingKeyboard.Alt_Button_Cycle_Shiny:
case SettingKeyboard.Alt_Button_Cycle_Form: case SettingKeyboard.Alt_Button_Cycle_Form:
case SettingKeyboard.Alt_Button_Cycle_Gender: case SettingKeyboard.Alt_Button_Cycle_Gender:
case SettingKeyboard.Alt_Button_Cycle_Ability: case SettingKeyboard.Alt_Button_Cycle_Ability:
case SettingKeyboard.Alt_Button_Cycle_Nature: case SettingKeyboard.Alt_Button_Cycle_Nature:
case SettingKeyboard.Alt_Button_Cycle_Variant: case SettingKeyboard.Alt_Button_Cycle_Variant:
case SettingKeyboard.Alt_Button_Speed_Up: case SettingKeyboard.Alt_Button_Speed_Up:
case SettingKeyboard.Alt_Button_Slow_Down: case SettingKeyboard.Alt_Button_Slow_Down:
case SettingKeyboard.Alt_Button_Submit: case SettingKeyboard.Alt_Button_Submit:
if (value) { if (value) {
if (scene.ui) { if (scene.ui) {
const cancelHandler = (success: boolean = false) : boolean => { const cancelHandler = (success: boolean = false) : boolean => {
scene.ui.revertMode(); scene.ui.revertMode();
(scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings();
return success; return success;
}; };
scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, {
target: setting, target: setting,
cancelHandler: cancelHandler, cancelHandler: cancelHandler,
}); });
}
} }
} break;
break;
// case SettingKeyboard.Default_Layout: // case SettingKeyboard.Default_Layout:
// if (value && scene.ui) { // if (value && scene.ui) {
// const cancelHandler = () => { // const cancelHandler = () => {

View File

@ -76,16 +76,16 @@ const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [
const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => { const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => {
switch (option.value) { switch (option.value) {
case "Rewards": case "Rewards":
return ShopCursorTarget.REWARDS; return ShopCursorTarget.REWARDS;
case "Shop": case "Shop":
return ShopCursorTarget.SHOP; return ShopCursorTarget.SHOP;
case "Reroll": case "Reroll":
return ShopCursorTarget.REROLL; return ShopCursorTarget.REROLL;
case "Check Team": case "Check Team":
return ShopCursorTarget.CHECK_TEAM; return ShopCursorTarget.CHECK_TEAM;
default: default:
throw new Error(`Unknown value: ${option.value}`); throw new Error(`Unknown value: ${option.value}`);
} }
}); });
@ -699,226 +699,226 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
return false; return false;
} }
switch (Setting[index].key) { switch (Setting[index].key) {
case SettingKeys.Game_Speed: case SettingKeys.Game_Speed:
scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", ""));
break;
case SettingKeys.Master_Volume:
scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.updateSoundVolume();
break;
case SettingKeys.BGM_Volume:
scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.updateSoundVolume();
break;
case SettingKeys.Field_Volume:
scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.updateSoundVolume();
break;
case SettingKeys.SE_Volume:
scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.updateSoundVolume();
break;
case SettingKeys.UI_Volume:
scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
break;
case SettingKeys.Music_Preference:
scene.musicPreference = value;
break;
case SettingKeys.Damage_Numbers:
scene.damageNumbersMode = value;
break;
case SettingKeys.UI_Theme:
scene.uiTheme = value;
break;
case SettingKeys.Window_Type:
updateWindowType(scene, parseInt(Setting[index].options[value].value));
break;
case SettingKeys.Tutorials:
scene.enableTutorials = Setting[index].options[value].value === "On";
break;
case SettingKeys.Move_Info:
scene.enableMoveInfo = Setting[index].options[value].value === "On";
break;
case SettingKeys.Enable_Retries:
scene.enableRetries = Setting[index].options[value].value === "On";
break;
case SettingKeys.Hide_IVs:
scene.hideIvs = Setting[index].options[value].value === "On";
break;
case SettingKeys.Skip_Seen_Dialogues:
scene.skipSeenDialogues = Setting[index].options[value].value === "On";
break;
case SettingKeys.Egg_Skip:
scene.eggSkipPreference = value;
break;
case SettingKeys.Battle_Style:
scene.battleStyle = value;
break;
case SettingKeys.Show_BGM_Bar:
scene.showBgmBar = Setting[index].options[value].value === "On";
break;
case SettingKeys.Candy_Upgrade_Notification:
if (scene.candyUpgradeNotification === value) {
break; break;
} case SettingKeys.Master_Volume:
scene.candyUpgradeNotification = value; scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); scene.updateSoundVolume();
break;
case SettingKeys.Candy_Upgrade_Display:
scene.candyUpgradeDisplay = value;
case SettingKeys.Money_Format:
switch (Setting[index].options[value].value) {
case "Normal":
scene.moneyFormat = MoneyFormat.NORMAL;
break; break;
case "Abbreviated": case SettingKeys.BGM_Volume:
scene.moneyFormat = MoneyFormat.ABBREVIATED; scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.updateSoundVolume();
break; break;
} case SettingKeys.Field_Volume:
scene.updateMoneyText(false); scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
break; scene.updateSoundVolume();
case SettingKeys.Sprite_Set: break;
scene.experimentalSprites = !!value; case SettingKeys.SE_Volume:
if (value) { scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.initExpSprites(); scene.updateSoundVolume();
} break;
break; case SettingKeys.UI_Volume:
case SettingKeys.Move_Animations: scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0;
scene.moveAnimations = Setting[index].options[value].value === "On"; break;
break; case SettingKeys.Music_Preference:
case SettingKeys.Show_Moveset_Flyout: scene.musicPreference = value;
scene.showMovesetFlyout = Setting[index].options[value].value === "On"; break;
break; case SettingKeys.Damage_Numbers:
case SettingKeys.Show_Arena_Flyout: scene.damageNumbersMode = value;
scene.showArenaFlyout = Setting[index].options[value].value === "On"; break;
break; case SettingKeys.UI_Theme:
case SettingKeys.Show_Time_Of_Day_Widget: scene.uiTheme = value;
scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; break;
break; case SettingKeys.Window_Type:
case SettingKeys.Time_Of_Day_Animation: updateWindowType(scene, parseInt(Setting[index].options[value].value));
scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; break;
break; case SettingKeys.Tutorials:
case SettingKeys.Show_Stats_on_Level_Up: scene.enableTutorials = Setting[index].options[value].value === "On";
scene.showLevelUpStats = Setting[index].options[value].value === "On"; break;
break; case SettingKeys.Move_Info:
case SettingKeys.Shop_Cursor_Target: scene.enableMoveInfo = Setting[index].options[value].value === "On";
const selectedValue = shopCursorTargetIndexMap[value]; break;
scene.shopCursorTarget = selectedValue; case SettingKeys.Enable_Retries:
break; scene.enableRetries = Setting[index].options[value].value === "On";
case SettingKeys.EXP_Gains_Speed: break;
scene.expGainsSpeed = value; case SettingKeys.Hide_IVs:
break; scene.hideIvs = Setting[index].options[value].value === "On";
case SettingKeys.EXP_Party_Display: break;
scene.expParty = value; case SettingKeys.Skip_Seen_Dialogues:
break; scene.skipSeenDialogues = Setting[index].options[value].value === "On";
case SettingKeys.HP_Bar_Speed: break;
scene.hpBarSpeed = value; case SettingKeys.Egg_Skip:
break; scene.eggSkipPreference = value;
case SettingKeys.Fusion_Palette_Swaps: break;
scene.fusionPaletteSwaps = !!value; case SettingKeys.Battle_Style:
break; scene.battleStyle = value;
case SettingKeys.Player_Gender: break;
if (scene.gameData) { case SettingKeys.Show_BGM_Bar:
const female = Setting[index].options[value].value === "Girl"; scene.showBgmBar = Setting[index].options[value].value === "On";
scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; break;
scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); case SettingKeys.Candy_Upgrade_Notification:
} else { if (scene.candyUpgradeNotification === value) {
return false; break;
} }
break; scene.candyUpgradeNotification = value;
case SettingKeys.Touch_Controls: scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value));
scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); break;
const touchControls = document.getElementById("touchControls"); case SettingKeys.Candy_Upgrade_Display:
if (touchControls) { scene.candyUpgradeDisplay = value;
touchControls.classList.toggle("visible", scene.enableTouchControls); case SettingKeys.Money_Format:
} switch (Setting[index].options[value].value) {
break; case "Normal":
case SettingKeys.Vibration: scene.moneyFormat = MoneyFormat.NORMAL;
scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); break;
break; case "Abbreviated":
case SettingKeys.Type_Hints: scene.moneyFormat = MoneyFormat.ABBREVIATED;
scene.typeHints = Setting[index].options[value].value === "On"; break;
break; }
case SettingKeys.Language: scene.updateMoneyText(false);
if (value) { break;
if (scene.ui) { case SettingKeys.Sprite_Set:
const cancelHandler = () => { scene.experimentalSprites = !!value;
scene.ui.revertMode(); if (value) {
(scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); scene.initExpSprites();
}; }
const changeLocaleHandler = (locale: string): boolean => { break;
try { case SettingKeys.Move_Animations:
i18next.changeLanguage(locale); scene.moveAnimations = Setting[index].options[value].value === "On";
localStorage.setItem("prLang", locale); break;
cancelHandler(); case SettingKeys.Show_Moveset_Flyout:
// Reload the whole game to apply the new locale since also some constants are translated scene.showMovesetFlyout = Setting[index].options[value].value === "On";
window.location.reload(); break;
return true; case SettingKeys.Show_Arena_Flyout:
} catch (error) { scene.showArenaFlyout = Setting[index].options[value].value === "On";
console.error("Error changing locale:", error); break;
return false; case SettingKeys.Show_Time_Of_Day_Widget:
} scene.showTimeOfDayWidget = Setting[index].options[value].value === "On";
}; break;
scene.ui.setOverlayMode(Mode.OPTION_SELECT, { case SettingKeys.Time_Of_Day_Animation:
options: [ scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK;
{ break;
label: "English", case SettingKeys.Show_Stats_on_Level_Up:
handler: () => changeLocaleHandler("en") scene.showLevelUpStats = Setting[index].options[value].value === "On";
}, break;
{ case SettingKeys.Shop_Cursor_Target:
label: "Español", const selectedValue = shopCursorTargetIndexMap[value];
handler: () => changeLocaleHandler("es") scene.shopCursorTarget = selectedValue;
}, break;
{ case SettingKeys.EXP_Gains_Speed:
label: "Italiano", scene.expGainsSpeed = value;
handler: () => changeLocaleHandler("it") break;
}, case SettingKeys.EXP_Party_Display:
{ scene.expParty = value;
label: "Français", break;
handler: () => changeLocaleHandler("fr") case SettingKeys.HP_Bar_Speed:
}, scene.hpBarSpeed = value;
{ break;
label: "Deutsch", case SettingKeys.Fusion_Palette_Swaps:
handler: () => changeLocaleHandler("de") scene.fusionPaletteSwaps = !!value;
}, break;
{ case SettingKeys.Player_Gender:
label: "Português (BR)", if (scene.gameData) {
handler: () => changeLocaleHandler("pt-BR") const female = Setting[index].options[value].value === "Girl";
}, scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE;
{ scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m"));
label: "简体中文", } else {
handler: () => changeLocaleHandler("zh-CN")
},
{
label: "繁體中文",
handler: () => changeLocaleHandler("zh-TW")
},
{
label: "한국어",
handler: () => changeLocaleHandler("ko")
},
{
label: "日本語",
handler: () => changeLocaleHandler("ja")
},
// {
// label: "Català",
// handler: () => changeLocaleHandler("ca-ES")
// },
{
label: i18next.t("settings:back"),
handler: () => cancelHandler()
}
],
maxOptions: 7
});
return false; return false;
} }
} break;
break; case SettingKeys.Touch_Controls:
case SettingKeys.Shop_Overlay_Opacity: scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen();
scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); const touchControls = document.getElementById("touchControls");
break; if (touchControls) {
touchControls.classList.toggle("visible", scene.enableTouchControls);
}
break;
case SettingKeys.Vibration:
scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen();
break;
case SettingKeys.Type_Hints:
scene.typeHints = Setting[index].options[value].value === "On";
break;
case SettingKeys.Language:
if (value) {
if (scene.ui) {
const cancelHandler = () => {
scene.ui.revertMode();
(scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true);
};
const changeLocaleHandler = (locale: string): boolean => {
try {
i18next.changeLanguage(locale);
localStorage.setItem("prLang", locale);
cancelHandler();
// Reload the whole game to apply the new locale since also some constants are translated
window.location.reload();
return true;
} catch (error) {
console.error("Error changing locale:", error);
return false;
}
};
scene.ui.setOverlayMode(Mode.OPTION_SELECT, {
options: [
{
label: "English",
handler: () => changeLocaleHandler("en")
},
{
label: "Español",
handler: () => changeLocaleHandler("es")
},
{
label: "Italiano",
handler: () => changeLocaleHandler("it")
},
{
label: "Français",
handler: () => changeLocaleHandler("fr")
},
{
label: "Deutsch",
handler: () => changeLocaleHandler("de")
},
{
label: "Português (BR)",
handler: () => changeLocaleHandler("pt-BR")
},
{
label: "简体中文",
handler: () => changeLocaleHandler("zh-CN")
},
{
label: "繁體中文",
handler: () => changeLocaleHandler("zh-TW")
},
{
label: "한국어",
handler: () => changeLocaleHandler("ko")
},
{
label: "日本語",
handler: () => changeLocaleHandler("ja")
},
// {
// label: "Català",
// handler: () => changeLocaleHandler("ca-ES")
// },
{
label: i18next.t("settings:back"),
handler: () => cancelHandler()
}
],
maxOptions: 7
});
return false;
}
}
break;
case SettingKeys.Shop_Overlay_Opacity:
scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01);
break;
} }
return true; return true;

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