Merge branch 'beta' into keyboard-key-repeat-fix

This commit is contained in:
flx-sta 2024-10-22 09:50:55 -07:00 committed by GitHub
commit dfe28b2752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
178 changed files with 13779 additions and 8808 deletions

View File

@ -1,5 +1,5 @@
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 importX from 'eslint-plugin-import-x';
@ -16,15 +16,15 @@ export default [
'@typescript-eslint': tseslint
},
rules: {
"eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and !=
"indent": ["error", 2], // Enforces a 2-space indentation
"eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=`
"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
"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
"no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead
"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)
"@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.
"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
"@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
"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
"@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
"skipBlankLines": false, // Enforces the rule even on blank lines
"ignoreComments": false // Enforces the rule on lines containing comments
}],
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
"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
"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

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pokemon-rogue-battle",
"version": "1.0.4",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pokemon-rogue-battle",
"version": "1.0.4",
"version": "1.1.0",
"hasInstallScript": true,
"dependencies": {
"@material/material-color-utilities": "^0.2.7",

View File

@ -1,7 +1,7 @@
{
"name": "pokemon-rogue-battle",
"private": true,
"version": "1.0.4",
"version": "1.1.0",
"type": "module",
"scripts": {
"start": "vite",
@ -20,7 +20,7 @@
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge"
"postinstall": "npx lefthook install && npx lefthook run post-merge"
},
"devDependencies": {
"@eslint/js": "^9.3.0",

Binary file not shown.

Binary file not shown.

View File

@ -3416,12 +3416,12 @@
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 24,
"h": 24
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"x": 5,
"y": 7,
"w": 22,
"h": 19
},
@ -8415,6 +8415,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$"
"smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$"
}
}

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { PokeballType } from "#app/data/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
import { Phase } from "#app/phase";
@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -789,7 +790,7 @@ export default class BattleScene extends SceneBase {
}
getEnemyParty(): EnemyPokemon[] {
return this.currentBattle?.enemyParty || [];
return this.currentBattle?.enemyParty ?? [];
}
getEnemyPokemon(): EnemyPokemon | undefined {
@ -1190,10 +1191,7 @@ export default class BattleScene extends SceneBase {
if (trainerConfigs[trainerType].doubleOnly) {
doubleTrainer = true;
} else if (trainerConfigs[trainerType].hasDouble) {
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
doubleTrainer = !Utils.randSeedInt(doubleChance.value);
doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
// 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)) {
doubleTrainer = false;
@ -1206,12 +1204,10 @@ export default class BattleScene extends SceneBase {
// Check for mystery encounter
// 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;
// Reset base spawn weight
// Reset to base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else if (newBattleType === BattleType.WILD) {
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
}
@ -1358,69 +1354,69 @@ export default class BattleScene extends SceneBase {
}
switch (species.speciesId) {
case Species.UNOWN:
case Species.SHELLOS:
case Species.GASTRODON:
case Species.BASCULIN:
case Species.DEERLING:
case Species.SAWSBUCK:
case Species.FROAKIE:
case Species.FROGADIER:
case Species.SCATTERBUG:
case Species.SPEWPA:
case Species.VIVILLON:
case Species.FLABEBE:
case Species.FLOETTE:
case Species.FLORGES:
case Species.FURFROU:
case Species.PUMPKABOO:
case Species.GOURGEIST:
case Species.ORICORIO:
case Species.MAGEARNA:
case Species.ZARUDE:
case Species.SQUAWKABILLY:
case Species.TATSUGIRI:
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(3);
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 {
case Species.UNOWN:
case Species.SHELLOS:
case Species.GASTRODON:
case Species.BASCULIN:
case Species.DEERLING:
case Species.SAWSBUCK:
case Species.FROAKIE:
case Species.FROGADIER:
case Species.SCATTERBUG:
case Species.SPEWPA:
case Species.VIVILLON:
case Species.FLABEBE:
case Species.FLOETTE:
case Species.FLORGES:
case Species.FURFROU:
case Species.PUMPKABOO:
case Species.GOURGEIST:
case Species.ORICORIO:
case Species.MAGEARNA:
case Species.ZARUDE:
case Species.SQUAWKABILLY:
case Species.TATSUGIRI:
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);
}
}
if (ignoreArena) {
switch (species.speciesId) {
case Species.BURMY:
case Species.WORMADAM:
case Species.ROTOM:
case Species.LYCANROC:
return Utils.randSeedInt(species.forms.length);
case Species.BURMY:
case Species.WORMADAM:
case Species.ROTOM:
case Species.LYCANROC:
return Utils.randSeedInt(species.forms.length);
}
return 0;
}
@ -1885,17 +1881,17 @@ export default class BattleScene extends SceneBase {
const soundDetails = sound.key.split("/");
switch (soundDetails[0]) {
case "battle_anims":
case "cry":
if (soundDetails[1].startsWith("PRSFX- ")) {
sound.setVolume(this.masterVolume * this.fieldVolume * 0.5);
} else {
sound.setVolume(this.masterVolume * this.fieldVolume);
}
break;
case "se":
case "ui":
sound.setVolume(this.masterVolume * this.seVolume);
case "battle_anims":
case "cry":
if (soundDetails[1].startsWith("PRSFX- ")) {
sound.setVolume(this.masterVolume * this.fieldVolume * 0.5);
} else {
sound.setVolume(this.masterVolume * this.fieldVolume);
}
break;
case "se":
case "ui":
sound.setVolume(this.masterVolume * this.seVolume);
}
}
}
@ -1935,31 +1931,31 @@ export default class BattleScene extends SceneBase {
const keyDetails = key.split("/");
config["volume"] = config["volume"] ?? 1;
switch (keyDetails[0]) {
case "level_up_fanfare":
case "item_fanfare":
case "minor_fanfare":
case "heal":
case "evolution":
case "evolution_fanfare":
case "level_up_fanfare":
case "item_fanfare":
case "minor_fanfare":
case "heal":
case "evolution":
case "evolution_fanfare":
// 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()
config["volume"] *= (this.masterVolume * this.bgmVolume);
break;
case "battle_anims":
case "cry":
config["volume"] *= (this.masterVolume * this.fieldVolume);
//PRSFX sound files are unusually loud
if (keyDetails[1].startsWith("PRSFX- ")) {
config["volume"] *= 0.5;
}
break;
case "ui":
config["volume"] *= (this.masterVolume * this.bgmVolume);
break;
case "battle_anims":
case "cry":
config["volume"] *= (this.masterVolume * this.fieldVolume);
//PRSFX sound files are unusually loud
if (keyDetails[1].startsWith("PRSFX- ")) {
config["volume"] *= 0.5;
}
break;
case "ui":
//As of, right now this applies to the "select", "menu_open", "error" sound effects
config["volume"] *= (this.masterVolume * this.uiVolume);
break;
case "se":
config["volume"] *= (this.masterVolume * this.seVolume);
break;
config["volume"] *= (this.masterVolume * this.uiVolume);
break;
case "se":
config["volume"] *= (this.masterVolume * this.seVolume);
break;
}
this.sound.play(key, config);
return this.sound.get(key) as AnySound;
@ -1988,208 +1984,208 @@ export default class BattleScene extends SceneBase {
getBgmLoopPoint(bgmName: string): number {
switch (bgmName) {
case "battle_kanto_champion": //B2W2 Kanto Champion Battle
return 13.950;
case "battle_johto_champion": //B2W2 Johto Champion Battle
return 23.498;
case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle
return 11.328;
case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle
return 11.762;
case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle
return 12.235;
case "battle_champion_alder": //BW Unova Champion Battle
return 27.653;
case "battle_champion_iris": //B2W2 Unova Champion Battle
return 10.145;
case "battle_kalos_champion": //XY Kalos Champion Battle
return 10.380;
case "battle_alola_champion": //USUM Alola Champion Battle
return 13.025;
case "battle_galar_champion": //SWSH Galar Champion Battle
return 61.635;
case "battle_champion_geeta": //SV Champion Geeta Battle
return 37.447;
case "battle_champion_nemona": //SV Champion Nemona Battle
return 14.914;
case "battle_champion_kieran": //SV Champion Kieran Battle
return 7.206;
case "battle_hoenn_elite": //ORAS Elite Four Battle
return 11.350;
case "battle_unova_elite": //BW Elite Four Battle
return 17.730;
case "battle_kalos_elite": //XY Elite Four Battle
return 12.340;
case "battle_alola_elite": //SM Elite Four Battle
return 19.212;
case "battle_galar_elite": //SWSH League Tournament Battle
return 164.069;
case "battle_paldea_elite": //SV Elite Four Battle
return 12.770;
case "battle_bb_elite": //SV BB League Elite Four Battle
return 19.434;
case "battle_final_encounter": //PMD RTDX Rayquaza's Domain
return 19.159;
case "battle_final": //BW Ghetsis Battle
return 16.453;
case "battle_kanto_gym": //B2W2 Kanto Gym Battle
return 13.857;
case "battle_johto_gym": //B2W2 Johto Gym Battle
return 12.911;
case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle
return 12.379;
case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle
return 13.122;
case "battle_unova_gym": //BW Unova Gym Battle
return 19.145;
case "battle_kalos_gym": //XY Kalos Gym Battle
return 44.810;
case "battle_galar_gym": //SWSH Galar Gym Battle
return 171.262;
case "battle_paldea_gym": //SV Paldea Gym Battle
return 127.489;
case "battle_legendary_kanto": //XY Kanto Legendary Battle
return 32.966;
case "battle_legendary_raikou": //HGSS Raikou Battle
return 12.632;
case "battle_legendary_entei": //HGSS Entei Battle
return 2.905;
case "battle_legendary_suicune": //HGSS Suicune Battle
return 12.636;
case "battle_legendary_lugia": //HGSS Lugia Battle
return 19.770;
case "battle_legendary_ho_oh": //HGSS Ho-oh Battle
return 17.668;
case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle
return 49.500;
case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle
return 21.130;
case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle
return 10.547;
case "battle_legendary_rayquaza": //ORAS Rayquaza Battle
return 10.495;
case "battle_legendary_deoxys": //ORAS Deoxys Battle
return 13.333;
case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle
return 16.887;
case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle
return 22.770;
case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle
return 16.009;
case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle
return 18.961;
case "battle_legendary_giratina": //ORAS Giratina Battle
return 10.451;
case "battle_legendary_arceus": //HGSS Arceus Battle
return 9.595;
case "battle_legendary_unova": //BW Unova Legendary Battle
return 13.855;
case "battle_legendary_kyurem": //BW Kyurem Battle
return 18.314;
case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle
return 18.329;
case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle
return 26.468;
case "battle_legendary_tapu": //SM Tapu Battle
return 0.000;
case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle
return 6.525;
case "battle_legendary_ub": //SM Ultra Beast Battle
return 9.818;
case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle
return 5.211;
case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle
return 10.344;
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
return 11.424;
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
return 12.503;
case "battle_legendary_calyrex": //SWSH Calyrex Battle
return 50.641;
case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle
return 18.155;
case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle
return 0.175;
case "battle_legendary_ruinous": //SV Treasures of Ruin Battle
return 6.333;
case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle
return 6.442;
case "battle_legendary_loyal_three": //SV Loyal Three Battle
return 6.500;
case "battle_legendary_ogerpon": //SV Ogerpon Battle
return 14.335;
case "battle_legendary_terapagos": //SV Terapagos Battle
return 24.377;
case "battle_legendary_pecharunt": //SV Pecharunt Battle
return 6.508;
case "battle_rival": //BW Rival Battle
return 14.110;
case "battle_rival_2": //BW N Battle
return 17.714;
case "battle_rival_3": //BW Final N Battle
return 17.586;
case "battle_trainer": //BW Trainer Battle
return 13.686;
case "battle_wild": //BW Wild Battle
return 12.703;
case "battle_wild_strong": //BW Strong Wild Battle
return 13.940;
case "end_summit": //PMD RTDX Sky Tower Summit
return 30.025;
case "battle_rocket_grunt": //HGSS Team Rocket Battle
return 12.707;
case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle
return 12.062;
case "battle_galactic_grunt": //BDSP Team Galactic Battle
return 13.043;
case "battle_plasma_grunt": //BW Team Plasma Battle
return 12.974;
case "battle_flare_grunt": //XY Team Flare Battle
return 4.228;
case "battle_aether_grunt": // SM Aether Foundation Battle
return 16.00;
case "battle_skull_grunt": // SM Team Skull Battle
return 20.87;
case "battle_macro_grunt": // SWSH Trainer Battle
return 11.56;
case "battle_star_grunt": //SV Team Star Battle
return 133.362;
case "battle_galactic_admin": //BDSP Team Galactic Admin Battle
return 11.997;
case "battle_skull_admin": //SM Team Skull Admin Battle
return 15.463;
case "battle_oleana": //SWSH Oleana Battle
return 14.110;
case "battle_star_admin": //SV Team Star Boss Battle
return 9.493;
case "battle_rocket_boss": //USUM Giovanni Battle
return 9.115;
case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle
return 14.847;
case "battle_galactic_boss": //BDSP Cyrus Battle
return 106.962;
case "battle_plasma_boss": //B2W2 Ghetsis Battle
return 25.624;
case "battle_flare_boss": //XY Lysandre Battle
return 8.085;
case "battle_aether_boss": //SM Lusamine Battle
return 11.33;
case "battle_skull_boss": //SM Guzma Battle
return 13.13;
case "battle_macro_boss": //SWSH Rose Battle
return 11.42;
case "battle_star_boss": //SV Cassiopeia Battle
return 25.764;
case "mystery_encounter_gen_5_gts": // BW GTS
return 8.52;
case "mystery_encounter_gen_6_gts": // XY GTS
return 9.24;
case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff
return 4.78;
case "mystery_encounter_weird_dream": // EoS Temporal Spire
return 41.42;
case "mystery_encounter_delibirdy": // Firel Delibirdy
return 82.28;
case "battle_kanto_champion": //B2W2 Kanto Champion Battle
return 13.950;
case "battle_johto_champion": //B2W2 Johto Champion Battle
return 23.498;
case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle
return 11.328;
case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle
return 11.762;
case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle
return 12.235;
case "battle_champion_alder": //BW Unova Champion Battle
return 27.653;
case "battle_champion_iris": //B2W2 Unova Champion Battle
return 10.145;
case "battle_kalos_champion": //XY Kalos Champion Battle
return 10.380;
case "battle_alola_champion": //USUM Alola Champion Battle
return 13.025;
case "battle_galar_champion": //SWSH Galar Champion Battle
return 61.635;
case "battle_champion_geeta": //SV Champion Geeta Battle
return 37.447;
case "battle_champion_nemona": //SV Champion Nemona Battle
return 14.914;
case "battle_champion_kieran": //SV Champion Kieran Battle
return 7.206;
case "battle_hoenn_elite": //ORAS Elite Four Battle
return 11.350;
case "battle_unova_elite": //BW Elite Four Battle
return 17.730;
case "battle_kalos_elite": //XY Elite Four Battle
return 12.340;
case "battle_alola_elite": //SM Elite Four Battle
return 19.212;
case "battle_galar_elite": //SWSH League Tournament Battle
return 164.069;
case "battle_paldea_elite": //SV Elite Four Battle
return 12.770;
case "battle_bb_elite": //SV BB League Elite Four Battle
return 19.434;
case "battle_final_encounter": //PMD RTDX Rayquaza's Domain
return 19.159;
case "battle_final": //BW Ghetsis Battle
return 16.453;
case "battle_kanto_gym": //B2W2 Kanto Gym Battle
return 13.857;
case "battle_johto_gym": //B2W2 Johto Gym Battle
return 12.911;
case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle
return 12.379;
case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle
return 13.122;
case "battle_unova_gym": //BW Unova Gym Battle
return 19.145;
case "battle_kalos_gym": //XY Kalos Gym Battle
return 44.810;
case "battle_galar_gym": //SWSH Galar Gym Battle
return 171.262;
case "battle_paldea_gym": //SV Paldea Gym Battle
return 127.489;
case "battle_legendary_kanto": //XY Kanto Legendary Battle
return 32.966;
case "battle_legendary_raikou": //HGSS Raikou Battle
return 12.632;
case "battle_legendary_entei": //HGSS Entei Battle
return 2.905;
case "battle_legendary_suicune": //HGSS Suicune Battle
return 12.636;
case "battle_legendary_lugia": //HGSS Lugia Battle
return 19.770;
case "battle_legendary_ho_oh": //HGSS Ho-oh Battle
return 17.668;
case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle
return 49.500;
case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle
return 21.130;
case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle
return 10.547;
case "battle_legendary_rayquaza": //ORAS Rayquaza Battle
return 10.495;
case "battle_legendary_deoxys": //ORAS Deoxys Battle
return 13.333;
case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle
return 16.887;
case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle
return 22.770;
case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle
return 16.009;
case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle
return 18.961;
case "battle_legendary_giratina": //ORAS Giratina Battle
return 10.451;
case "battle_legendary_arceus": //HGSS Arceus Battle
return 9.595;
case "battle_legendary_unova": //BW Unova Legendary Battle
return 13.855;
case "battle_legendary_kyurem": //BW Kyurem Battle
return 18.314;
case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle
return 18.329;
case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle
return 26.468;
case "battle_legendary_tapu": //SM Tapu Battle
return 0.000;
case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle
return 6.525;
case "battle_legendary_ub": //SM Ultra Beast Battle
return 9.818;
case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle
return 5.211;
case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle
return 10.344;
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
return 11.424;
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
return 12.503;
case "battle_legendary_calyrex": //SWSH Calyrex Battle
return 50.641;
case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle
return 18.155;
case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle
return 0.175;
case "battle_legendary_ruinous": //SV Treasures of Ruin Battle
return 6.333;
case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle
return 6.442;
case "battle_legendary_loyal_three": //SV Loyal Three Battle
return 6.500;
case "battle_legendary_ogerpon": //SV Ogerpon Battle
return 14.335;
case "battle_legendary_terapagos": //SV Terapagos Battle
return 24.377;
case "battle_legendary_pecharunt": //SV Pecharunt Battle
return 6.508;
case "battle_rival": //BW Rival Battle
return 14.110;
case "battle_rival_2": //BW N Battle
return 17.714;
case "battle_rival_3": //BW Final N Battle
return 17.586;
case "battle_trainer": //BW Trainer Battle
return 13.686;
case "battle_wild": //BW Wild Battle
return 12.703;
case "battle_wild_strong": //BW Strong Wild Battle
return 13.940;
case "end_summit": //PMD RTDX Sky Tower Summit
return 30.025;
case "battle_rocket_grunt": //HGSS Team Rocket Battle
return 12.707;
case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle
return 12.062;
case "battle_galactic_grunt": //BDSP Team Galactic Battle
return 13.043;
case "battle_plasma_grunt": //BW Team Plasma Battle
return 12.974;
case "battle_flare_grunt": //XY Team Flare Battle
return 4.228;
case "battle_aether_grunt": // SM Aether Foundation Battle
return 16.00;
case "battle_skull_grunt": // SM Team Skull Battle
return 20.87;
case "battle_macro_grunt": // SWSH Trainer Battle
return 11.56;
case "battle_star_grunt": //SV Team Star Battle
return 133.362;
case "battle_galactic_admin": //BDSP Team Galactic Admin Battle
return 11.997;
case "battle_skull_admin": //SM Team Skull Admin Battle
return 15.463;
case "battle_oleana": //SWSH Oleana Battle
return 14.110;
case "battle_star_admin": //SV Team Star Boss Battle
return 9.493;
case "battle_rocket_boss": //USUM Giovanni Battle
return 9.115;
case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle
return 14.847;
case "battle_galactic_boss": //BDSP Cyrus Battle
return 106.962;
case "battle_plasma_boss": //B2W2 Ghetsis Battle
return 25.624;
case "battle_flare_boss": //XY Lysandre Battle
return 8.085;
case "battle_aether_boss": //SM Lusamine Battle
return 11.33;
case "battle_skull_boss": //SM Guzma Battle
return 13.13;
case "battle_macro_boss": //SWSH Rose Battle
return 11.42;
case "battle_star_boss": //SV Cassiopeia Battle
return 25.764;
case "mystery_encounter_gen_5_gts": // BW GTS
return 8.52;
case "mystery_encounter_gen_6_gts": // XY GTS
return 9.24;
case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff
return 4.78;
case "mystery_encounter_weird_dream": // EoS Temporal Spire
return 41.42;
case "mystery_encounter_delibirdy": // Firel Delibirdy
return 82.28;
}
return 0;
@ -2363,6 +2359,19 @@ export default class BattleScene extends SceneBase {
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()
* @param phase {@linkcode Phase} the phase to be added
@ -2425,7 +2434,7 @@ export default class BattleScene extends SceneBase {
return Math.floor(moneyValue / 10) * 10;
}
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise<boolean> {
if (!modifier) {
return Promise.resolve(false);
}
@ -2482,6 +2491,8 @@ export default class BattleScene extends SceneBase {
}
} else if (modifier instanceof FusePokemonModifier) {
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
} else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) {
args.push(cost);
}
if (modifier.shouldApply(pokemon, ...args)) {
@ -3052,7 +3063,7 @@ export default class BattleScene extends SceneBase {
const pId = partyMember.id;
const participated = participantIds.has(pId);
if (participated && pokemonDefeated) {
partyMember.addFriendship(2);
partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) {
machoBraceModifier.stackCount++;
@ -3122,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}.
* 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
*
* @param newBattleType
* @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();
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
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
@ -3174,6 +3193,9 @@ export default class BattleScene extends SceneBase {
let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
if (canBypass) {
return encounter;
}
} else if (canBypass) {
encounter = allMysteryEncounters[encounterType ?? -1];
return encounter;

View File

@ -2579,24 +2579,24 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
switch (weatherType) {
case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
turnOffWeather = true;
}
break;
case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
turnOffWeather = true;
}
break;
case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
turnOffWeather = true;
}
break;
case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
turnOffWeather = true;
}
break;
case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
&& pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
turnOffWeather = true;
}
break;
turnOffWeather = true;
}
break;
}
if (simulated) {
@ -4079,24 +4079,24 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
switch (weatherType) {
case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
case (WeatherType.HARSH_SUN):
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
turnOffWeather = true;
}
break;
case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
turnOffWeather = true;
}
break;
case (WeatherType.HEAVY_RAIN):
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
turnOffWeather = true;
}
break;
case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
turnOffWeather = true;
}
break;
case (WeatherType.STRONG_WINDS):
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
&& pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
turnOffWeather = true;
}
break;
turnOffWeather = true;
}
break;
}
if (simulated) {
@ -4200,6 +4200,11 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
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 {
private statusEffect: StatusEffect;
@ -4209,9 +4214,19 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr {
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) {
(args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2);
args[1].value -= 1;
return true;
}
@ -4342,6 +4357,30 @@ export class AlwaysHitAbAttr extends AbAttr { }
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
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 {
constructor() {
super(false);
@ -4913,8 +4952,7 @@ export function initAbilities() {
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr)
.edgeCase(), // Does not work with secret power (unimplemented)
.attr(IgnoreMoveEffectsAbAttr),
new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr)
@ -4958,8 +4996,7 @@ export function initAbilities() {
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
.attr(MoveEffectChanceMultiplierAbAttr, 2)
.edgeCase(), // does not work with secret power (unimplemented)
.attr(MoveEffectChanceMultiplierAbAttr, 2),
new Ability(Abilities.SWIFT_SWIM, 3)
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
@ -5323,7 +5360,8 @@ export function initAbilities() {
.attr(PostSummonTransformAbAttr)
.attr(UncopiableAbilityAbAttr),
new Ability(Abilities.INFILTRATOR, 5)
.unimplemented(),
.attr(InfiltratorAbAttr)
.partial(), // does not bypass Mist
new Ability(Abilities.MUMMY, 5)
.attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY)
.bypassFaint(),
@ -5535,16 +5573,18 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.POWER_CONSTRUCT, 7) // TODO: 10% Power Construct Zygarde isn't accounted for yet. If changed, update Zygarde's getSpeciesFormIndex entry accordingly
.attr(PostBattleInitFormChangeAbAttr, () => 2)
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
.attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
new Ability(Abilities.POWER_CONSTRUCT, 7)
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
.bypassFaint(),
new Ability(Abilities.CORROSION, 7)
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet)

View File

@ -1,13 +1,13 @@
import { Arena } from "#app/field/arena";
import BattleScene from "#app/battle-scene";
import { Type } from "#app/data/type";
import * as Utils from "#app/utils";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils";
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move";
import { getPokemonNameWithAffix } from "#app/messages";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
import { StatusEffect } from "#app/data/status-effect";
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 { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
import i18next from "i18next";
@ -36,7 +36,7 @@ export abstract class ArenaTag {
public side: ArenaTagSide = ArenaTagSide.BOTH
) {}
apply(arena: Arena, args: any[]): boolean {
apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean {
return true;
}
@ -88,13 +88,13 @@ export abstract class ArenaTag {
*/
public getAffectedPokemon(scene: BattleScene): Pokemon[] {
switch (this.side) {
case ArenaTagSide.PLAYER:
return scene.getPlayerField() ?? [];
case ArenaTagSide.ENEMY:
return scene.getEnemyField() ?? [];
case ArenaTagSide.BOTH:
default:
return scene.getField(true) ?? [];
case ArenaTagSide.PLAYER:
return scene.getPlayerField() ?? [];
case ArenaTagSide.ENEMY:
return scene.getEnemyField() ?? [];
case ArenaTagSide.BOTH:
default:
return scene.getField(true) ?? [];
}
}
}
@ -122,10 +122,31 @@ export class MistTag extends ArenaTag {
}
}
apply(arena: Arena, args: any[]): boolean {
(args[0] as Utils.BooleanHolder).value = true;
/**
* Cancels the lowering of stats
* @param arena the {@linkcode Arena} containing this effect
* @param simulated `true` if the effect should be applied quietly
* @param cancelled a {@linkcode BooleanHolder} whose value is set to `true`
* to flag the stat reduction as cancelled
* @returns `true` if a stat reduction was cancelled; `false` otherwise
*/
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;
}
}
arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
cancelled.value = true;
if (!simulated) {
arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
}
return true;
}
@ -157,17 +178,21 @@ export class WeakenMoveScreenTag extends ArenaTag {
/**
* Applies the weakening effect to the move.
*
* @param arena - The arena where the move is applied.
* @param args - The arguments for the move application.
* @param args[0] - The category of the move.
* @param args[1] - A boolean indicating whether it is a double battle.
* @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier
*
* @returns True if the move was weakened, otherwise false.
* @param arena the {@linkcode Arena} where the move is applied.
* @param simulated n/a
* @param attacker the attacking {@linkcode Pokemon}
* @param moveCategory the attacking move's {@linkcode MoveCategory}.
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
* @returns `true` if the attacking move was weakened; `false` otherwise.
*/
apply(arena: Arena, args: any[]): boolean {
if (this.weakenedCategories.includes((args[0] as MoveCategory))) {
(args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5;
override apply(arena: Arena, simulated: boolean, attacker: Pokemon, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
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;
return true;
}
return false;
@ -249,38 +274,34 @@ export class ConditionalProtectTag extends ArenaTag {
onRemove(arena: Arena): void { }
/**
* apply(): Checks incoming moves against the condition function
* Checks incoming moves against the condition function
* and protects the target if conditions are met
* @param arena The arena containing this tag
* @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled
* @param args\[1\] (Pokemon) The Pokemon using the move
* @param args\[2\] (Pokemon) The intended target of the move
* @param args\[3\] (Moves) The parameters to the condition function
* @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects
* @returns
* @param arena the {@linkcode Arena} containing this tag
* @param simulated `true` if the tag is applied quietly; `false` otherwise.
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
* @param attacker the attacking {@linkcode Pokemon}
* @param defender the defending {@linkcode Pokemon}
* @param moveId the {@linkcode Moves | identifier} for the move being used
* @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
* @returns `true` if this tag protected against the attack; `false` otherwise
*/
apply(arena: Arena, args: any[]): boolean {
const [ cancelled, user, target, moveId, ignoresBypass ] = args;
override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon,
moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean {
if (cancelled instanceof Utils.BooleanHolder
&& user instanceof Pokemon
&& target instanceof Pokemon
&& typeof moveId === "number"
&& ignoresBypass instanceof Utils.BooleanHolder) {
if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer()
&& this.protectConditionFunc(arena, moveId)) {
if (!isProtected.value) {
isProtected.value = true;
if (!simulated) {
attacker.stopMultiHit(defender);
if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer()
&& this.protectConditionFunc(arena, moveId)) {
if (!cancelled.value) {
cancelled.value = true;
user.stopMultiHit(target);
new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
}
ignoresBypass.value = ignoresBypass.value || this.ignoresBypass;
return true;
}
ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass;
return true;
}
return false;
}
@ -296,7 +317,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId];
const priority = new Utils.NumberHolder(move.priority);
const priority = new NumberHolder(move.priority);
const effectPhase = arena.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
@ -328,11 +349,11 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
const move = allMoves[moveId];
switch (move.moveTarget) {
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_OTHERS:
case MoveTarget.ALL_NEAR_OTHERS:
return true;
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_OTHERS:
case MoveTarget.ALL_NEAR_OTHERS:
return true;
}
return false;
};
@ -460,7 +481,7 @@ class WishTag extends ArenaTag {
if (user) {
this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Utils.toDmgValue(user.getMaxHp() / 2);
this.healHp = toDmgValue(user.getMaxHp() / 2);
} else {
console.warn("Failed to get source for WishTag onAdd");
}
@ -497,12 +518,19 @@ export class WeakenMoveTypeTag extends ArenaTag {
this.weakenedType = type;
}
apply(arena: Arena, args: any[]): boolean {
if ((args[0] as Type) === this.weakenedType) {
(args[1] as Utils.NumberHolder).value *= 0.33;
/**
* Reduces an attack's power by 0.33x if it matches this tag's weakened type.
* @param arena n/a
* @param simulated n/a
* @param type the attack's {@linkcode Type}
* @param power a {@linkcode NumberHolder} containing the attack's power
* @returns `true` if the attack's power was reduced; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, type: Type, power: NumberHolder): boolean {
if (type === this.weakenedType) {
power.value *= 0.33;
return true;
}
return false;
}
}
@ -563,13 +591,12 @@ export class IonDelugeTag extends ArenaTag {
/**
* Converts Normal-type moves to Electric type
* @param arena n/a
* @param args
* - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type}
* @param simulated n/a
* @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type}
* @returns `true` if the given move type changed; `false` otherwise.
*/
apply(arena: Arena, args: any[]): boolean {
const moveType = args[0];
if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) {
override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean {
if (moveType.value === Type.NORMAL) {
moveType.value = Type.ELECTRIC;
return true;
}
@ -608,16 +635,22 @@ export class ArenaTrapTag extends ArenaTag {
}
}
apply(arena: Arena, args: any[]): boolean {
const pokemon = args[0] as Pokemon;
if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
/**
* Activates the hazard effect onto a Pokemon when it enters the field
* @param arena the {@linkcode Arena} containing this tag
* @param simulated if `true`, only checks if the hazard would activate.
* @param pokemon the {@linkcode Pokemon} triggering this hazard
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
return false;
}
return this.activateTrap(pokemon);
return this.activateTrap(pokemon, simulated);
}
activateTrap(pokemon: Pokemon): boolean {
activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
return false;
}
@ -651,14 +684,18 @@ class SpikesTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) {
const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
@ -702,8 +739,11 @@ class ToxicSpikesTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
if (simulated) {
return true;
}
if (pokemon.isOfType(Type.POISON)) {
this.neutralized = true;
if (pokemon.scene.arena.removeTag(this.tagType)) {
@ -784,31 +824,31 @@ class StealthRockTag extends ArenaTrapTag {
let damageHpRatio: number = 0;
switch (effectiveness) {
case 0:
damageHpRatio = 0;
break;
case 0.25:
damageHpRatio = 0.03125;
break;
case 0.5:
damageHpRatio = 0.0625;
break;
case 1:
damageHpRatio = 0.125;
break;
case 2:
damageHpRatio = 0.25;
break;
case 4:
damageHpRatio = 0.5;
break;
case 0:
damageHpRatio = 0;
break;
case 0.25:
damageHpRatio = 0.03125;
break;
case 0.5:
damageHpRatio = 0.0625;
break;
case 1:
damageHpRatio = 0.125;
break;
case 2:
damageHpRatio = 0.25;
break;
case 4:
damageHpRatio = 0.5;
break;
}
return damageHpRatio;
}
activateTrap(pokemon: Pokemon): boolean {
const cancelled = new Utils.BooleanHolder(false);
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) {
@ -818,12 +858,16 @@ class StealthRockTag extends ArenaTrapTag {
const damageHpRatio = this.getDamageHpRatio(pokemon);
if (damageHpRatio) {
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
if (simulated) {
return true;
}
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) {
pokemon.turnData.damageTaken += damage;
}
return true;
}
return false;
@ -853,14 +897,20 @@ class StickyWebTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const stages = new Utils.NumberHolder(-1);
const stages = new NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
return true;
}
}
@ -879,8 +929,15 @@ export class TrickRoomTag extends ArenaTag {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
}
apply(arena: Arena, args: any[]): boolean {
const speedReversed = args[0] as Utils.BooleanHolder;
/**
* Reverses Speed-based turn order for all Pokemon on the field
* @param arena n/a
* @param simulated n/a
* @param speedReversed a {@linkcode BooleanHolder} used to flag if Speed-based
* turn order should be reversed.
* @returns `true` if turn order is successfully reversed; `false` otherwise
*/
override apply(arena: Arena, simulated: boolean, speedReversed: BooleanHolder): boolean {
speedReversed.value = !speedReversed.value;
return true;
}
@ -1087,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag {
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// TODO: Replace this with a proper animation
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
});
return super.lapse(arena);
@ -1111,8 +1168,15 @@ class WaterFirePledgeTag extends ArenaTag {
arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
override apply(arena: Arena, args: any[]): boolean {
const moveChance = args[0] as Utils.NumberHolder;
/**
* Doubles the chance for the given move's secondary effect(s) to trigger
* @param arena the {@linkcode Arena} containing this tag
* @param simulated n/a
* @param moveChance a {@linkcode NumberHolder} containing
* the move's current effect chance
* @returns `true` if the move's effect chance was doubled (currently always `true`)
*/
override apply(arena: Arena, simulated: boolean, moveChance: NumberHolder): boolean {
moveChance.value *= 2;
return true;
}
@ -1138,63 +1202,63 @@ class GrassWaterPledgeTag extends ArenaTag {
// 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 {
switch (tagType) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
case ArenaTagType.QUICK_GUARD:
return new QuickGuardTag(sourceId, side);
case ArenaTagType.WIDE_GUARD:
return new WideGuardTag(sourceId, side);
case ArenaTagType.MAT_BLOCK:
return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT:
return new WaterSportTag(turnCount, sourceId);
case ArenaTagType.ION_DELUGE:
return new IonDelugeTag(sourceMove);
case ArenaTagType.SPIKES:
return new SpikesTag(sourceId, side);
case ArenaTagType.TOXIC_SPIKES:
return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK:
return new StealthRockTag(sourceId, side);
case ArenaTagType.STICKY_WEB:
return new StickyWebTag(sourceId, side);
case ArenaTagType.TRICK_ROOM:
return new TrickRoomTag(turnCount, sourceId);
case ArenaTagType.GRAVITY:
return new GravityTag(turnCount);
case ArenaTagType.REFLECT:
return new ReflectTag(turnCount, sourceId, side);
case ArenaTagType.LIGHT_SCREEN:
return new LightScreenTag(turnCount, sourceId, side);
case ArenaTagType.AURORA_VEIL:
return new AuroraVeilTag(turnCount, sourceId, side);
case ArenaTagType.TAILWIND:
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
case ArenaTagType.SAFEGUARD:
return new SafeguardTag(turnCount, sourceId, side);
case ArenaTagType.IMPRISON:
return new ImprisonTag(sourceId, side);
case ArenaTagType.FIRE_GRASS_PLEDGE:
return new FireGrassPledgeTag(sourceId, side);
case ArenaTagType.WATER_FIRE_PLEDGE:
return new WaterFirePledgeTag(sourceId, side);
case ArenaTagType.GRASS_WATER_PLEDGE:
return new GrassWaterPledgeTag(sourceId, side);
default:
return null;
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
case ArenaTagType.QUICK_GUARD:
return new QuickGuardTag(sourceId, side);
case ArenaTagType.WIDE_GUARD:
return new WideGuardTag(sourceId, side);
case ArenaTagType.MAT_BLOCK:
return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT:
return new WaterSportTag(turnCount, sourceId);
case ArenaTagType.ION_DELUGE:
return new IonDelugeTag(sourceMove);
case ArenaTagType.SPIKES:
return new SpikesTag(sourceId, side);
case ArenaTagType.TOXIC_SPIKES:
return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK:
return new StealthRockTag(sourceId, side);
case ArenaTagType.STICKY_WEB:
return new StickyWebTag(sourceId, side);
case ArenaTagType.TRICK_ROOM:
return new TrickRoomTag(turnCount, sourceId);
case ArenaTagType.GRAVITY:
return new GravityTag(turnCount);
case ArenaTagType.REFLECT:
return new ReflectTag(turnCount, sourceId, side);
case ArenaTagType.LIGHT_SCREEN:
return new LightScreenTag(turnCount, sourceId, side);
case ArenaTagType.AURORA_VEIL:
return new AuroraVeilTag(turnCount, sourceId, side);
case ArenaTagType.TAILWIND:
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
case ArenaTagType.SAFEGUARD:
return new SafeguardTag(turnCount, sourceId, side);
case ArenaTagType.IMPRISON:
return new ImprisonTag(sourceId, side);
case ArenaTagType.FIRE_GRASS_PLEDGE:
return new FireGrassPledgeTag(sourceId, side);
case ArenaTagType.WATER_FIRE_PLEDGE:
return new WaterFirePledgeTag(sourceId, side);
case ArenaTagType.GRASS_WATER_PLEDGE:
return new GrassWaterPledgeTag(sourceId, side);
default:
return null;
}
}

View File

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

View File

@ -2,6 +2,12 @@ import { Species } from "#enums/species";
export const POKERUS_STARTER_COUNT = 5;
// #region Friendship constants
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
/**
* Function to get the cumulative friendship threshold at which a candy is earned
* @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts}
@ -9,25 +15,25 @@ export const POKERUS_STARTER_COUNT = 5;
*/
export function getStarterValueFriendshipCap(starterCost: number): number {
switch (starterCost) {
case 1:
return 20;
case 2:
return 40;
case 3:
return 60;
case 4:
return 100;
case 5:
return 140;
case 6:
return 200;
case 7:
return 280;
case 8:
case 9:
return 450;
default:
return 600;
case 1:
return 20;
case 2:
return 40;
case 3:
return 60;
case 4:
return 100;
case 5:
return 140;
case 6:
return 200;
case 7:
return 280;
case 8:
case 9:
return 450;
default:
return 600;
}
}

View File

@ -134,15 +134,15 @@ export class AnimConfig {
for (const te of frameTimedEvents[fte]) {
let timedEvent: AnimTimedEvent | undefined;
switch (te.eventType) {
case "AnimTimedSoundEvent":
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
break;
case "AnimTimedAddBgEvent":
timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te);
break;
case "AnimTimedUpdateBgEvent":
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break;
case "AnimTimedSoundEvent":
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
break;
case "AnimTimedAddBgEvent":
timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te);
break;
case "AnimTimedUpdateBgEvent":
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break;
}
timedEvent && timedEvents.push(timedEvent);
@ -243,12 +243,12 @@ class AnimFrame {
if (!init) {
let target = AnimFrameTarget.GRAPHIC;
switch (pattern) {
case -2:
target = AnimFrameTarget.TARGET;
break;
case -1:
target = AnimFrameTarget.USER;
break;
case -2:
target = AnimFrameTarget.TARGET;
break;
case -1:
target = AnimFrameTarget.USER;
break;
}
this.target = target;
this.graphicFrame = pattern >= 0 ? pattern : 0;
@ -803,23 +803,23 @@ export abstract class BattleAnim {
let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100);
switch (frame.focus) {
case AnimFocus.TARGET:
x += targetInitialX - targetFocusX;
y += (targetInitialY - targetHalfHeight) - targetFocusY;
break;
case AnimFocus.USER:
x += userInitialX - userFocusX;
y += (userInitialY - userHalfHeight) - userFocusY;
break;
case AnimFocus.USER_TARGET:
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);
x = point[0];
y = point[1];
if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) {
scaleX = scaleX * -1;
}
break;
case AnimFocus.TARGET:
x += targetInitialX - targetFocusX;
y += (targetInitialY - targetHalfHeight) - targetFocusY;
break;
case AnimFocus.USER:
x += userInitialX - userFocusX;
y += (userInitialY - userHalfHeight) - userFocusY;
break;
case AnimFocus.USER_TARGET:
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);
x = point[0];
y = point[1];
if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) {
scaleX = scaleX * -1;
}
break;
}
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
@ -993,44 +993,44 @@ export abstract class BattleAnim {
spritePriorities[graphicIndex] = frame.priority;
const setSpritePriority = (priority: integer) => {
switch (priority) {
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?
break;
case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
break;
case 2:
switch (frame.focus) {
case AnimFocus.USER:
if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else {
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
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?
break;
case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
break;
case 2:
switch (frame.focus) {
case AnimFocus.USER:
if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else {
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;
case AnimFocus.TARGET:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
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);
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);
@ -1396,108 +1396,108 @@ export async function populateAnims() {
const fieldName = field.slice(0, field.indexOf(":"));
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim();
switch (fieldName) {
case "array":
const framesData = fieldData.split(" - - - ").slice(1);
for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]);
const frameData = framesData[fd];
const focusFramesData = frameData.split(" - - ");
for (let tf = 0; tf < focusFramesData.length; tf++) {
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]),
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[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);
case "array":
const framesData = fieldData.split(" - - - ").slice(1);
for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]);
const frameData = framesData[fd];
const focusFramesData = frameData.split(" - - ");
for (let tf = 0; tf < focusFramesData.length; tf++) {
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]),
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[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);
}
}
}
break;
case "graphic":
const graphic = fieldData !== "''" ? fieldData : "";
anim.graphic = graphic.indexOf(".") > -1
? graphic.slice(0, fieldData.indexOf("."))
: graphic;
break;
case "timing":
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
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\",")
.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?
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?
let timedEvent: AnimTimedEvent | undefined;
switch (timingType) {
case 0:
if (resourceName && resourceName.indexOf(".") === -1) {
let ext: string | undefined;
[ "wav", "mp3", "m4a" ].every(e => {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
ext = e;
return false;
break;
case "graphic":
const graphic = fieldData !== "''" ? fieldData : "";
anim.graphic = graphic.indexOf(".") > -1
? graphic.slice(0, fieldData.indexOf("."))
: graphic;
break;
case "timing":
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
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\",")
.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?
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?
let timedEvent: AnimTimedEvent | undefined;
switch (timingType) {
case 0:
if (resourceName && resourceName.indexOf(".") === -1) {
let ext: string | undefined;
[ "wav", "mp3", "m4a" ].every(e => {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
ext = e;
return false;
}
return true;
});
if (!ext) {
ext = ".wav";
}
resourceName += `.${ext}`;
}
return true;
});
if (!ext) {
ext = ".wav";
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
break;
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;
}
resourceName += `.${ext}`;
}
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
break;
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 (!anim.frameTimedEvents.has(frameIndex)) {
anim.frameTimedEvents.set(frameIndex, []);
}
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?
}
break;
case "position":
anim.position = parseInt(fieldData);
break;
case "hue":
anim.hue = parseInt(fieldData);
break;
}
break;
case "position":
anim.position = parseInt(fieldData);
break;
case "hue":
anim.hue = parseInt(fieldData);
break;
}
}
}

View File

@ -1,29 +1,44 @@
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move";
import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
import { TerrainType } from "./terrain";
import { WeatherType } from "./weather";
import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import i18next from "#app/plugins/i18n";
import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import BattleScene from "#app/battle-scene";
import {
allAbilities,
applyAbAttrs,
BlockNonDirectDamageAbAttr,
FlinchEffectAbAttr,
ProtectStatAbAttr,
ReverseDrainAbAttr
} from "#app/data/ability";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import Move, {
allMoves,
applyMoveAttrs,
ChargeAttr,
ConsecutiveUseDoublePowerAttr,
HealOnAllyAttr,
MoveCategory,
MoveFlags,
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 { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MovePhase } from "#app/phases/move-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
import BattleScene from "#app/battle-scene";
import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n";
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 {
FAINT,
@ -33,6 +48,7 @@ export enum BattlerTagLapseType {
MOVE_EFFECT,
TURN_END,
HIT,
AFTER_HIT,
CUSTOM
}
@ -405,7 +421,7 @@ export class RechargingTag extends BattlerTag {
*/
export class BeakBlastChargingTag extends BattlerTag {
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 {
@ -421,16 +437,13 @@ export class BeakBlastChargingTag extends BattlerTag {
* 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 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 {
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
const attacker = effectPhase.getPokemon();
if (effectPhase.move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const phaseData = getMoveEffectPhaseData(pokemon);
if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) {
phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
return true;
}
@ -444,11 +457,10 @@ export class BeakBlastChargingTag extends BattlerTag {
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
*/
export class ShellTrapTag extends BattlerTag {
public activated: boolean;
public activated: boolean = false;
constructor() {
super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1);
this.activated = false;
super(BattlerTagType.SHELL_TRAP, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1);
}
onAdd(pokemon: Pokemon): void {
@ -459,25 +471,33 @@ export class ShellTrapTag extends BattlerTag {
* "Activates" the shell trap, causing the tag owner to move next.
* @param pokemon {@linkcode Pokemon} the owner of this tag
* @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 {
if (lapseType === BattlerTagLapseType.CUSTOM) {
const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase && phase.pokemon === pokemon
);
const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase
);
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const phaseData = getMoveEffectPhaseData(pokemon);
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase);
// Trap should only be triggered by opponent's Physical moves
if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) {
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 super.lapse(pokemon, lapseType);
}
}
@ -641,7 +661,7 @@ export class ConfusedTag extends BattlerTag {
if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getEffectiveStat(Stat.ATK);
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.damageAndUpdate(damage);
pokemon.battleData.hitCount++;
@ -812,13 +832,13 @@ export class SeedTag extends BattlerTag {
if (ret) {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
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);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1,
@ -838,7 +858,7 @@ export class SeedTag extends BattlerTag {
export class NightmareTag extends BattlerTag {
constructor() {
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE);
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE);
}
onAdd(pokemon: Pokemon): void {
@ -860,11 +880,11 @@ export class NightmareTag extends BattlerTag {
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
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4));
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4));
}
}
@ -919,14 +939,14 @@ export class EncoreTag extends BattlerTag {
}
switch (repeatableMove.move) {
case Moves.MIMIC:
case Moves.MIRROR_MOVE:
case Moves.TRANSFORM:
case Moves.STRUGGLE:
case Moves.SKETCH:
case Moves.SLEEP_TALK:
case Moves.ENCORE:
return false;
case Moves.MIMIC:
case Moves.MIRROR_MOVE:
case Moves.TRANSFORM:
case Moves.STRUGGLE:
case Moves.SKETCH:
case Moves.SLEEP_TALK:
case Moves.ENCORE:
return false;
}
if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) {
@ -1004,7 +1024,7 @@ export class IngrainTag extends TrappedTag {
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 16),
toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true
)
@ -1067,7 +1087,7 @@ export class AquaRingTag extends BattlerTag {
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 16),
toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:aquaRingLapse", {
moveName: this.getMoveName(),
pokemonName: getPokemonNameWithAffix(pokemon)
@ -1161,11 +1181,11 @@ export abstract class DamagingTrapTag extends TrappedTag {
);
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);
if (!cancelled.value) {
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
}
}
@ -1356,7 +1376,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
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 +1661,12 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
this.stat = highestStat;
switch (this.stat) {
case Stat.SPD:
this.multiplier = 1.5;
break;
default:
this.multiplier = 1.3;
break;
case Stat.SPD:
this.multiplier = 1.5;
break;
default:
this.multiplier = 1.3;
break;
}
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true);
@ -1709,7 +1729,7 @@ export class SemiInvulnerableTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible
pokemon.scene.tweens.addCounter({
duration: Utils.getFrameMs(2),
duration: getFrameMs(2),
onComplete: () => pokemon.setVisible(true)
});
}
@ -1860,12 +1880,12 @@ export class SaltCuredTag extends BattlerTag {
if (ret) {
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);
if (!cancelled.value) {
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(
i18next.t("battlerTags:saltCuredLapse", {
@ -1907,11 +1927,11 @@ export class CursedTag extends BattlerTag {
if (ret) {
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);
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) }));
}
}
@ -2173,7 +2193,7 @@ export class GulpMissileTag extends BattlerTag {
return true;
}
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) {
@ -2289,7 +2309,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
* @returns `true` if the move cannot be used because the target is an ally
*/
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);
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
return true;
@ -2436,15 +2456,15 @@ export class SubstituteTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
switch (lapseType) {
case BattlerTagLapseType.PRE_MOVE:
this.onPreMove(pokemon);
break;
case BattlerTagLapseType.AFTER_MOVE:
this.onAfterMove(pokemon);
break;
case BattlerTagLapseType.HIT:
this.onHit(pokemon);
break;
case BattlerTagLapseType.PRE_MOVE:
this.onPreMove(pokemon);
break;
case BattlerTagLapseType.AFTER_MOVE:
this.onAfterMove(pokemon);
break;
case BattlerTagLapseType.HIT:
this.onHit(pokemon);
break;
}
return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse
}
@ -2506,7 +2526,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) {
@ -2724,6 +2744,44 @@ export class TelekinesisTag extends BattlerTag {
}
}
/**
* Tag that swaps the user's base ATK stat with its base DEF stat.
* @extends BattlerTag
*/
export class PowerTrickTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) {
super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true);
}
onAdd(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
/**
* Removes the Power Trick tag and reverts any stat changes if the tag is already applied.
* @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag.
*/
onOverlap(pokemon: Pokemon): void {
pokemon.removeTag(this.tagType);
}
/**
* Swaps the user's base ATK stat with its base DEF stat.
* @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped.
*/
swapStat(pokemon: Pokemon): void {
const temp = pokemon.getStat(Stat.ATK, false);
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false);
pokemon.setStat(Stat.DEF, temp, false);
}
}
/**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
* @param sourceId - The ID of the pokemon adding the tag
@ -2731,177 +2789,179 @@ export class TelekinesisTag extends BattlerTag {
*/
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) {
case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.SHELL_TRAP:
return new ShellTrapTag();
case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED:
return new InterruptedTag(sourceMove);
case BattlerTagType.CONFUSED:
return new ConfusedTag(turnCount, sourceMove);
case BattlerTagType.INFATUATED:
return new InfatuatedTag(sourceMove, sourceId);
case BattlerTagType.SEEDED:
return new SeedTag(sourceId);
case BattlerTagType.NIGHTMARE:
return new NightmareTag();
case BattlerTagType.FRENZY:
return new FrenzyTag(turnCount, sourceMove, sourceId);
case BattlerTagType.CHARGING:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId);
case BattlerTagType.ENCORE:
return new EncoreTag(sourceId);
case BattlerTagType.HELPING_HAND:
return new HelpingHandTag(sourceId);
case BattlerTagType.INGRAIN:
return new IngrainTag(sourceId);
case BattlerTagType.AQUA_RING:
return new AquaRingTag();
case BattlerTagType.DROWSY:
return new DrowsyTag();
case BattlerTagType.TRAPPED:
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
case BattlerTagType.NO_RETREAT:
return new NoRetreatTag(sourceId);
case BattlerTagType.BIND:
return new BindTag(turnCount, sourceId);
case BattlerTagType.WRAP:
return new WrapTag(turnCount, sourceId);
case BattlerTagType.FIRE_SPIN:
return new FireSpinTag(turnCount, sourceId);
case BattlerTagType.WHIRLPOOL:
return new WhirlpoolTag(turnCount, sourceId);
case BattlerTagType.CLAMP:
return new ClampTag(turnCount, sourceId);
case BattlerTagType.SAND_TOMB:
return new SandTombTag(turnCount, sourceId);
case BattlerTagType.MAGMA_STORM:
return new MagmaStormTag(turnCount, sourceId);
case BattlerTagType.SNAP_TRAP:
return new SnapTrapTag(turnCount, sourceId);
case BattlerTagType.THUNDER_CAGE:
return new ThunderCageTag(turnCount, sourceId);
case BattlerTagType.INFESTATION:
return new InfestationTag(turnCount, sourceId);
case BattlerTagType.PROTECTED:
return new ProtectedTag(sourceMove);
case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove);
case BattlerTagType.ENDURING:
return new EnduringTag(sourceMove);
case BattlerTagType.STURDY:
return new SturdyTag(sourceMove);
case BattlerTagType.PERISH_SONG:
return new PerishSongTag(turnCount);
case BattlerTagType.CENTER_OF_ATTENTION:
return new CenterOfAttentionTag(sourceMove);
case BattlerTagType.TRUANT:
return new TruantTag();
case BattlerTagType.SLOW_START:
return new SlowStartTag();
case BattlerTagType.PROTOSYNTHESIS:
return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN);
case BattlerTagType.QUARK_DRIVE:
return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC);
case BattlerTagType.FLYING:
case BattlerTagType.UNDERGROUND:
case BattlerTagType.UNDERWATER:
case BattlerTagType.HIDDEN:
return new SemiInvulnerableTag(tagType, turnCount, sourceMove);
case BattlerTagType.FIRE_BOOST:
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST:
return new CritBoostTag(tagType, sourceMove);
case BattlerTagType.DRAGON_CHEER:
return new DragonCheerTag();
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
case BattlerTagType.BYPASS_SLEEP:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove);
case BattlerTagType.IGNORE_FLYING:
return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.ROOSTED:
return new RoostedTag();
case BattlerTagType.BURNED_UP:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.DOUBLE_SHOCKED:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.SALT_CURED:
return new SaltCuredTag(sourceId);
case BattlerTagType.CURSED:
return new CursedTag(sourceId);
case BattlerTagType.CHARGED:
return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true);
case BattlerTagType.FLOATING:
return new FloatingTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED:
return new MinimizeTag();
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE:
return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId);
case BattlerTagType.DISABLED:
return new DisabledTag(sourceId);
case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]);
case BattlerTagType.IGNORE_DARK:
return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]);
case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.TAR_SHOT:
return new TarShotTag();
case BattlerTagType.ELECTRIFIED:
return new ElectrifiedTag();
case BattlerTagType.THROAT_CHOPPED:
return new ThroatChoppedTag();
case BattlerTagType.GORILLA_TACTICS:
return new GorillaTacticsTag();
case BattlerTagType.SUBSTITUTE:
return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.AUTOTOMIZED:
return new AutotomizedTag();
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag();
case BattlerTagType.HEAL_BLOCK:
return new HealBlockTag(turnCount, sourceMove);
case BattlerTagType.TORMENT:
return new TormentTag(sourceId);
case BattlerTagType.TAUNT:
return new TauntTag();
case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.SHELL_TRAP:
return new ShellTrapTag();
case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED:
return new InterruptedTag(sourceMove);
case BattlerTagType.CONFUSED:
return new ConfusedTag(turnCount, sourceMove);
case BattlerTagType.INFATUATED:
return new InfatuatedTag(sourceMove, sourceId);
case BattlerTagType.SEEDED:
return new SeedTag(sourceId);
case BattlerTagType.NIGHTMARE:
return new NightmareTag();
case BattlerTagType.FRENZY:
return new FrenzyTag(turnCount, sourceMove, sourceId);
case BattlerTagType.CHARGING:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId);
case BattlerTagType.ENCORE:
return new EncoreTag(sourceId);
case BattlerTagType.HELPING_HAND:
return new HelpingHandTag(sourceId);
case BattlerTagType.INGRAIN:
return new IngrainTag(sourceId);
case BattlerTagType.AQUA_RING:
return new AquaRingTag();
case BattlerTagType.DROWSY:
return new DrowsyTag();
case BattlerTagType.TRAPPED:
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
case BattlerTagType.NO_RETREAT:
return new NoRetreatTag(sourceId);
case BattlerTagType.BIND:
return new BindTag(turnCount, sourceId);
case BattlerTagType.WRAP:
return new WrapTag(turnCount, sourceId);
case BattlerTagType.FIRE_SPIN:
return new FireSpinTag(turnCount, sourceId);
case BattlerTagType.WHIRLPOOL:
return new WhirlpoolTag(turnCount, sourceId);
case BattlerTagType.CLAMP:
return new ClampTag(turnCount, sourceId);
case BattlerTagType.SAND_TOMB:
return new SandTombTag(turnCount, sourceId);
case BattlerTagType.MAGMA_STORM:
return new MagmaStormTag(turnCount, sourceId);
case BattlerTagType.SNAP_TRAP:
return new SnapTrapTag(turnCount, sourceId);
case BattlerTagType.THUNDER_CAGE:
return new ThunderCageTag(turnCount, sourceId);
case BattlerTagType.INFESTATION:
return new InfestationTag(turnCount, sourceId);
case BattlerTagType.PROTECTED:
return new ProtectedTag(sourceMove);
case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove);
case BattlerTagType.ENDURING:
return new EnduringTag(sourceMove);
case BattlerTagType.STURDY:
return new SturdyTag(sourceMove);
case BattlerTagType.PERISH_SONG:
return new PerishSongTag(turnCount);
case BattlerTagType.CENTER_OF_ATTENTION:
return new CenterOfAttentionTag(sourceMove);
case BattlerTagType.TRUANT:
return new TruantTag();
case BattlerTagType.SLOW_START:
return new SlowStartTag();
case BattlerTagType.PROTOSYNTHESIS:
return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN);
case BattlerTagType.QUARK_DRIVE:
return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC);
case BattlerTagType.FLYING:
case BattlerTagType.UNDERGROUND:
case BattlerTagType.UNDERWATER:
case BattlerTagType.HIDDEN:
return new SemiInvulnerableTag(tagType, turnCount, sourceMove);
case BattlerTagType.FIRE_BOOST:
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST:
return new CritBoostTag(tagType, sourceMove);
case BattlerTagType.DRAGON_CHEER:
return new DragonCheerTag();
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
case BattlerTagType.BYPASS_SLEEP:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove);
case BattlerTagType.IGNORE_FLYING:
return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.ROOSTED:
return new RoostedTag();
case BattlerTagType.BURNED_UP:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.DOUBLE_SHOCKED:
return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.SALT_CURED:
return new SaltCuredTag(sourceId);
case BattlerTagType.CURSED:
return new CursedTag(sourceId);
case BattlerTagType.CHARGED:
return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true);
case BattlerTagType.FLOATING:
return new FloatingTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED:
return new MinimizeTag();
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE:
return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId);
case BattlerTagType.DISABLED:
return new DisabledTag(sourceId);
case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]);
case BattlerTagType.IGNORE_DARK:
return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]);
case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.TAR_SHOT:
return new TarShotTag();
case BattlerTagType.ELECTRIFIED:
return new ElectrifiedTag();
case BattlerTagType.THROAT_CHOPPED:
return new ThroatChoppedTag();
case BattlerTagType.GORILLA_TACTICS:
return new GorillaTacticsTag();
case BattlerTagType.SUBSTITUTE:
return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.AUTOTOMIZED:
return new AutotomizedTag();
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag();
case BattlerTagType.HEAL_BLOCK:
return new HealBlockTag(turnCount, sourceMove);
case BattlerTagType.TORMENT:
return new TormentTag(sourceId);
case BattlerTagType.TAUNT:
return new TauntTag();
case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove);
case BattlerTagType.POWER_TRICK:
return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
}
}
@ -2915,3 +2975,22 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag {
tag.loadTag(source);
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 {
switch (berryType) {
case BerryType.SITRUS:
return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5;
case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA:
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.PETAYA:
case BerryType.APICOT:
case BerryType.SALAC:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25;
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
};
case BerryType.SITRUS:
return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5;
case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA:
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.PETAYA:
case BerryType.APICOT:
case BerryType.SALAC:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25;
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
};
}
}
@ -65,70 +65,70 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void;
export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
switch (berryType) {
case BerryType.SITRUS:
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
};
case BerryType.LUM:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
if (pokemon.status) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
}
pokemon.resetStatus(true, true);
pokemon.updateInfo();
};
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.PETAYA:
case BerryType.APICOT:
case BerryType.SALAC:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
pokemon.addTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
const stages = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
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?
if (ppRestoreMove !== undefined) {
case BerryType.SITRUS:
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
};
case BerryType.LUM:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
if (pokemon.status) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
}
pokemon.resetStatus(true, true);
pokemon.updateInfo();
};
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.PETAYA:
case BerryType.APICOT:
case BerryType.SALAC:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
pokemon.addTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
const stages = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
if (pokemon.battleData) {
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?
if (ppRestoreMove !== undefined) {
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
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 {
let trainerTypes: TrainerType[] = [];
switch (waveIndex) {
case 182:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
break;
case 184:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
break;
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 ];
break;
case 188:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
break;
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 ]) ];
break;
case 182:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
break;
case 184:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
break;
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 ];
break;
case 188:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
break;
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 ]) ];
break;
}
if (trainerTypes.length === 0) {
return false;
@ -891,45 +891,45 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
gameMode.challenges.forEach(c => {
if (c.value !== 0) {
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);
break;
case ChallengeType.STARTER_COST:
ret ||= c.applyStarterCost(args[0], args[1]);
break;
case ChallengeType.STARTER_MODIFY:
ret ||= c.applyStarterModify(args[0]);
break;
case ChallengeType.POKEMON_IN_BATTLE:
ret ||= c.applyPokemonInBattle(args[0], args[1]);
break;
case ChallengeType.FIXED_BATTLES:
ret ||= c.applyFixedBattle(args[0], args[1]);
break;
case ChallengeType.TYPE_EFFECTIVENESS:
ret ||= c.applyTypeEffectiveness(args[0]);
break;
case ChallengeType.AI_LEVEL:
ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.AI_MOVE_SLOTS:
ret ||= c.applyMoveSlot(args[0], args[1]);
break;
case ChallengeType.PASSIVE_ACCESS:
ret ||= c.applyPassiveAccess(args[0], args[1]);
break;
case ChallengeType.GAME_MODE_MODIFY:
ret ||= c.applyGameModeModify(gameMode);
break;
case ChallengeType.MOVE_ACCESS:
ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.MOVE_WEIGHT:
ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);
break;
case ChallengeType.STARTER_COST:
ret ||= c.applyStarterCost(args[0], args[1]);
break;
case ChallengeType.STARTER_MODIFY:
ret ||= c.applyStarterModify(args[0]);
break;
case ChallengeType.POKEMON_IN_BATTLE:
ret ||= c.applyPokemonInBattle(args[0], args[1]);
break;
case ChallengeType.FIXED_BATTLES:
ret ||= c.applyFixedBattle(args[0], args[1]);
break;
case ChallengeType.TYPE_EFFECTIVENESS:
ret ||= c.applyTypeEffectiveness(args[0]);
break;
case ChallengeType.AI_LEVEL:
ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.AI_MOVE_SLOTS:
ret ||= c.applyMoveSlot(args[0], args[1]);
break;
case ChallengeType.PASSIVE_ACCESS:
ret ||= c.applyPassiveAccess(args[0], args[1]);
break;
case ChallengeType.GAME_MODE_MODIFY:
ret ||= c.applyGameModeModify(gameMode);
break;
case ChallengeType.MOVE_ACCESS:
ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.MOVE_WEIGHT:
ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]);
break;
}
}
});
@ -943,18 +943,18 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
*/
export function copyChallenge(source: Challenge | any): Challenge {
switch (source.id) {
case Challenges.SINGLE_GENERATION:
return SingleGenerationChallenge.loadChallenge(source);
case Challenges.SINGLE_TYPE:
return SingleTypeChallenge.loadChallenge(source);
case Challenges.LOWER_MAX_STARTER_COST:
return LowerStarterMaxCostChallenge.loadChallenge(source);
case Challenges.LOWER_STARTER_POINTS:
return LowerStarterPointsChallenge.loadChallenge(source);
case Challenges.FRESH_START:
return FreshStartChallenge.loadChallenge(source);
case Challenges.INVERSE_BATTLE:
return InverseBattleChallenge.loadChallenge(source);
case Challenges.SINGLE_GENERATION:
return SingleGenerationChallenge.loadChallenge(source);
case Challenges.SINGLE_TYPE:
return SingleTypeChallenge.loadChallenge(source);
case Challenges.LOWER_MAX_STARTER_COST:
return LowerStarterMaxCostChallenge.loadChallenge(source);
case Challenges.LOWER_STARTER_POINTS:
return LowerStarterPointsChallenge.loadChallenge(source);
case Challenges.FRESH_START:
return FreshStartChallenge.loadChallenge(source);
case Challenges.INVERSE_BATTLE:
return InverseBattleChallenge.loadChallenge(source);
}
throw new Error("Unknown challenge copied");
}

View File

@ -1,18 +1,20 @@
import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type";
import { isNullOrUndefined } from "#app/utils";
import { Nature } from "#enums/nature";
/**
* 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 ability: Abilities | -1;
public passive: Abilities | -1;
public nature: Nature | -1;
public types: Type[];
constructor(data?: MysteryEncounterPokemonData | Partial<MysteryEncounterPokemonData>) {
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
if (!isNullOrUndefined(data)) {
Object.assign(this, data);
}
@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData {
this.spriteScale = this.spriteScale ?? -1;
this.ability = this.ability ?? -1;
this.passive = this.passive ?? -1;
this.nature = this.nature ?? -1;
this.types = this.types ?? [];
}
}

View File

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

View File

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

View File

@ -6,20 +6,20 @@ export enum Gender {
export function getGenderSymbol(gender: Gender) {
switch (gender) {
case Gender.MALE:
return "♂";
case Gender.FEMALE:
return "♀";
case Gender.MALE:
return "♂";
case Gender.FEMALE:
return "♀";
}
return "";
}
export function getGenderColor(gender: Gender, shadow?: boolean) {
switch (gender) {
case Gender.MALE:
return shadow ? "#006090" : "#40c8f8";
case Gender.FEMALE:
return shadow ? "#984038" : "#f89890";
case Gender.MALE:
return shadow ? "#006090" : "#40c8f8";
case Gender.FEMALE:
return shadow ? "#984038" : "#f89890";
}
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 trainerNameKey: string;
switch (randSeedInt(5)) {
default:
case 0:
trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck";
break;
case 1:
trainerType = TrainerType.CHERYL;
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
trainerNameKey = "cheryl";
break;
case 2:
trainerType = TrainerType.MARLEY;
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
trainerNameKey = "marley";
break;
case 3:
trainerType = TrainerType.MIRA;
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
trainerNameKey = "mira";
break;
case 4:
trainerType = TrainerType.RILEY;
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
trainerNameKey = "riley";
break;
default:
case 0:
trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck";
break;
case 1:
trainerType = TrainerType.CHERYL;
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
trainerNameKey = "cheryl";
break;
case 2:
trainerType = TrainerType.MARLEY;
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
trainerNameKey = "marley";
break;
case 3:
trainerType = TrainerType.MIRA;
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
trainerNameKey = "mira";
break;
case 4:
trainerType = TrainerType.RILEY;
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
trainerNameKey = "riley";
break;
}
// Dialogue and tokens for trainer
@ -154,7 +154,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
return initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(scene, config);
}
)
.withSimpleOption(

View File

@ -286,7 +286,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
ignorePp: true
});
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
})
.build()
@ -328,7 +328,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
});
await scene.updateModifiers(true);
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
leaveEncounterWithoutBattle(scene, true);
})
.build()
@ -359,7 +359,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
greedent.passive = true;
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
})

View File

@ -133,8 +133,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES),
new AbilityRequirement(EXTORTION_ABILITIES))
new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true))
)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,

View File

@ -42,6 +42,8 @@ import {
AttackTypeBoosterModifier,
BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier,
MegaEvolutionAccessModifier,
PokemonHeldItemModifier
} from "#app/modifier/modifier";
import i18next from "i18next";
@ -356,10 +358,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
];
} 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
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ];
// If the player has any evolution/form change items that are valid for their party,
// 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[] = [];
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);
if (nonRareEvolutionModifier) {
specialOptions.push(nonRareEvolutionModifier);

View File

@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
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 { EncounterAnim } from "#enums/encounter-anims";
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
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,
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
},
@ -353,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
newTypes.push(secondType);
// Apply the type changes (to both base and fusion, if pokemon is fused)
if (!pokemon.mysteryEncounterPokemonData) {
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (!pokemon.customPokemonData) {
pokemon.customPokemonData = new CustomPokemonData();
}
pokemon.mysteryEncounterPokemonData.types = newTypes;
pokemon.customPokemonData.types = newTypes;
if (pokemon.isFusion()) {
if (!pokemon.fusionMysteryEncounterPokemonData) {
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (!pokemon.fusionCustomPokemonData) {
pokemon.fusionCustomPokemonData = new CustomPokemonData();
}
pokemon.fusionMysteryEncounterPokemonData.types = newTypes;
pokemon.fusionCustomPokemonData.types = newTypes;
}
}
})
@ -426,15 +426,15 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
// Do ability swap
const encounter = scene.currentBattle.mysteryEncounter!;
if (pokemon.isFusion()) {
if (!pokemon.fusionMysteryEncounterPokemonData) {
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (!pokemon.fusionCustomPokemonData) {
pokemon.fusionCustomPokemonData = new CustomPokemonData();
}
pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability;
pokemon.fusionCustomPokemonData.ability = encounter.misc.ability;
} else {
if (!pokemon.mysteryEncounterPokemonData) {
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (!pokemon.customPokemonData) {
pokemon.customPokemonData = new CustomPokemonData();
}
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
pokemon.customPokemonData.ability = encounter.misc.ability;
}
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));

View File

@ -228,7 +228,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Learn its Dance
hideOricorioPokemon(scene);
await hideOricorioPokemon(scene);
leaveEncounterWithoutBattle(scene, true);
})
.build()
@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.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({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
@ -303,7 +303,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
}
}
hideOricorioPokemon(scene);
await hideOricorioPokemon(scene);
await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
})

View File

@ -182,7 +182,7 @@ export const DarkDealEncounter: MysteryEncounter =
const config: EnemyPartyConfig = {
pokemonConfigs: [ pokemonConfig ],
};
return initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)

View File

@ -222,12 +222,13 @@ export const FieryFalloutEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Do NOT await this, to prevent player from repeatedly pressing options
transitionMysteryEncounterIntroVisuals(scene, false, false, 2000);
})
.withOptionPhase(async (scene: BattleScene) => {
// Fire types help calm the Volcarona
const encounter = scene.currentBattle.mysteryEncounter!;
transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals(scene);
setEncounterRewards(scene,
{ fillRemaining: true },
undefined,

View File

@ -145,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.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({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,

View File

@ -152,7 +152,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
transitionMysteryEncounterIntroVisuals(scene, true, true);
await transitionMysteryEncounterIntroVisuals(scene, true, true);
leaveEncounterWithoutBattle(scene, true);
return true;
}

View File

@ -399,7 +399,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
if (modifier.stackCount === 0) {
scene.removeModifier(modifier);
}
scene.updateModifiers(true, true);
await scene.updateModifiers(true, true);
// Generate a trainer name
const traderName = generateRandomTraderName();

View File

@ -129,7 +129,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
*
* @param scene Battle scene
*/
async function handlePokemonGuidingYouPhase(scene: BattleScene) {
function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle;

View File

@ -147,11 +147,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 10);
return ret;
await initBattlePromise!;
}
)
.withSimpleOption(
@ -172,11 +172,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 100);
return ret;
await initBattlePromise!;
}
)
.withSimpleOption(
@ -200,11 +200,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 1000);
return ret;
await initBattlePromise!;
}
)
.withOutroDialogue([

View File

@ -184,7 +184,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
scene.unshiftPhase(new GameOverPhase(scene));
} else {
// Show which Pokemon was KOed, then start battle against Gimmighoul
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}

View File

@ -227,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.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({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -303,13 +303,16 @@ async function summonSafariPokemon(scene: BattleScene) {
scene.unshiftPhase(new SummonPhase(scene, 0, false));
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 1500, false)
.then(() => {
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
}
});
// TODO: If we await showEncounterText here, then the text will display without
// the wild Pokemon on screen, but if we don't await it, then the text never
// shows up and the IV scanner breaks. For now, we place the IV scanner code
// separately so that at least the IV scanner works.
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
}
}
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {

View File

@ -138,11 +138,11 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
newNature = randSeedInt(25) as Nature;
}
chosenPokemon.nature = newNature;
chosenPokemon.customPokemonData.nature = newNature;
encounter.setDialogueToken("newNature", getNatureName(newNature));
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100);
chosenPokemon.updateInfo();
await chosenPokemon.updateInfo();
})
.build()
)
@ -204,7 +204,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
queueEncounterMessage(scene, `${namespace}:no_bad_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100);
chosenPokemon.updateInfo();
await chosenPokemon.updateInfo();
})
.build()
)

View File

@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { BerryType } from "#enums/berry-type";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounters/slumberingSnorlax";
@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
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
};
const config: EnemyPartyConfig = {
@ -143,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -149,7 +149,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!;
const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
transitionMysteryEncounterIntroVisuals(scene, true, true);
await transitionMysteryEncounterIntroVisuals(scene, true, true);
await initBattleWithEnemyConfig(scene, config);
}
)

View File

@ -25,6 +25,7 @@ import { achvs } from "#app/system/achv";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { Type } from "#app/data/type";
import { getPokeballTintColor } from "#app/data/pokeball";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theExpertPokemonBreeder";
@ -61,7 +62,7 @@ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.JYNX ],
[ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ],
@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
}
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
@ -221,7 +222,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon1;
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
@ -245,10 +246,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
@ -273,7 +271,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon2;
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
@ -297,10 +295,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
@ -325,7 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.misc.chosenPokemon = pokemon3;
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
@ -349,10 +344,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
await doPostEncounterCleanup(scene);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
@ -521,19 +513,19 @@ function checkAchievement(scene: BattleScene) {
}
}
async function restorePartyAndHeldItems(scene: BattleScene) {
function restorePartyAndHeldItems(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
// Restore original party
scene.getParty().push(...encounter.misc.originalParty);
// Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach(pokemonHeldItemsList => {
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
pokemonHeldItemsList.forEach(heldItem => {
scene.addModifier(heldItem, true, false, false, true);
});
});
await scene.updateModifiers(true);
scene.updateModifiers(true);
}
function onGameOver(scene: BattleScene) {
@ -609,13 +601,13 @@ function onGameOver(scene: BattleScene) {
return false;
}
async function doPostEncounterCleanup(scene: BattleScene) {
function doPostEncounterCleanup(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
if (!encounter.misc.encounterFailed) {
// Give achievement if in Space biome
checkAchievement(scene);
// Give 20 friendship to the chosen pokemon
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 { BerryType } from "#enums/berry-type";
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 { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true,
bossSegments: 5,
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }),
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
nature: Nature.BOLD,
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
modifierConfigs: [
@ -201,7 +201,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
});
encounter.dialogue.outro = [];
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}
)

View File

@ -111,8 +111,8 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards
scene.currentBattle.mysteryEncounter!.doContinueEncounter = (scene: BattleScene) => {
return endTrainerBattleAndShowDialogue(scene);
scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => {
await endTrainerBattleAndShowDialogue(scene);
};
await transitionMysteryEncounterIntroVisuals(scene, true, false);
await spawnNextTrainerOrEndEncounter(scene);

View File

@ -162,7 +162,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
@ -238,7 +238,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
@ -351,7 +351,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)

View File

@ -105,7 +105,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Gain 2 Leftovers and 2 Shell Bell
transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals(scene);
await tryApplyDigRewardItems(scene);
const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
@ -136,7 +136,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
// Investigate garbage, battle Gmax Garbodor
scene.setFieldScale(0.75);
await showEncounterText(scene, `${namespace}:option.2.selected_2`);
transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals(scene);
const encounter = scene.currentBattle.mysteryEncounter!;
@ -222,7 +222,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
}
async function doGarbageDig(scene: BattleScene) {
function doGarbageDig(scene: BattleScene) {
scene.playSound("battle_anims/PRSFX- Dig2");
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
scene.playSound("battle_anims/PRSFX- Dig2");

View File

@ -210,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.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({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -12,7 +12,7 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
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 { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@ -379,10 +379,10 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
newType = randSeedInt(18) as Type;
}
newTypes.push(newType);
if (!newPokemon.mysteryEncounterPokemonData) {
newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (!newPokemon.customPokemonData) {
newPokemon.customPokemonData = new CustomPokemonData();
}
newPokemon.mysteryEncounterPokemonData.types = newTypes;
newPokemon.customPokemonData.types = newTypes;
for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id;

View File

@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { SpeciesFormKey } from "#enums/species-form-key";
import { allAbilities } from "#app/data/ability";
export interface EncounterRequirement {
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
@ -476,9 +477,11 @@ export class MoveRequirement extends EncounterPokemonRequirement {
requiredMoves: Moves[] = [];
minNumberOfPokemon: number;
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();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [ moves ];
@ -494,10 +497,15 @@ export class MoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
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 {
// 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 +567,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
requiredAbilities: Abilities[];
minNumberOfPokemon: number;
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();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ];
@ -577,16 +587,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
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 {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0);
// 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.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0);
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
return [ "ability", pokemon.getAbility().name ];
override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false));
if (!isNullOrUndefined(matchingAbility)) {
return [ "ability", allAbilities[matchingAbility].name ];
}
return [ "ability", "" ];
}

View File

@ -325,7 +325,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (activeMon.length > 0) {
this.primaryPokemon = activeMon[0];
} else {
this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0];
this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0];
}
return true;
}

View File

@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect";
import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
import PokemonSpecies from "#app/data/pokemon-species";
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 { MovePhase } from "#app/phases/move-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
@ -71,7 +71,7 @@ export interface EnemyPokemonConfig {
nickname?: string;
bossSegments?: number;
bossSegmentModifier?: number; // Additive to the determined segment number
mysteryEncounterPokemonData?: MysteryEncounterPokemonData;
customPokemonData?: CustomPokemonData;
formIndex?: number;
abilityIndex?: number;
level?: number;
@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
newTrainer.setVisible(false);
scene.field.add(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);
} 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.)
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData;
if (!isNullOrUndefined(config.customPokemonData)) {
enemyPokemon.customPokemonData = config.customPokemonData;
}
// 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;
switch (tier) {
default:
case BiomePoolTier.BOSS:
++bossEncountersByRarity[0];
break;
case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1];
break;
case BiomePoolTier.BOSS_SUPER_RARE:
++bossEncountersByRarity[2];
break;
case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3];
break;
default:
case BiomePoolTier.BOSS:
++bossEncountersByRarity[0];
break;
case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1];
break;
case BiomePoolTier.BOSS_SUPER_RARE:
++bossEncountersByRarity[2];
break;
case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3];
break;
}
}

View File

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

View File

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

View File

@ -799,8 +799,8 @@ export const pokemonFormChanges: PokemonFormChanges = {
[Species.ZYGARDE]: [
new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "10-pc", "complete", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "complete", "10-pc", new SpeciesFormChangeManualTrigger(), true)
new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true)
],
[Species.DIANCIE]: [
new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE))

View File

@ -238,8 +238,8 @@ export abstract class PokemonSpeciesForm {
isRareRegional(): boolean {
switch (this.getRegion()) {
case Region.HISUI:
return true;
case Region.HISUI:
return true;
}
return false;
@ -265,14 +265,14 @@ export abstract class PokemonSpeciesForm {
getBaseExp(): number {
let ret = this.baseExp;
switch (this.getFormSpriteKey()) {
case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.ETERNAMAX:
ret *= 1.5;
break;
case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.ETERNAMAX:
ret *= 1.5;
break;
}
return ret;
}
@ -346,29 +346,29 @@ export abstract class PokemonSpeciesForm {
}
switch (this.speciesId) {
case Species.HIPPOPOTAS:
case Species.HIPPOWDON:
case Species.UNFEZANT:
case Species.FRILLISH:
case Species.JELLICENT:
case Species.PYROAR:
ret += female ? "-f" : "";
break;
case Species.HIPPOPOTAS:
case Species.HIPPOWDON:
case Species.UNFEZANT:
case Species.FRILLISH:
case Species.JELLICENT:
case Species.PYROAR:
ret += female ? "-f" : "";
break;
}
let formSpriteKey = this.getFormSpriteKey(formIndex);
if (formSpriteKey) {
switch (this.speciesId) {
case Species.DUDUNSPARCE:
break;
case Species.ZACIAN:
case Species.ZAMAZENTA:
if (formSpriteKey.startsWith("behemoth")) {
formSpriteKey = "crowned";
}
default:
ret += `-${formSpriteKey}`;
break;
case Species.DUDUNSPARCE:
break;
case Species.ZACIAN:
case Species.ZAMAZENTA:
if (formSpriteKey.startsWith("behemoth")) {
formSpriteKey = "crowned";
}
default:
ret += `-${formSpriteKey}`;
break;
}
}
@ -383,15 +383,15 @@ export abstract class PokemonSpeciesForm {
let speciesId = this.speciesId;
if (this.speciesId > 2000) {
switch (this.speciesId) {
case Species.GALAR_SLOWPOKE:
break;
case Species.ETERNAL_FLOETTE:
break;
case Species.BLOODMOON_URSALUNA:
break;
default:
speciesId = speciesId % 2000;
break;
case Species.GALAR_SLOWPOKE:
break;
case Species.ETERNAL_FLOETTE:
break;
case Species.BLOODMOON_URSALUNA:
break;
default:
speciesId = speciesId % 2000;
break;
}
}
let ret = speciesId.toString();
@ -403,43 +403,44 @@ export abstract class PokemonSpeciesForm {
}
const formKey = forms[formIndex || 0].formKey;
switch (formKey) {
case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.GIGANTAMAX_SINGLE:
case SpeciesFormKey.GIGANTAMAX_RAPID:
case "white":
case "black":
case "therian":
case "sky":
case "gorging":
case "gulping":
case "no-ice":
case "hangry":
case "crowned":
case "eternamax":
case "four":
case "droopy":
case "stretchy":
case "hero":
case "roaming":
case "complete":
case "10":
case "10-pc":
case "super":
case "unbound":
case "pau":
case "pompom":
case "sensu":
case "dusk":
case "midnight":
case "school":
case "dawn-wings":
case "dusk-mane":
case "ultra":
ret += `-${formKey}`;
break;
case SpeciesFormKey.MEGA:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
case SpeciesFormKey.GIGANTAMAX:
case SpeciesFormKey.GIGANTAMAX_SINGLE:
case SpeciesFormKey.GIGANTAMAX_RAPID:
case "white":
case "black":
case "therian":
case "sky":
case "gorging":
case "gulping":
case "no-ice":
case "hangry":
case "crowned":
case "eternamax":
case "four":
case "droopy":
case "stretchy":
case "hero":
case "roaming":
case "complete":
case "10-complete":
case "10":
case "10-pc":
case "super":
case "unbound":
case "pau":
case "pompom":
case "sensu":
case "dusk":
case "midnight":
case "school":
case "dawn-wings":
case "dusk-mane":
case "ultra":
ret += `-${formKey}`;
break;
}
}
return ret;
@ -635,19 +636,19 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const form = this.forms[formIndex];
let key: string | null;
switch (form.formKey) {
case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.ETERNAMAX:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
key = form.formKey;
break;
default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax";
} else {
key = null;
}
case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL:
case SpeciesFormKey.ETERNAMAX:
case SpeciesFormKey.MEGA_X:
case SpeciesFormKey.MEGA_Y:
key = form.formKey;
break;
default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax";
} else {
key = null;
}
}
if (key) {
@ -689,18 +690,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
*/
private getStrengthLevelDiff(strength: PartyMemberStrength): integer {
switch (Math.min(strength, PartyMemberStrength.STRONGER)) {
case PartyMemberStrength.WEAKEST:
return 60;
case PartyMemberStrength.WEAKER:
return 40;
case PartyMemberStrength.WEAK:
return 20;
case PartyMemberStrength.AVERAGE:
return 8;
case PartyMemberStrength.STRONG:
return 4;
default:
return 0;
case PartyMemberStrength.WEAKEST:
return 60;
case PartyMemberStrength.WEAKER:
return 40;
case PartyMemberStrength.WEAK:
return 20;
case PartyMemberStrength.AVERAGE:
return 8;
case PartyMemberStrength.STRONG:
return 4;
default:
return 0;
}
}
@ -2135,7 +2136,8 @@ export function initSpecies() {
new PokemonForm("10% Forme", "10", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, null, true),
new PokemonForm("50% Forme Power Construct", "50-pc", Type.DRAGON, Type.GROUND, 5, 305, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true),
new PokemonForm("10% Forme Power Construct", "10-pc", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, "10", true),
new PokemonForm("Complete Forme", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300),
new PokemonForm("Complete Forme (50% PC)", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300),
new PokemonForm("Complete Forme (10% PC)", "10-complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300, false, "complete"),
),
new PokemonSpecies(Species.DIANCIE, 6, false, false, true, "Jewel Pokémon", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true),

View File

@ -1,4 +1,4 @@
import * as Utils from "../utils";
import { randIntRange } from "#app/utils";
import { StatusEffect } from "#enums/status-effect";
import i18next, { ParseKeys } from "i18next";
@ -6,17 +6,21 @@ export { StatusEffect };
export class Status {
public effect: StatusEffect;
public turnCount: integer;
public cureTurn: integer | null;
/** Toxic damage is `1/16 max HP * toxicTurnCount` */
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.turnCount = turnCount === undefined ? 0 : turnCount;
this.cureTurn = cureTurn!; // TODO: is this bang correct?
this.toxicTurnCount = toxicTurnCount;
this.sleepTurnsRemaining = sleepTurnsRemaining;
}
incrementTurn(): void {
this.turnCount++;
this.toxicTurnCount++;
if (this.sleepTurnsRemaining) {
this.sleepTurnsRemaining--;
}
}
isPostTurn(): boolean {
@ -26,20 +30,20 @@ export class Status {
function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string {
switch (statusEffect) {
case StatusEffect.POISON:
return "statusEffect:poison";
case StatusEffect.TOXIC:
return "statusEffect:toxic";
case StatusEffect.PARALYSIS:
return "statusEffect:paralysis";
case StatusEffect.SLEEP:
return "statusEffect:sleep";
case StatusEffect.FREEZE:
return "statusEffect:freeze";
case StatusEffect.BURN:
return "statusEffect:burn";
default:
return "statusEffect:none";
case StatusEffect.POISON:
return "statusEffect:poison";
case StatusEffect.TOXIC:
return "statusEffect:toxic";
case StatusEffect.PARALYSIS:
return "statusEffect:paralysis";
case StatusEffect.SLEEP:
return "statusEffect:sleep";
case StatusEffect.FREEZE:
return "statusEffect:freeze";
case StatusEffect.BURN:
return "statusEffect:burn";
default:
return "statusEffect:none";
}
}
@ -90,14 +94,14 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect): string {
export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number {
switch (statusEffect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
case StatusEffect.PARALYSIS:
case StatusEffect.BURN:
return 1.5;
case StatusEffect.SLEEP:
case StatusEffect.FREEZE:
return 2.5;
case StatusEffect.POISON:
case StatusEffect.TOXIC:
case StatusEffect.PARALYSIS:
case StatusEffect.BURN:
return 1.5;
case StatusEffect.SLEEP:
case StatusEffect.FREEZE:
return 2.5;
}
return 1;
@ -107,7 +111,7 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect):
* Returns a random non-volatile 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 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 {
switch (this.terrainType) {
case TerrainType.ELECTRIC:
if (attackType === Type.ELECTRIC) {
return 1.3;
}
break;
case TerrainType.GRASSY:
if (attackType === Type.GRASS) {
return 1.3;
}
break;
case TerrainType.PSYCHIC:
if (attackType === Type.PSYCHIC) {
return 1.3;
}
break;
case TerrainType.ELECTRIC:
if (attackType === Type.ELECTRIC) {
return 1.3;
}
break;
case TerrainType.GRASSY:
if (attackType === Type.GRASS) {
return 1.3;
}
break;
case TerrainType.PSYCHIC:
if (attackType === Type.PSYCHIC) {
return 1.3;
}
break;
}
return 1;
@ -56,13 +56,13 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) {
case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) {
const priority = new Utils.IntegerHolder(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
return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
}
case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) {
const priority = new Utils.IntegerHolder(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
return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
}
}
return false;
@ -71,14 +71,14 @@ export class Terrain {
export function getTerrainName(terrainType: TerrainType): string {
switch (terrainType) {
case TerrainType.MISTY:
return i18next.t("terrain:misty");
case TerrainType.ELECTRIC:
return i18next.t("terrain:electric");
case TerrainType.GRASSY:
return i18next.t("terrain:grassy");
case TerrainType.PSYCHIC:
return i18next.t("terrain:psychic");
case TerrainType.MISTY:
return i18next.t("terrain:misty");
case TerrainType.ELECTRIC:
return i18next.t("terrain:electric");
case TerrainType.GRASSY:
return i18next.t("terrain:grassy");
case TerrainType.PSYCHIC:
return i18next.t("terrain:psychic");
}
return "";
@ -87,14 +87,14 @@ export function getTerrainName(terrainType: TerrainType): string {
export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] {
switch (terrainType) {
case TerrainType.MISTY:
return [ 232, 136, 200 ];
case TerrainType.ELECTRIC:
return [ 248, 248, 120 ];
case TerrainType.GRASSY:
return [ 120, 200, 80 ];
case TerrainType.PSYCHIC:
return [ 160, 64, 160 ];
case TerrainType.MISTY:
return [ 232, 136, 200 ];
case TerrainType.ELECTRIC:
return [ 248, 248, 120 ];
case TerrainType.GRASSY:
return [ 120, 200, 80 ];
case TerrainType.PSYCHIC:
return [ 160, 64, 160 ];
}
return [ 0, 0, 0 ];

View File

@ -299,65 +299,65 @@ export class TrainerConfig {
getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType {
let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType;
switch (trainerType) {
case TrainerType.RIVAL_2:
case TrainerType.RIVAL_3:
case TrainerType.RIVAL_4:
case TrainerType.RIVAL_5:
case TrainerType.RIVAL_6:
trainerType = TrainerType.RIVAL;
break;
case TrainerType.LANCE_CHAMPION:
trainerType = TrainerType.LANCE;
break;
case TrainerType.LARRY_ELITE:
trainerType = TrainerType.LARRY;
break;
case TrainerType.ROCKET_BOSS_GIOVANNI_1:
case TrainerType.ROCKET_BOSS_GIOVANNI_2:
trainerType = TrainerType.GIOVANNI;
break;
case TrainerType.MAXIE_2:
trainerType = TrainerType.MAXIE;
break;
case TrainerType.ARCHIE_2:
trainerType = TrainerType.ARCHIE;
break;
case TrainerType.CYRUS_2:
trainerType = TrainerType.CYRUS;
break;
case TrainerType.GHETSIS_2:
trainerType = TrainerType.GHETSIS;
break;
case TrainerType.LYSANDRE_2:
trainerType = TrainerType.LYSANDRE;
break;
case TrainerType.LUSAMINE_2:
trainerType = TrainerType.LUSAMINE;
break;
case TrainerType.GUZMA_2:
trainerType = TrainerType.GUZMA;
break;
case TrainerType.ROSE_2:
trainerType = TrainerType.ROSE;
break;
case TrainerType.PENNY_2:
trainerType = TrainerType.PENNY;
break;
case TrainerType.MARNIE_ELITE:
trainerType = TrainerType.MARNIE;
break;
case TrainerType.NESSA_ELITE:
trainerType = TrainerType.NESSA;
break;
case TrainerType.BEA_ELITE:
trainerType = TrainerType.BEA;
break;
case TrainerType.ALLISTER_ELITE:
trainerType = TrainerType.ALLISTER;
break;
case TrainerType.RAIHAN_ELITE:
trainerType = TrainerType.RAIHAN;
break;
case TrainerType.RIVAL_2:
case TrainerType.RIVAL_3:
case TrainerType.RIVAL_4:
case TrainerType.RIVAL_5:
case TrainerType.RIVAL_6:
trainerType = TrainerType.RIVAL;
break;
case TrainerType.LANCE_CHAMPION:
trainerType = TrainerType.LANCE;
break;
case TrainerType.LARRY_ELITE:
trainerType = TrainerType.LARRY;
break;
case TrainerType.ROCKET_BOSS_GIOVANNI_1:
case TrainerType.ROCKET_BOSS_GIOVANNI_2:
trainerType = TrainerType.GIOVANNI;
break;
case TrainerType.MAXIE_2:
trainerType = TrainerType.MAXIE;
break;
case TrainerType.ARCHIE_2:
trainerType = TrainerType.ARCHIE;
break;
case TrainerType.CYRUS_2:
trainerType = TrainerType.CYRUS;
break;
case TrainerType.GHETSIS_2:
trainerType = TrainerType.GHETSIS;
break;
case TrainerType.LYSANDRE_2:
trainerType = TrainerType.LYSANDRE;
break;
case TrainerType.LUSAMINE_2:
trainerType = TrainerType.LUSAMINE;
break;
case TrainerType.GUZMA_2:
trainerType = TrainerType.GUZMA;
break;
case TrainerType.ROSE_2:
trainerType = TrainerType.ROSE;
break;
case TrainerType.PENNY_2:
trainerType = TrainerType.PENNY;
break;
case TrainerType.MARNIE_ELITE:
trainerType = TrainerType.MARNIE;
break;
case TrainerType.NESSA_ELITE:
trainerType = TrainerType.NESSA;
break;
case TrainerType.BEA_ELITE:
trainerType = TrainerType.BEA;
break;
case TrainerType.ALLISTER_ELITE:
trainerType = TrainerType.ALLISTER;
break;
case TrainerType.RAIHAN_ELITE:
trainerType = TrainerType.RAIHAN;
break;
}
return trainerType;
@ -564,104 +564,104 @@ export class TrainerConfig {
speciesPoolPerEvilTeamAdmin(team): TrainerTierPools {
team = team.toLowerCase();
switch (team) {
case "rocket": {
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.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 ]
};
}
case "magma": {
return {
[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.BARBOACH ],
[TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
};
}
case "aqua": {
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 ],
[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 ]
};
}
case "galactic": {
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.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 ]
};
}
case "plasma": {
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.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 ]
};
}
case "flare": {
return {
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, 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.RARE]: [ Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ]
};
}
case "aether": {
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.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 ]
};
}
case "skull": {
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.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ],
[TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ]
};
}
case "macro": {
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.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ],
[TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ]
};
}
case "star_1": {
return {
[TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ],
[TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ],
[TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ]
};
}
case "star_2": {
return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ],
[TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ],
[TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ]
};
}
case "star_3": {
return {
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
[TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ],
[TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
};
}
case "star_4": {
return {
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
[TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ],
[TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
};
}
case "star_5": {
return {
[TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ],
[TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ],
[TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ]
};
}
case "rocket": {
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.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 ]
};
}
case "magma": {
return {
[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.RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
};
}
case "aqua": {
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.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 ]
};
}
case "galactic": {
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.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 ]
};
}
case "plasma": {
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.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 ]
};
}
case "flare": {
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.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 ]
};
}
case "aether": {
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.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 ]
};
}
case "skull": {
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.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ],
[TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ]
};
}
case "macro": {
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.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ],
[TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ]
};
}
case "star_1": {
return {
[TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ],
[TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ],
[TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ]
};
}
case "star_2": {
return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ],
[TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ],
[TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ]
};
}
case "star_3": {
return {
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
[TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ],
[TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
};
}
case "star_4": {
return {
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
[TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ],
[TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
};
}
case "star_5": {
return {
[TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ],
[TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ],
[TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ]
};
}
}
console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`);
@ -1504,7 +1504,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ],
[TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ],
[TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON ],
[TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
}),
[TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1540,9 +1540,9 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ],
[TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP ],
[TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP, Species.FOONGUS ],
[TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ],
[TrainerPoolTier.SUPER_RARE]: [ Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ]
[TrainerPoolTier.SUPER_RARE]: [ Species.NOIBAT, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ]
}),
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1893,7 +1893,10 @@ export const trainerConfigs: TrainerConfigs = {
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ]))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DUGTRIO, Species.ALOLA_DUGTRIO ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HONCHKROW ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.NIDOKING, Species.NIDOQUEEN ]))
@ -1945,6 +1948,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => {
@ -1967,6 +1971,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -1985,6 +1990,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.EMPOLEON, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => {
@ -2010,6 +2016,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2031,6 +2038,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})),
[TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss", [], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => {
@ -2049,6 +2057,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2065,6 +2074,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})),
[TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss", [], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GENESECT ], TrainerSlot.TRAINER, true, p => {
@ -2084,6 +2094,11 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
if (p.species.speciesId === Species.HYDREIGON) {
p.gender = Gender.MALE;
} else if (p.species.speciesId === Species.IRON_JUGULIS) {
p.gender = Gender.GENDERLESS;
}
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2105,6 +2120,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyarados
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss", [], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => {
@ -2124,6 +2140,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyardos
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2131,7 +2148,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.LUSAMINE]: new TrainerConfig(++t).setName("Lusamine").initForEvilTeamLeader("Aether Boss", []).setMixedBattleBgm("battle_aether_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ]))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.LILLIGANT, Species.HISUI_LILLIGANT ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ]))
@ -2148,7 +2168,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.STAKATAKA, Species.CELESTEELA, Species.GUZZLORD ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
@ -2191,6 +2214,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GOLISOPOD ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL;
})),
[TrainerType.GUZMA_2]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", [], true).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
@ -2198,6 +2222,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.abilityIndex = 2; //Anticipation
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
@ -2239,6 +2264,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah
p.generateName();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})),
[TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
@ -2262,6 +2288,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah
p.generateName();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})),
[TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ]))
@ -2275,8 +2302,9 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2290,20 +2318,21 @@ export const trainerConfigs: TrainerConfigs = {
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2318,7 +2347,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL;
}))
.setGenModifiersFunc(party => {
const teraPokemon = party[3];
const teraPokemon = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)

View File

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

View File

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

View File

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

View File

@ -80,6 +80,7 @@ export enum BattlerTagType {
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
POWER_TRICK = "POWER_TRICK",
HEAL_BLOCK = "HEAL_BLOCK",
TORMENT = "TORMENT",
TAUNT = "TAUNT",

View File

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

View File

@ -129,18 +129,18 @@ export class Arena {
if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) {
case (ret.baseTotal >= 720):
regen = level < 90;
break;
case (ret.baseTotal >= 670):
regen = level < 70;
break;
case (ret.baseTotal >= 580):
regen = level < 50;
break;
default:
regen = level < 30;
break;
case (ret.baseTotal >= 720):
regen = level < 90;
break;
case (ret.baseTotal >= 670):
regen = level < 70;
break;
case (ret.baseTotal >= 580):
regen = level < 50;
break;
default:
regen = level < 30;
break;
}
}
}
@ -177,41 +177,41 @@ export class Arena {
getSpeciesFormIndex(species: PokemonSpecies): integer {
switch (species.speciesId) {
case Species.BURMY:
case Species.WORMADAM:
switch (this.biomeType) {
case Biome.BEACH:
return 1;
case Biome.SLUM:
return 2;
}
break;
case Species.ROTOM:
switch (this.biomeType) {
case Biome.VOLCANO:
return 1;
case Biome.SEA:
return 2;
case Biome.ICE_CAVE:
return 3;
case Biome.MOUNTAIN:
return 4;
case Biome.TALL_GRASS:
return 5;
}
break;
case Species.LYCANROC:
const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) {
case TimeOfDay.DAY:
case TimeOfDay.DAWN:
return 0;
case TimeOfDay.DUSK:
return 2;
case TimeOfDay.NIGHT:
return 1;
}
break;
case Species.BURMY:
case Species.WORMADAM:
switch (this.biomeType) {
case Biome.BEACH:
return 1;
case Biome.SLUM:
return 2;
}
break;
case Species.ROTOM:
switch (this.biomeType) {
case Biome.VOLCANO:
return 1;
case Biome.SEA:
return 2;
case Biome.ICE_CAVE:
return 3;
case Biome.MOUNTAIN:
return 4;
case Biome.TALL_GRASS:
return 5;
}
break;
case Species.LYCANROC:
const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) {
case TimeOfDay.DAY:
case TimeOfDay.DAWN:
return 0;
case TimeOfDay.DUSK:
return 2;
case TimeOfDay.NIGHT:
return 1;
}
break;
}
return 0;
@ -219,70 +219,70 @@ export class Arena {
getTypeForBiome() {
switch (this.biomeType) {
case Biome.TOWN:
case Biome.PLAINS:
case Biome.METROPOLIS:
return Type.NORMAL;
case Biome.GRASS:
case Biome.TALL_GRASS:
return Type.GRASS;
case Biome.FOREST:
case Biome.JUNGLE:
return Type.BUG;
case Biome.SLUM:
case Biome.SWAMP:
return Type.POISON;
case Biome.SEA:
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
return Type.WATER;
case Biome.MOUNTAIN:
return Type.FLYING;
case Biome.BADLANDS:
return Type.GROUND;
case Biome.CAVE:
case Biome.DESERT:
return Type.ROCK;
case Biome.ICE_CAVE:
case Biome.SNOWY_FOREST:
return Type.ICE;
case Biome.MEADOW:
case Biome.FAIRY_CAVE:
case Biome.ISLAND:
return Type.FAIRY;
case Biome.POWER_PLANT:
return Type.ELECTRIC;
case Biome.VOLCANO:
return Type.FIRE;
case Biome.GRAVEYARD:
case Biome.TEMPLE:
return Type.GHOST;
case Biome.DOJO:
case Biome.CONSTRUCTION_SITE:
return Type.FIGHTING;
case Biome.FACTORY:
case Biome.LABORATORY:
return Type.STEEL;
case Biome.RUINS:
case Biome.SPACE:
return Type.PSYCHIC;
case Biome.WASTELAND:
case Biome.END:
return Type.DRAGON;
case Biome.ABYSS:
return Type.DARK;
default:
return Type.UNKNOWN;
case Biome.TOWN:
case Biome.PLAINS:
case Biome.METROPOLIS:
return Type.NORMAL;
case Biome.GRASS:
case Biome.TALL_GRASS:
return Type.GRASS;
case Biome.FOREST:
case Biome.JUNGLE:
return Type.BUG;
case Biome.SLUM:
case Biome.SWAMP:
return Type.POISON;
case Biome.SEA:
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
return Type.WATER;
case Biome.MOUNTAIN:
return Type.FLYING;
case Biome.BADLANDS:
return Type.GROUND;
case Biome.CAVE:
case Biome.DESERT:
return Type.ROCK;
case Biome.ICE_CAVE:
case Biome.SNOWY_FOREST:
return Type.ICE;
case Biome.MEADOW:
case Biome.FAIRY_CAVE:
case Biome.ISLAND:
return Type.FAIRY;
case Biome.POWER_PLANT:
return Type.ELECTRIC;
case Biome.VOLCANO:
return Type.FIRE;
case Biome.GRAVEYARD:
case Biome.TEMPLE:
return Type.GHOST;
case Biome.DOJO:
case Biome.CONSTRUCTION_SITE:
return Type.FIGHTING;
case Biome.FACTORY:
case Biome.LABORATORY:
return Type.STEEL;
case Biome.RUINS:
case Biome.SPACE:
return Type.PSYCHIC;
case Biome.WASTELAND:
case Biome.END:
return Type.DRAGON;
case Biome.ABYSS:
return Type.DARK;
default:
return Type.UNKNOWN;
}
}
getBgTerrainColorRatioForBiome(): number {
switch (this.biomeType) {
case Biome.SPACE:
return 1;
case Biome.END:
return 0;
case Biome.SPACE:
return 1;
case Biome.END:
return 0;
}
return 131 / 180;
@ -424,52 +424,52 @@ export class Arena {
*/
getTrainerChance(): integer {
switch (this.biomeType) {
case Biome.METROPOLIS:
return 2;
case Biome.SLUM:
case Biome.BEACH:
case Biome.DOJO:
case Biome.CONSTRUCTION_SITE:
return 4;
case Biome.PLAINS:
case Biome.GRASS:
case Biome.LAKE:
case Biome.CAVE:
return 6;
case Biome.TALL_GRASS:
case Biome.FOREST:
case Biome.SEA:
case Biome.SWAMP:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.DESERT:
case Biome.MEADOW:
case Biome.POWER_PLANT:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.SNOWY_FOREST:
return 8;
case Biome.ICE_CAVE:
case Biome.VOLCANO:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.JUNGLE:
case Biome.FAIRY_CAVE:
return 12;
case Biome.SEABED:
case Biome.ABYSS:
case Biome.SPACE:
case Biome.TEMPLE:
return 16;
default:
return 0;
case Biome.METROPOLIS:
return 2;
case Biome.SLUM:
case Biome.BEACH:
case Biome.DOJO:
case Biome.CONSTRUCTION_SITE:
return 4;
case Biome.PLAINS:
case Biome.GRASS:
case Biome.LAKE:
case Biome.CAVE:
return 6;
case Biome.TALL_GRASS:
case Biome.FOREST:
case Biome.SEA:
case Biome.SWAMP:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.DESERT:
case Biome.MEADOW:
case Biome.POWER_PLANT:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.SNOWY_FOREST:
return 8;
case Biome.ICE_CAVE:
case Biome.VOLCANO:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.JUNGLE:
case Biome.FAIRY_CAVE:
return 12;
case Biome.SEABED:
case Biome.ABYSS:
case Biome.SPACE:
case Biome.TEMPLE:
return 16;
default:
return 0;
}
}
getTimeOfDay(): TimeOfDay {
switch (this.biomeType) {
case Biome.ABYSS:
return TimeOfDay.NIGHT;
case Biome.ABYSS:
return TimeOfDay.NIGHT;
}
const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40;
@ -491,35 +491,35 @@ export class Arena {
isOutside(): boolean {
switch (this.biomeType) {
case Biome.SEABED:
case Biome.CAVE:
case Biome.ICE_CAVE:
case Biome.POWER_PLANT:
case Biome.DOJO:
case Biome.FACTORY:
case Biome.ABYSS:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.LABORATORY:
return false;
default:
return true;
case Biome.SEABED:
case Biome.CAVE:
case Biome.ICE_CAVE:
case Biome.POWER_PLANT:
case Biome.DOJO:
case Biome.FACTORY:
case Biome.ABYSS:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.LABORATORY:
return false;
default:
return true;
}
}
overrideTint(): [integer, integer, integer] {
switch (Overrides.ARENA_TINT_OVERRIDE) {
case TimeOfDay.DUSK:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
break;
case (TimeOfDay.NIGHT):
return [ 64, 64, 64 ];
break;
case TimeOfDay.DAWN:
case TimeOfDay.DAY:
default:
return [ 128, 128, 128 ];
break;
case TimeOfDay.DUSK:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
break;
case (TimeOfDay.NIGHT):
return [ 64, 64, 64 ];
break;
case TimeOfDay.DAWN:
case TimeOfDay.DAY:
default:
return [ 128, 128, 128 ];
break;
}
}
@ -528,10 +528,10 @@ export class Arena {
return this.overrideTint();
}
switch (this.biomeType) {
case Biome.ABYSS:
return [ 64, 64, 64 ];
default:
return [ 128, 128, 128 ];
case Biome.ABYSS:
return [ 64, 64, 64 ];
default:
return [ 128, 128, 128 ];
}
}
@ -544,8 +544,8 @@ export class Arena {
}
switch (this.biomeType) {
default:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
default:
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();
}
switch (this.biomeType) {
case Biome.ABYSS:
case Biome.SPACE:
case Biome.END:
return this.getDayTint();
case Biome.ABYSS:
case Biome.SPACE:
case Biome.END:
return this.getDayTint();
}
if (!this.isOutside()) {
@ -565,8 +565,8 @@ export class Arena {
}
switch (this.biomeType) {
default:
return [ 48, 48, 98 ];
default:
return [ 48, 48, 98 ];
}
}
@ -579,26 +579,28 @@ export class Arena {
* Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param side {@linkcode ArenaTagSide} which side's arena tags to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need
*/
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, ...args: unknown[]): void {
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, simulated: boolean, ...args: unknown[]): void {
let tags = typeof tagType === "string"
? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType);
if (side !== ArenaTagSide.BOTH) {
tags = tags.filter(t => t.side === side);
}
tags.forEach(t => t.apply(this, args));
tags.forEach(t => t.apply(this, simulated, ...args));
}
/**
* Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified)
* by calling {@linkcode applyTagsForSide()}
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need
*/
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, simulated: boolean, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
}
/**
@ -745,77 +747,77 @@ export class Arena {
getBgmLoopPoint(): number {
switch (this.biomeType) {
case Biome.TOWN:
return 7.288;
case Biome.PLAINS:
return 17.485;
case Biome.GRASS:
return 1.995;
case Biome.TALL_GRASS:
return 9.608;
case Biome.METROPOLIS:
return 141.470;
case Biome.FOREST:
return 4.294;
case Biome.SEA:
return 0.024;
case Biome.SWAMP:
return 4.461;
case Biome.BEACH:
return 3.462;
case Biome.LAKE:
return 7.215;
case Biome.SEABED:
return 2.600;
case Biome.MOUNTAIN:
return 4.018;
case Biome.BADLANDS:
return 17.790;
case Biome.CAVE:
return 14.240;
case Biome.DESERT:
return 1.143;
case Biome.ICE_CAVE:
return 0.000;
case Biome.MEADOW:
return 3.891;
case Biome.POWER_PLANT:
return 9.447;
case Biome.VOLCANO:
return 17.637;
case Biome.GRAVEYARD:
return 3.232;
case Biome.DOJO:
return 6.205;
case Biome.FACTORY:
return 4.985;
case Biome.RUINS:
return 0.000;
case Biome.WASTELAND:
return 6.336;
case Biome.ABYSS:
return 5.130;
case Biome.SPACE:
return 20.036;
case Biome.CONSTRUCTION_SITE:
return 1.222;
case Biome.JUNGLE:
return 0.000;
case Biome.FAIRY_CAVE:
return 4.542;
case Biome.TEMPLE:
return 2.547;
case Biome.ISLAND:
return 2.751;
case Biome.LABORATORY:
return 114.862;
case Biome.SLUM:
return 0.000;
case Biome.SNOWY_FOREST:
return 3.047;
default:
console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`);
return 0;
case Biome.TOWN:
return 7.288;
case Biome.PLAINS:
return 17.485;
case Biome.GRASS:
return 1.995;
case Biome.TALL_GRASS:
return 9.608;
case Biome.METROPOLIS:
return 141.470;
case Biome.FOREST:
return 4.294;
case Biome.SEA:
return 0.024;
case Biome.SWAMP:
return 4.461;
case Biome.BEACH:
return 3.462;
case Biome.LAKE:
return 7.215;
case Biome.SEABED:
return 2.600;
case Biome.MOUNTAIN:
return 4.018;
case Biome.BADLANDS:
return 17.790;
case Biome.CAVE:
return 14.240;
case Biome.DESERT:
return 1.143;
case Biome.ICE_CAVE:
return 0.000;
case Biome.MEADOW:
return 3.891;
case Biome.POWER_PLANT:
return 9.447;
case Biome.VOLCANO:
return 17.637;
case Biome.GRAVEYARD:
return 3.232;
case Biome.DOJO:
return 6.205;
case Biome.FACTORY:
return 4.985;
case Biome.RUINS:
return 0.000;
case Biome.WASTELAND:
return 6.336;
case Biome.ABYSS:
return 5.130;
case Biome.SPACE:
return 20.036;
case Biome.CONSTRUCTION_SITE:
return 1.222;
case Biome.JUNGLE:
return 0.000;
case Biome.FAIRY_CAVE:
return 4.542;
case Biome.TEMPLE:
return 2.547;
case Biome.ISLAND:
return 2.751;
case Biome.LABORATORY:
return 114.862;
case Biome.SLUM:
return 0.000;
case Biome.SNOWY_FOREST:
return 3.047;
default:
console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`);
return 0;
}
}
}
@ -826,32 +828,32 @@ export function getBiomeKey(biome: Biome): string {
export function getBiomeHasProps(biomeType: Biome): boolean {
switch (biomeType) {
case Biome.METROPOLIS:
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.CAVE:
case Biome.DESERT:
case Biome.ICE_CAVE:
case Biome.MEADOW:
case Biome.POWER_PLANT:
case Biome.VOLCANO:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.ABYSS:
case Biome.CONSTRUCTION_SITE:
case Biome.JUNGLE:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.SNOWY_FOREST:
case Biome.ISLAND:
case Biome.LABORATORY:
case Biome.END:
return true;
case Biome.METROPOLIS:
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.CAVE:
case Biome.DESERT:
case Biome.ICE_CAVE:
case Biome.MEADOW:
case Biome.POWER_PLANT:
case Biome.VOLCANO:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.ABYSS:
case Biome.CONSTRUCTION_SITE:
case Biome.JUNGLE:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.SNOWY_FOREST:
case Biome.ISLAND:
case Biome.LABORATORY:
case Biome.END:
return true;
}
return false;

View File

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

View File

@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
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 { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { 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 { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils";
@ -19,10 +19,10 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } 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 { 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 { BattlerIndex } from "#app/battle";
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 { PokemonAnimType } from "#enums/pokemon-anim-type";
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 { 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";
@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionVariant: Variant;
public fusionGender: Gender;
public fusionLuck: integer;
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null;
public fusionCustomPokemonData: CustomPokemonData | null;
private summonDataPrimer: PokemonSummonData | null;
@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public battleData: PokemonBattleData;
public battleSummonData: PokemonBattleSummonData;
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 */
public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
this.nature = dataSource.nature || 0 as Nature;
this.nickname = dataSource.nickname;
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
this.moveset = dataSource.moveset;
this.status = dataSource.status!; // TODO: is this bang correct?
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = dataSource.fusionVariant || 0;
this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck;
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData;
this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData;
this.usedTMs = dataSource.usedTMs ?? [];
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData);
} else {
this.id = Utils.randSeedInt(4294967296);
this.ivs = ivs || Utils.getIvsFromId(this.id);
@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.variant = this.shiny ? this.generateVariant() : 0;
}
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
this.customPokemonData = new CustomPokemonData();
if (nature !== undefined) {
this.setNature(nature);
@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.generateNature();
}
this.natureOverride = -1;
this.friendship = species.baseFriendship;
this.metLevel = level;
this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1;
@ -593,8 +590,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const formKey = this.getFormKey();
if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") {
return 1.5;
} else if (this.mysteryEncounterPokemonData.spriteScale > 0) {
return this.mysteryEncounterPokemonData.spriteScale;
} else if (this.customPokemonData.spriteScale > 0) {
return this.customPokemonData.spriteScale;
}
return 1;
}
@ -680,12 +677,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getFieldPositionOffset(): [ number, number ] {
switch (this.fieldPosition) {
case FieldPosition.CENTER:
return [ 0, 0 ];
case FieldPosition.LEFT:
return [ -32, -8 ];
case FieldPosition.RIGHT:
return [ 32, 0 ];
case FieldPosition.CENTER:
return [ 0, 0 ];
case FieldPosition.LEFT:
return [ -32, -8 ];
case FieldPosition.RIGHT:
return [ 32, 0 ];
}
}
@ -917,39 +914,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated);
switch (stat) {
case Stat.ATK:
if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1;
}
break;
case Stat.DEF:
if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) {
ret *= 1.5;
}
break;
case Stat.SPATK:
break;
case Stat.SPDEF:
if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
ret *= 1.5;
}
break;
case Stat.SPD:
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) {
ret *= 2;
}
if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) {
ret >>= 2;
}
case Stat.ATK:
if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1;
}
break;
case Stat.DEF:
if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) {
ret *= 1.5;
}
break;
case Stat.SPATK:
break;
case Stat.SPDEF:
if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
ret *= 1.5;
}
break;
case Stat.SPD:
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) {
ret *= 2;
}
if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) {
ret >>= 2;
}
if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1;
}
if (this.status && this.status.effect === StatusEffect.PARALYSIS) {
ret >>= 1;
}
break;
if (this.getTag(BattlerTagType.SLOW_START)) {
ret >>= 1;
}
if (this.status && this.status.effect === StatusEffect.PARALYSIS) {
ret >>= 1;
}
break;
}
const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag;
@ -1023,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
getNature(): Nature {
return this.natureOverride !== -1 ? this.natureOverride : this.nature;
return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature;
}
setNature(nature: Nature): void {
@ -1198,15 +1195,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
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
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
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
if (fusionSpeciesForm) {
// 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]) {
types.push(fusionMETypes[1]);
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
@ -1218,8 +1215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) {
types.push(this.mysteryEncounterPokemonData.types[1]);
if (types.length === 1 && this.customPokemonData.types.length >= 2) {
types.push(this.customPokemonData.types[1]);
}
} else {
const speciesForm = this.getSpeciesForm(ignoreOverride);
@ -1230,7 +1227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (fusionSpeciesForm) {
// Check if the fusion Pokemon also had "permanently changed" types
// 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]) {
types.push(fusionMETypes[1]);
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
@ -1262,6 +1259,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;
}
@ -1288,14 +1290,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
}
if (this.isFusion()) {
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) {
return allAbilities[this.fusionMysteryEncounterPokemonData.ability];
if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) {
return allAbilities[this.fusionCustomPokemonData.ability];
} else {
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
}
}
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.ability];
if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) {
return allAbilities[this.customPokemonData.ability];
}
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
if (abilityId === Abilities.NONE) {
@ -1318,8 +1320,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
}
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.passive];
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
return allAbilities[this.customPokemonData.passive];
}
let starterSpeciesId = this.species.speciesId;
@ -1538,7 +1540,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder);
if (this.getTag(BattlerTagType.ELECTRIFIED)) {
moveTypeHolder.value = Type.ELECTRIC;
}
@ -2018,7 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = 0;
this.fusionGender = 0;
this.fusionLuck = 0;
this.fusionMysteryEncounterPokemonData = null;
this.fusionCustomPokemonData = null;
this.generateName();
this.calculateStats();
@ -2187,7 +2189,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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 {
@ -2285,6 +2290,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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 {
const ret = this.getOpponents()[targetIndex];
if (ret.summonData) {
@ -2338,14 +2352,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (opponent) {
if (isCritical) {
switch (stat) {
case Stat.ATK:
case Stat.SPATK:
statStage.value = Math.max(statStage.value, 0);
break;
case Stat.DEF:
case Stat.SPDEF:
statStage.value = Math.min(statStage.value, 0);
break;
case Stat.ATK:
case Stat.SPATK:
statStage.value = Math.max(statStage.value, 0);
break;
case Stat.DEF:
case Stat.SPDEF:
statStage.value = Math.min(statStage.value, 0);
break;
}
}
if (!ignoreOppAbility) {
@ -2605,7 +2619,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) */
const screenMultiplier = new Utils.NumberHolder(1);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, 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:
@ -2795,30 +2809,31 @@ 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
if (source.turnData.hitsLeft === 1 || this.isFainted()) {
switch (result) {
case HitResult.SUPER_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective"));
break;
case HitResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective"));
break;
case HitResult.ONE_HIT_KO:
this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO"));
break;
case HitResult.SUPER_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective"));
break;
case HitResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective"));
break;
case HitResult.ONE_HIT_KO:
this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO"));
break;
}
}
if (this.isFainted()) {
// set splice index here, so future scene queues happen before FaintedPhase
this.scene.setPhaseQueueSplice();
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo));
if (!isNullOrUndefined(destinyTag) && dmg) {
// Destiny Bond will activate during FaintPhase
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, source));
} else {
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo));
}
this.destroySubstitute();
this.resetSummonData();
}
if (dmg) {
destinyTag?.lapse(source, BattlerTagLapseType.CUSTOM);
}
return result;
}
}
@ -3048,6 +3063,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
continue;
}
if (tag instanceof PowerTrickTag) {
tag.swapStat(this);
}
this.summonData.tags.push(tag);
}
@ -3342,61 +3361,60 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
const types = this.getTypes(true, true);
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) {
return false;
}
switch (effect) {
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;
}
const types = this.getTypes(true, true);
// 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) {
switch (effect) {
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
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 true;
});
if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) {
if (poisonImmunity.includes(true)) {
break;
case StatusEffect.PARALYSIS:
if (this.isOfType(Type.ELECTRIC)) {
return false;
}
}
break;
case StatusEffect.PARALYSIS:
if (this.isOfType(Type.ELECTRIC)) {
return false;
}
break;
case StatusEffect.SLEEP:
if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) {
return false;
}
break;
case StatusEffect.FREEZE:
if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) {
return false;
}
break;
case StatusEffect.BURN:
if (this.isOfType(Type.FIRE)) {
return false;
}
break;
break;
case StatusEffect.SLEEP:
if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) {
return false;
}
break;
case StatusEffect.FREEZE:
if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) {
return false;
}
break;
case StatusEffect.BURN:
if (this.isOfType(Type.FIRE)) {
return false;
}
break;
}
const cancelled = new Utils.BooleanHolder(false);
@ -3412,7 +3430,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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)) {
return false;
}
@ -3426,15 +3444,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
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;
}
let statusCureTurn: Utils.IntegerHolder;
let sleepTurnsRemaining: Utils.NumberHolder;
if (effect === StatusEffect.SLEEP) {
statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4));
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn);
sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4));
this.setFrameRate(4);
@ -3454,9 +3471,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
this.status = new Status(effect, 0, statusCureTurn?.value);
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
if (effect !== StatusEffect.FAINT) {
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
@ -3494,6 +3511,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 {
this.summonDataPrimer = summonDataPrimer;
}
@ -3966,16 +4000,20 @@ export class PlayerPokemon extends Pokemon {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (Overrides.STATUS_OVERRIDE) {
this.status = new Status(Overrides.STATUS_OVERRIDE);
this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4);
}
if (Overrides.SHINY_OVERRIDE) {
this.shiny = true;
this.initShinySparkle();
if (Overrides.VARIANT_OVERRIDE) {
this.variant = Overrides.VARIANT_OVERRIDE;
}
} else if (Overrides.SHINY_OVERRIDE === false) {
this.shiny = false;
}
if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) {
this.variant = Overrides.VARIANT_OVERRIDE;
}
if (!dataSource) {
if (this.scene.gameMode.isDaily) {
this.generateAndPopulateMoveset();
@ -4077,7 +4115,7 @@ export class PlayerPokemon extends Pokemon {
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship);
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) {
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
@ -4287,12 +4325,33 @@ export class PlayerPokemon extends Pokemon {
changeForm(formChange: SpeciesFormChange): Promise<void> {
return new Promise(resolve => {
const previousFormIndex = this.formIndex;
this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0);
this.generateName();
const abilityCount = this.getSpeciesForm().getAbilityCount();
if (this.abilityIndex >= abilityCount) { // Shouldn't happen
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.generateCompatibleTms();
const updateAndResolve = () => {
@ -4329,7 +4388,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionVariant = pokemon.variant;
this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck;
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData;
this.fusionCustomPokemonData = pokemon.customPokemonData;
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
this.pauseEvolutions = true;
}
@ -4421,7 +4480,7 @@ export class EnemyPokemon extends Pokemon {
}
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) {
@ -4430,9 +4489,11 @@ export class EnemyPokemon extends Pokemon {
const speciesId = this.species.speciesId;
if (speciesId in Overrides.OPP_FORM_OVERRIDES
if (
speciesId in Overrides.OPP_FORM_OVERRIDES
&& 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;
}
@ -4443,10 +4504,13 @@ export class EnemyPokemon extends Pokemon {
if (Overrides.OPP_SHINY_OVERRIDE) {
this.shiny = true;
this.initShinySparkle();
} else if (Overrides.OPP_SHINY_OVERRIDE === false) {
this.shiny = false;
}
if (this.shiny) {
this.variant = this.generateVariant();
if (Overrides.OPP_VARIANT_OVERRIDE) {
if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
}
}
@ -4496,35 +4560,35 @@ export class EnemyPokemon extends Pokemon {
generateAndPopulateMoveset(formIndex?: integer): void {
switch (true) {
case (this.species.speciesId === Species.SMEARGLE):
this.moveset = [
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)
case (this.species.speciesId === Species.SMEARGLE):
this.moveset = [
new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH),
new PokemonMove(Moves.SKETCH)
];
if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) {
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT);
}
break;
default:
super.generateAndPopulateMoveset();
break;
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)) {
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT);
}
break;
default:
super.generateAndPopulateMoveset();
break;
}
}
@ -4564,135 +4628,135 @@ export class EnemyPokemon extends Pokemon {
}
}
switch (this.aiType) {
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?
return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM:
case AiType.SMART:
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?
return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM:
case AiType.SMART:
/**
* Search this Pokemon's move pool for moves that will KO an opposing target.
* If there are any moves that can KO an opponent (i.e. a player Pokemon),
* those moves are the only ones considered for selection on this turn.
*/
const koMoves = movePool.filter(pkmnMove => {
if (!pkmnMove) {
return false;
}
const koMoves = movePool.filter(pkmnMove => {
if (!pkmnMove) {
return false;
}
const move = pkmnMove.getMove()!;
if (move.moveTarget === MoveTarget.ATTACKER) {
return false;
}
const move = pkmnMove.getMove()!;
if (move.moveTarget === MoveTarget.ATTACKER) {
return false;
}
const fieldPokemon = this.scene.getField();
const moveTargets = getMoveTargets(this, move.id).targets
.map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer());
// Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
const fieldPokemon = this.scene.getField();
const moveTargets = getMoveTargets(this, move.id).targets
.map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer());
// Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
return move.category !== MoveCategory.STATUS
return move.category !== MoveCategory.STATUS
&& moveTargets.some(p => {
const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id);
return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp;
});
}, this);
}, this);
if (koMoves.length > 0) {
movePool = koMoves;
}
if (koMoves.length > 0) {
movePool = koMoves;
}
/**
/**
* Move selection is based on the move's calculated "benefit score" against the
* best possible target(s) (as determined by {@linkcode getNextTargets}).
* For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
*/
const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct?
for (const m in movePool) {
const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove();
const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct?
for (const m in movePool) {
const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove();
let moveScore = moveScores[m];
const targetScores: integer[] = [];
let moveScore = moveScores[m];
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
if (mt === BattlerIndex.ATTACKER) {
break;
}
if (mt === BattlerIndex.ATTACKER) {
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.
* 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);
if (Number.isNaN(targetScore)) {
console.error(`Move ${move.name} returned score of NaN`);
targetScore = 0;
}
/**
let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
if (Number.isNaN(targetScore)) {
console.error(`Move ${move.name} returned score of NaN`);
targetScore = 0;
}
/**
* If this move is unimplemented, or the move is known to fail when used, set its
* 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)) {
targetScore = -20;
} else if (move instanceof AttackMove) {
if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) {
targetScore = -20;
} else if (move instanceof AttackMove) {
/**
* Attack moves are given extra multipliers to their base benefit score based on
* the move's type effectiveness against the target and whether the move is a STAB move.
*/
const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed);
if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness;
if (this.isOfType(move.type)) {
targetScore *= 1.5;
const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed);
if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness;
if (this.isOfType(move.type)) {
targetScore *= 1.5;
}
} else if (effectiveness) {
targetScore /= effectiveness;
if (this.isOfType(move.type)) {
targetScore /= 1.5;
}
}
} else if (effectiveness) {
targetScore /= effectiveness;
if (this.isOfType(move.type)) {
targetScore /= 1.5;
/** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */
if (!targetScore) {
targetScore = -20;
}
}
/** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */
if (!targetScore) {
targetScore = -20;
}
targetScores.push(targetScore);
}
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
moveScores[m] = moveScore;
}
console.log(moveScores);
console.log(moveScores);
// Sort the move pool in decreasing order of move score
const sortedMovePool = movePool.slice(0);
sortedMovePool.sort((a, b) => {
const scoreA = moveScores[movePool.indexOf(a)];
const scoreB = moveScores[movePool.indexOf(b)];
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
});
let r = 0;
if (this.aiType === AiType.SMART_RANDOM) {
// Sort the move pool in decreasing order of move score
const sortedMovePool = movePool.slice(0);
sortedMovePool.sort((a, b) => {
const scoreA = moveScores[movePool.indexOf(a)];
const scoreB = moveScores[movePool.indexOf(b)];
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
});
let r = 0;
if (this.aiType === AiType.SMART_RANDOM) {
// Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll)
while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) {
r++;
}
} else if (this.aiType === AiType.SMART) {
while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) {
r++;
}
} else if (this.aiType === AiType.SMART) {
// The chance to advance to the next best move increases when the compared moves' scores are closer to each other.
while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 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)) {
r++;
r++;
}
}
}
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] };
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] };
}
}
@ -4840,10 +4904,10 @@ export class EnemyPokemon extends Pokemon {
}
switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS:
if (!this.formIndex && this.bossSegmentIndex < 1) {
damage = Math.min(damage, this.hp - 1);
}
case BattleSpec.FINAL_BOSS:
if (!this.formIndex && this.bossSegmentIndex < 1) {
damage = Math.min(damage, this.hp - 1);
}
}
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) {
case TrainerVariant.FEMALE:
if (!this.config.hasGenders) {
variant = TrainerVariant.DEFAULT;
}
break;
case TrainerVariant.DOUBLE:
if (!this.config.hasDouble) {
variant = TrainerVariant.DEFAULT;
}
break;
case TrainerVariant.FEMALE:
if (!this.config.hasGenders) {
variant = TrainerVariant.DEFAULT;
}
break;
case TrainerVariant.DOUBLE:
if (!this.config.hasDouble) {
variant = TrainerVariant.DEFAULT;
}
break;
}
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);
switch (strength) {
case PartyMemberStrength.WEAKER:
multiplier = 0.95;
break;
case PartyMemberStrength.WEAK:
multiplier = 1.0;
break;
case PartyMemberStrength.AVERAGE:
multiplier = 1.1;
break;
case PartyMemberStrength.STRONG:
multiplier = 1.2;
break;
case PartyMemberStrength.STRONGER:
multiplier = 1.25;
break;
case PartyMemberStrength.WEAKER:
multiplier = 0.95;
break;
case PartyMemberStrength.WEAK:
multiplier = 1.0;
break;
case PartyMemberStrength.AVERAGE:
multiplier = 1.1;
break;
case PartyMemberStrength.STRONG:
multiplier = 1.2;
break;
case PartyMemberStrength.STRONGER:
multiplier = 1.25;
break;
}
let levelOffset = 0;
@ -515,19 +515,19 @@ export default class Trainer extends Phaser.GameObjects.Container {
getPartyMemberModifierChanceMultiplier(index: integer): number {
switch (this.getPartyTemplate().getStrength(index)) {
case PartyMemberStrength.WEAKER:
return 0.75;
case PartyMemberStrength.WEAK:
return 0.675;
case PartyMemberStrength.AVERAGE:
return 0.5625;
case PartyMemberStrength.STRONG:
return 0.45;
case PartyMemberStrength.STRONGER:
return 0.375;
default:
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
return 0;
case PartyMemberStrength.WEAKER:
return 0.75;
case PartyMemberStrength.WEAK:
return 0.675;
case PartyMemberStrength.AVERAGE:
return 0.5625;
case PartyMemberStrength.STRONG:
return 0.45;
case PartyMemberStrength.STRONGER:
return 0.375;
default:
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
return 0;
}
}

View File

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

View File

@ -477,18 +477,18 @@ export class LoadingScene extends SceneBase {
this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => {
assetText.setText(i18next.t("menu:loadingAsset", { assetName: key }));
switch (key) {
case "loading_bg":
bg.setTexture("loading_bg");
if (mobile) {
bg.setVisible(true);
}
break;
case "logo":
logo.setTexture("logo");
if (mobile) {
logo.setVisible(true);
}
break;
case "loading_bg":
bg.setTexture("loading_bg");
if (mobile) {
bg.setVisible(true);
}
break;
case "logo":
logo.setTexture("logo");
if (mobile) {
logo.setVisible(true);
}
break;
}
});

View File

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

View File

@ -78,18 +78,18 @@ export class ModifierType {
}
let poolTypes: ModifierPoolType[];
switch (poolType) {
case ModifierPoolType.PLAYER:
poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ];
break;
case ModifierPoolType.WILD:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ];
break;
case ModifierPoolType.TRAINER:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ];
break;
default:
poolTypes = [ poolType ];
break;
case ModifierPoolType.PLAYER:
poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ];
break;
case ModifierPoolType.WILD:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ];
break;
case ModifierPoolType.TRAINER:
poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ];
break;
default:
poolTypes = [ poolType ];
break;
}
// Try multiple pool types in case of stolen items
for (const type of poolTypes) {
@ -502,42 +502,42 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge
function getAttackTypeBoosterItemName(type: Type) {
switch (type) {
case Type.NORMAL:
return "Silk Scarf";
case Type.FIGHTING:
return "Black Belt";
case Type.FLYING:
return "Sharp Beak";
case Type.POISON:
return "Poison Barb";
case Type.GROUND:
return "Soft Sand";
case Type.ROCK:
return "Hard Stone";
case Type.BUG:
return "Silver Powder";
case Type.GHOST:
return "Spell Tag";
case Type.STEEL:
return "Metal Coat";
case Type.FIRE:
return "Charcoal";
case Type.WATER:
return "Mystic Water";
case Type.GRASS:
return "Miracle Seed";
case Type.ELECTRIC:
return "Magnet";
case Type.PSYCHIC:
return "Twisted Spoon";
case Type.ICE:
return "Never-Melt Ice";
case Type.DRAGON:
return "Dragon Fang";
case Type.DARK:
return "Black Glasses";
case Type.FAIRY:
return "Fairy Feather";
case Type.NORMAL:
return "Silk Scarf";
case Type.FIGHTING:
return "Black Belt";
case Type.FLYING:
return "Sharp Beak";
case Type.POISON:
return "Poison Barb";
case Type.GROUND:
return "Soft Sand";
case Type.ROCK:
return "Hard Stone";
case Type.BUG:
return "Silver Powder";
case Type.GHOST:
return "Spell Tag";
case Type.STEEL:
return "Metal Coat";
case Type.FIRE:
return "Charcoal";
case Type.WATER:
return "Mystic Water";
case Type.GRASS:
return "Miracle Seed";
case Type.ELECTRIC:
return "Magnet";
case Type.PSYCHIC:
return "Twisted Spoon";
case Type.ICE:
return "Never-Melt Ice";
case Type.DRAGON:
return "Dragon Fang";
case Type.DARK:
return "Black Glasses";
case Type.FAIRY:
return "Fairy Feather";
}
}
@ -1126,7 +1126,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
}
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
constructor(rare: boolean) {
constructor(isRareFormChangeItem: boolean) {
super((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) {
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
@ -1149,15 +1149,15 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
foundN_SOLAR = false;
formChangeItemTriggers.forEach((fc, i) => {
switch (fc.item) {
case FormChangeItem.ULTRANECROZIUM_Z:
foundULTRA_Z = true;
break;
case FormChangeItem.N_LUNARIZER:
foundN_LUNA = true;
break;
case FormChangeItem.N_SOLARIZER:
foundN_SOLAR = true;
break;
case FormChangeItem.ULTRANECROZIUM_Z:
foundULTRA_Z = true;
break;
case FormChangeItem.N_LUNARIZER:
foundN_LUNA = true;
break;
case FormChangeItem.N_SOLARIZER:
foundN_SOLAR = true;
break;
}
});
if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) {
@ -1167,7 +1167,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
}
return formChangeItemTriggers;
}).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.
if (!formChangeItemPool.length) {
@ -1975,21 +1975,21 @@ let enemyBuffIgnoredPoolIndexes = {}; // eslint-disable-line @typescript-eslint/
export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool {
let pool: ModifierPool;
switch (poolType) {
case ModifierPoolType.PLAYER:
pool = modifierPool;
break;
case ModifierPoolType.WILD:
pool = wildModifierPool;
break;
case ModifierPoolType.TRAINER:
pool = trainerModifierPool;
break;
case ModifierPoolType.ENEMY_BUFF:
pool = enemyBuffModifierPool;
break;
case ModifierPoolType.DAILY_STARTER:
pool = dailyStarterModifierPool;
break;
case ModifierPoolType.PLAYER:
pool = modifierPool;
break;
case ModifierPoolType.WILD:
pool = wildModifierPool;
break;
case ModifierPoolType.TRAINER:
pool = trainerModifierPool;
break;
case ModifierPoolType.ENEMY_BUFF:
pool = enemyBuffModifierPool;
break;
case ModifierPoolType.DAILY_STARTER:
pool = dailyStarterModifierPool;
break;
}
return pool;
}
@ -2060,23 +2060,23 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod
console.table(modifierTableData);
}
switch (poolType) {
case ModifierPoolType.PLAYER:
modifierPoolThresholds = thresholds;
ignoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.WILD:
case ModifierPoolType.TRAINER:
enemyModifierPoolThresholds = thresholds;
enemyIgnoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.ENEMY_BUFF:
enemyBuffModifierPoolThresholds = thresholds;
enemyBuffIgnoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.DAILY_STARTER:
dailyStarterModifierPoolThresholds = thresholds;
ignoredDailyStarterPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.PLAYER:
modifierPoolThresholds = thresholds;
ignoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.WILD:
case ModifierPoolType.TRAINER:
enemyModifierPoolThresholds = thresholds;
enemyIgnoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.ENEMY_BUFF:
enemyBuffModifierPoolThresholds = thresholds;
enemyBuffIgnoredPoolIndexes = ignoredIndexes;
break;
case ModifierPoolType.DAILY_STARTER:
dailyStarterModifierPoolThresholds = thresholds;
ignoredDailyStarterPoolIndexes = ignoredIndexes;
break;
}
}
@ -2227,7 +2227,8 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
],
[
new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8),
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75)
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75),
new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4)
],
[
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
@ -2246,15 +2247,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier {
let tierStackCount: number;
switch (tier) {
case ModifierTier.ULTRA:
tierStackCount = 5;
break;
case ModifierTier.GREAT:
tierStackCount = 3;
break;
default:
tierStackCount = 1;
break;
case ModifierTier.ULTRA:
tierStackCount = 5;
break;
case ModifierTier.GREAT:
tierStackCount = 3;
break;
default:
tierStackCount = 1;
break;
}
const retryCount = 50;
@ -2320,21 +2321,21 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
const pool = getModifierPoolForType(poolType);
let thresholds: object;
switch (poolType) {
case ModifierPoolType.PLAYER:
thresholds = modifierPoolThresholds;
break;
case ModifierPoolType.WILD:
thresholds = enemyModifierPoolThresholds;
break;
case ModifierPoolType.TRAINER:
thresholds = enemyModifierPoolThresholds;
break;
case ModifierPoolType.ENEMY_BUFF:
thresholds = enemyBuffModifierPoolThresholds;
break;
case ModifierPoolType.DAILY_STARTER:
thresholds = dailyStarterModifierPoolThresholds;
break;
case ModifierPoolType.PLAYER:
thresholds = modifierPoolThresholds;
break;
case ModifierPoolType.WILD:
thresholds = enemyModifierPoolThresholds;
break;
case ModifierPoolType.TRAINER:
thresholds = enemyModifierPoolThresholds;
break;
case ModifierPoolType.ENEMY_BUFF:
thresholds = enemyBuffModifierPoolThresholds;
break;
case ModifierPoolType.DAILY_STARTER:
thresholds = dailyStarterModifierPoolThresholds;
break;
}
if (tier === undefined) {
const tierValue = randSeedInt(1024);

View File

@ -11,7 +11,7 @@ import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides";
import { EvolutionPhase } from "#app/phases/evolution-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { achvs } from "#app/system/achv";
@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next";
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color";
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
export type ModifierPredicate = (modifier: Modifier) => boolean;
@ -1694,12 +1695,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
super(type, pokemonId, stackCount);
switch (type.id) {
case "TOXIC_ORB":
this.effect = StatusEffect.TOXIC;
break;
case "FLAME_ORB":
this.effect = StatusEffect.BURN;
break;
case "TOXIC_ORB":
this.effect = StatusEffect.TOXIC;
break;
case "FLAME_ORB":
this.effect = StatusEffect.BURN;
break;
}
}
@ -2180,7 +2181,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
* @returns
*/
override apply(playerPokemon: PlayerPokemon): boolean {
playerPokemon.natureOverride = this.nature;
playerPokemon.customPokemonData.nature = this.nature;
let speciesId = playerPokemon.species.speciesId;
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
playerPokemon.levelExp = 0;
}
playerPokemon.addFriendship(5);
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level));
@ -2235,7 +2236,7 @@ export class TmModifier extends ConsumablePokemonModifier {
*/
override apply(playerPokemon: PlayerPokemon): boolean {
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true));
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM));
return true;
}
@ -2255,8 +2256,9 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
* @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move
* @returns always `true`
*/
override apply(playerPokemon: PlayerPokemon): boolean {
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex]));
override apply(playerPokemon: PlayerPokemon, cost?: number): boolean {
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost));
return true;
}
@ -2682,15 +2684,15 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
count.value *= (this.getStackCount() + 1);
switch (this.getStackCount()) {
case 1:
power.value *= 0.4;
break;
case 2:
power.value *= 0.25;
break;
case 3:
power.value *= 0.175;
break;
case 1:
power.value *= 0.4;
break;
case 2:
power.value *= 0.25;
break;
case 3:
power.value *= 0.175;
break;
}
return true;

View File

@ -113,8 +113,8 @@ class DefaultOverrides {
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly GENDER_OVERRIDE: Gender | null = null;
readonly MOVESET_OVERRIDE: Moves | Array<Moves> = [];
readonly SHINY_OVERRIDE: boolean = false;
readonly VARIANT_OVERRIDE: Variant = 0;
readonly SHINY_OVERRIDE: boolean | null = null;
readonly VARIANT_OVERRIDE: Variant | null = null;
// --------------------------
// OPPONENT / ENEMY OVERRIDES
@ -134,8 +134,8 @@ class DefaultOverrides {
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly OPP_GENDER_OVERRIDE: Gender | null = null;
readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = [];
readonly OPP_SHINY_OVERRIDE: boolean = false;
readonly OPP_VARIANT_OVERRIDE: Variant = 0;
readonly OPP_SHINY_OVERRIDE: boolean | null = null;
readonly OPP_VARIANT_OVERRIDE: Variant | null = null;
readonly OPP_IVS_OVERRIDE: number | number[] = [];
readonly OPP_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
/**

View File

@ -84,170 +84,170 @@ export class CommandPhase extends FieldPhase {
let success: boolean;
switch (command) {
case Command.FIGHT:
let useStruggle = false;
if (cursor === -1 ||
case Command.FIGHT:
let useStruggle = false;
if (cursor === -1 ||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
(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 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];
if (!moveId) {
turnCommand.targets = [ this.fieldIndex ];
}
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
}
if (moveTargets.targets.length <= 1 || moveTargets.multiple) {
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 moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
if (!moveId) {
turnCommand.targets = [ this.fieldIndex ];
}
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
}
if (moveTargets.targets.length <= 1 || moveTargets.multiple) {
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?
} else {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
}
this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true;
} else if (cursor < playerPokemon.getMoveset().length) {
const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct?
this.scene.ui.setMode(Mode.MESSAGE);
} else {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
}
this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true;
} else if (cursor < playerPokemon.getMoveset().length) {
const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct?
this.scene.ui.setMode(Mode.MESSAGE);
// Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage =
// Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage =
playerPokemon.isMoveRestricted(move.moveId, playerPokemon)
? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId)
: 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.clearText();
this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex);
}, null, true);
}
break;
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);
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.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex);
}, null, true);
}
break;
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);
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:noPokeballMulti"), null, () => {
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 (cursor < 5) {
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) {
} 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.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.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor };
} else if (cursor < 5) {
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;
if (this.fieldIndex) {
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
}
success = true;
}
}
}
}
break;
case Command.POKEMON:
case Command.RUN:
const isSwitch = command === Command.POKEMON;
const { currentBattle, arena } = this.scene;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => {
this.scene.ui.showText("", 0);
break;
case Command.POKEMON:
case Command.RUN:
const isSwitch = command === Command.POKEMON;
const { currentBattle, arena } = this.scene;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, 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);
} 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.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => {
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);
} else {
const trapTag = playerPokemon.getTag(TrappedTag);
// trapTag should be defined at this point, but just in case...
if (!trapTag) {
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 };
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,
() => {
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);
if (!isSwitch) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}
}, 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?

View File

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

View File

@ -33,7 +33,7 @@ export class EggLapsePhase extends Phase {
if (eggsToHatchCount > 0) {
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
// show prompt for skip
// show prompt for skip, blocking inputs for 1 second
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
this.hatchEggsSkipped(eggsToHatch);
@ -41,7 +41,8 @@ export class EggLapsePhase extends Phase {
}, () => {
this.hatchEggsRegular(eggsToHatch);
this.end();
}
},
null, null, null, 1000, true
);
}, 100, true);
} else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) {

View File

@ -1,7 +1,6 @@
import BattleScene from "#app/battle-scene";
import { Phase } from "#app/phase";
import { Mode } from "#app/ui/ui";
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
import { EggHatchData } from "#app/data/egg-hatch-data";
/**
@ -11,7 +10,6 @@ import { EggHatchData } from "#app/data/egg-hatch-data";
*/
export class EggSummaryPhase extends Phase {
private eggHatchData: EggHatchData[];
private eggHatchHandler: EggHatchSceneHandler;
constructor(scene: BattleScene, eggHatchData: EggHatchData[]) {
super(scene);
@ -26,7 +24,6 @@ export class EggSummaryPhase extends Phase {
if (i >= this.eggHatchData.length) {
this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => {
this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
});
} else {

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 { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Biome } from "#enums/biome";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
export class EncounterPhase extends BattlePhase {
private loaded: boolean;
@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase {
this.scene.executeWithSeedOffset(() => {
const currentSessionEncounterType = battle.mysteryEncounterType;
battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType);
}, battle.waveIndex << 4);
}, battle.waveIndex * 16);
}
const mysteryEncounter = battle.mysteryEncounter;
if (mysteryEncounter) {
@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase {
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()) {
if (pokemon) {
pokemon.resetBattleData();
@ -494,31 +502,31 @@ export class EncounterPhase extends BattlePhase {
tryOverrideForBattleSpec(): boolean {
switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS:
const enemy = this.scene.getEnemyPokemon();
this.scene.ui.showText(this.getEncounterMessage(), null, () => {
const localizationKey = "battleSpecDialogue:encounter";
if (this.scene.ui.shouldSkipDialogue(localizationKey)) {
case BattleSpec.FINAL_BOSS:
const enemy = this.scene.getEnemyPokemon();
this.scene.ui.showText(this.getEncounterMessage(), null, () => {
const localizationKey = "battleSpecDialogue:encounter";
if (this.scene.ui.shouldSkipDialogue(localizationKey)) {
// Logging mirrors logging found in dialogue-ui-handler
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, () => {
console.log(`Dialogue ${localizationKey} skipped`);
this.doEncounterCommon(false);
});
}
}, 1500, true);
return true;
} 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);
});
}
}, 1500, true);
return true;
}
return false;
}

View File

@ -1,12 +1,12 @@
import BattleScene from "#app/battle-scene";
import { BattlerIndex, BattleType } from "#app/battle";
import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability";
import { BattlerTagLapseType } from "#app/data/battler-tags";
import { BattlerTagLapseType, DestinyBondTag } from "#app/data/battler-tags";
import { battleSpecDialogue } from "#app/data/dialogue";
import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move";
import { BattleSpec } from "#app/enums/battle-spec";
import { StatusEffect } from "#app/enums/status-effect";
import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon";
import Pokemon, { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
import i18next from "i18next";
@ -19,19 +19,40 @@ import { SwitchPhase } from "./switch-phase";
import { VictoryPhase } from "./victory-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { SwitchType } from "#enums/switch-type";
import { isNullOrUndefined } from "#app/utils";
import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
export class FaintPhase extends PokemonPhase {
/**
* Whether or not enduring (for this phase's purposes, Reviver Seed) should be prevented
*/
private preventEndure: boolean;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) {
/**
* Destiny Bond tag belonging to the currently fainting Pokemon, if applicable
*/
private destinyTag?: DestinyBondTag;
/**
* The source Pokemon that dealt fatal damage and should get KO'd by Destiny Bond, if applicable
*/
private source?: Pokemon;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag, source?: Pokemon) {
super(scene, battlerIndex);
this.preventEndure = preventEndure!; // TODO: is this bang correct?
this.preventEndure = preventEndure;
this.destinyTag = destinyTag;
this.source = source;
}
start() {
super.start();
if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) {
this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM);
}
if (!this.preventEndure) {
const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier;
@ -127,7 +148,7 @@ export class FaintPhase extends PokemonPhase {
pokemon.faintCry(() => {
if (pokemon instanceof PlayerPokemon) {
pokemon.addFriendship(-10);
pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT);
}
pokemon.hideInfo();
this.scene.playSound("se/faint");
@ -158,19 +179,19 @@ export class FaintPhase extends PokemonPhase {
tryOverrideForBattleSpec(): boolean {
switch (this.scene.currentBattle.battleSpec) {
case BattleSpec.FINAL_BOSS:
if (!this.player) {
const enemy = this.getPokemon();
if (enemy.formIndex) {
this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint());
} else {
case BattleSpec.FINAL_BOSS:
if (!this.player) {
const enemy = this.getPokemon();
if (enemy.formIndex) {
this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint());
} else {
// Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase
enemy.hp++;
this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER));
this.end();
enemy.hp++;
this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER));
this.end();
}
return true;
}
return true;
}
}
return false;

View File

@ -2,24 +2,37 @@ import BattleScene from "#app/battle-scene";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import Move, { allMoves } from "#app/data/move";
import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms";
import { Moves } from "#app/enums/moves";
import { Moves } from "#enums/moves";
import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides";
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import Pokemon from "#app/field/pokemon";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */
LEARN_MOVE,
/** For learning a move via Memory Mushroom */
MEMORY,
/** For learning a move via TM */
TM
}
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
private moveId: Moves;
private messageMode: Mode;
private fromTM: boolean;
private learnMoveType;
private cost: number;
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) {
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) {
super(scene, partyMemberIndex);
this.moveId = moveId;
this.fromTM = fromTM ?? false;
this.learnMoveType = learnMoveType;
this.cost = cost;
}
start() {
@ -136,22 +149,37 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
* @param Pokemon The Pokemon learning the move
*/
async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) {
if (this.fromTM) {
if (this.learnMoveType === LearnMoveType.TM) {
if (!pokemon.usedTMs) {
pokemon.usedTMs = [];
}
pokemon.usedTMs.push(this.moveId);
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
} else if (this.learnMoveType === LearnMoveType.MEMORY) {
if (this.cost !== -1) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
this.scene.money -= this.cost;
this.scene.updateMoneyText();
this.scene.animateMoneyChanged(false);
}
this.scene.playSound("se/buy");
} else {
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
}
}
pokemon.setMove(index, this.moveId);
initMoveAnim(this.scene, this.moveId).then(() => {
loadMoveAnimAssets(this.scene, [ this.moveId ], true);
this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is
});
this.scene.ui.setMode(this.messageMode);
const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name });
textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText;
await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true);
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true);
this.end();
if (textMessage) {
await this.scene.ui.showTextPromise(textMessage);
}
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

@ -99,8 +99,9 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const hasActiveTargets = targets.some(t => t.isActive(true));
/** Check if the target is immune via ability to the attacking move */
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !targets[0].getTag(SemiInvulnerableTag);
/**
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target
@ -140,7 +141,7 @@ export class MoveEffectPhase extends PokemonPhase {
const bypassIgnoreProtect = new Utils.BooleanHolder(false);
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
if (!this.move.getMove().isAllyTarget()) {
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
}
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
@ -148,8 +149,9 @@ export class MoveEffectPhase extends PokemonPhase {
&& (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)))
|| (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
/** Is the pokemon immune due to an ablility? */
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !target.getTag(SemiInvulnerableTag);
/**
* If the move missed a target, stop all future hits against that target
@ -278,10 +280,8 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
}
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING);
if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) {
target.lapseTag(BattlerTagType.SHELL_TRAP);
}
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
})).then(() => {
// Apply the user's post-attack ability effects
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {

View File

@ -1,6 +1,6 @@
import { BattlerIndex } from "#app/battle";
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 { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
@ -128,7 +128,9 @@ export class MovePhase extends BattlePhase {
this.lapsePreMoveAndMoveTags();
this.resolveFinalPreMoveCancellationChecks();
if (!(this.failed || this.cancelled)) {
this.resolveFinalPreMoveCancellationChecks();
}
if (this.cancelled || this.failed) {
this.handlePreMoveFailures();
@ -145,8 +147,9 @@ export class MovePhase extends BattlePhase {
const moveQueue = this.pokemon.getMoveQueue();
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
this.showMoveText();
this.showFailedText();
this.cancelled = true;
this.cancel();
}
}
@ -164,23 +167,26 @@ export class MovePhase extends BattlePhase {
let healed = false;
switch (this.pokemon.status.effect) {
case StatusEffect.PARALYSIS:
if (!this.pokemon.randSeedInt(4)) {
activated = true;
this.cancelled = true;
}
break;
case StatusEffect.SLEEP:
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
this.cancelled = activated;
break;
case StatusEffect.FREEZE:
healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5);
activated = !healed;
this.cancelled = activated;
break;
case StatusEffect.PARALYSIS:
if (!this.pokemon.randSeedInt(4)) {
activated = true;
this.cancelled = true;
}
break;
case StatusEffect.SLEEP:
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this.pokemon, null, false, this.pokemon.status.effect, turnsRemaining);
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
this.cancelled = activated;
break;
case StatusEffect.FREEZE:
healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5);
activated = !healed;
this.cancelled = activated;
break;
}
if (activated) {

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()) {
scene.pushPhase(new SummonPhase(scene, 0));

View File

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

View File

@ -1,13 +0,0 @@
import BattleScene from "#app/battle-scene";
import { Phase } from "#app/phase";
import { Mode } from "#app/ui/ui";
export class OutdatedPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
}
start(): void {
this.scene.ui.setMode(Mode.OUTDATED);
}
}

View File

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

View File

@ -18,9 +18,9 @@ export class PostSummonPhase extends PokemonPhase {
const pokemon = this.getPokemon();
if (pokemon.status?.effect === StatusEffect.TOXIC) {
pokemon.status.turnCount = 0;
pokemon.status.toxicTurnCount = 0;
}
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);
// If this is mystery encounter and has post summon phase tag, apply post summon effects
if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) {

View File

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

View File

@ -16,26 +16,32 @@ export class SelectModifierPhase extends BattlePhase {
private rerollCount: integer;
private modifierTiers?: ModifierTier[];
private customModifierSettings?: CustomModifierSettings;
private isCopy: boolean;
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) {
private typeOptions: ModifierTypeOption[];
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) {
super(scene);
this.rerollCount = rerollCount;
this.modifierTiers = modifierTiers;
this.customModifierSettings = customModifierSettings;
this.isCopy = isCopy;
}
start() {
super.start();
if (!this.rerollCount) {
if (!this.rerollCount && !this.isCopy) {
this.updateSeed();
} else {
} else if (this.rerollCount) {
this.scene.reroll = false;
}
const party = this.scene.getParty();
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
if (!this.isCopy) {
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
}
const modifierCount = new Utils.IntegerHolder(3);
if (this.isPlayer()) {
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount);
@ -54,7 +60,7 @@ export class SelectModifierPhase extends BattlePhase {
}
}
const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value);
this.typeOptions = this.getModifierTypeOptions(modifierCount.value);
const modifierSelectCallback = (rowCursor: integer, cursor: integer) => {
if (rowCursor < 0 || cursor < 0) {
@ -63,86 +69,86 @@ export class SelectModifierPhase extends BattlePhase {
this.scene.ui.revertMode();
this.scene.ui.setMode(Mode.MESSAGE);
super.end();
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)));
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)));
});
return false;
}
let modifierType: ModifierType;
let cost: integer;
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
const rerollCost = this.getRerollCost(this.scene.lockModifierTiers);
switch (rowCursor) {
case 0:
switch (cursor) {
case 0:
if (rerollCost < 0 || this.scene.money < rerollCost) {
this.scene.ui.playError();
return false;
} else {
this.scene.reroll = true;
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
this.scene.money -= rerollCost;
this.scene.updateMoneyText();
this.scene.animateMoneyChanged(false);
}
this.scene.playSound("se/buy");
}
break;
case 1:
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
switch (cursor) {
case 0:
if (rerollCost < 0 || this.scene.money < rerollCost) {
this.scene.ui.playError();
return false;
} else {
this.scene.reroll = true;
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
this.scene.money -= rerollCost;
this.scene.updateMoneyText();
this.scene.animateMoneyChanged(false);
}
this.scene.playSound("se/buy");
}
break;
case 1:
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[];
const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
}
}, PartyUiHandler.FilterItemMaxStacks);
break;
case 2:
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
});
break;
case 3:
if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled
this.scene.ui.playError();
return false;
const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
}
}, PartyUiHandler.FilterItemMaxStacks);
break;
case 2:
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));
});
break;
case 3:
if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled
this.scene.ui.playError();
return false;
}
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(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(typeOptions, this.scene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false;
}
return true;
case 1:
if (typeOptions.length === 0) {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
super.end();
return true;
}
if (typeOptions[cursor].type) {
modifierType = typeOptions[cursor].type;
}
break;
default:
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 (shopOption.type) {
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;
case 1:
if (this.typeOptions.length === 0) {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
super.end();
return true;
}
if (this.typeOptions[cursor].type) {
modifierType = this.typeOptions[cursor].type;
}
break;
default:
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 (shopOption.type) {
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?
@ -151,8 +157,16 @@ export class SelectModifierPhase extends BattlePhase {
}
const applyModifier = (modifier: Modifier, playSound: boolean = false) => {
const result = this.scene.addModifier(modifier, false, playSound);
if (cost) {
const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost);
// Queue a copy of this phase when applying a TM or Memory Mushroom.
// If the player selects either of these, then escapes out of consuming them,
// they are returned to a shop in the same state.
if (modifier.type instanceof RememberMoveModifierType ||
modifier.type instanceof TmModifierType) {
this.scene.unshiftPhase(this.copy());
}
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
result.then(success => {
if (success) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
@ -189,7 +203,7 @@ export class SelectModifierPhase extends BattlePhase {
applyModifier(modifier, true);
});
} else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
}
}, modifierType.selectFilter);
} else {
@ -216,7 +230,7 @@ export class SelectModifierPhase extends BattlePhase {
applyModifier(modifier!, true); // TODO: is the bang correct?
});
} else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
}
}, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier);
}
@ -226,7 +240,7 @@ export class SelectModifierPhase extends BattlePhase {
return !cost!;// TODO: is the bang correct?
};
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
}
updateSeed(): void {
@ -237,13 +251,13 @@ export class SelectModifierPhase extends BattlePhase {
return true;
}
getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): number {
getRerollCost(lockRarities: boolean): number {
let baseValue = 0;
if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
return baseValue;
} else if (lockRarities) {
const tierValues = [ 50, 125, 300, 750, 2000 ];
for (const opt of typeOptions) {
for (const opt of this.typeOptions) {
baseValue += tierValues[opt.type.tier ?? 0];
}
} else {
@ -271,6 +285,16 @@ export class SelectModifierPhase extends BattlePhase {
return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings);
}
copy(): SelectModifierPhase {
return new SelectModifierPhase(
this.scene,
this.rerollCount,
this.modifierTiers,
{ guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false },
true
);
}
addModifier(modifier: Modifier): Promise<boolean> {
return this.scene.addModifier(modifier, false, true);
}

View File

@ -64,8 +64,8 @@ export class StatStageChangePhase extends PokemonPhase {
const cancelled = new BooleanHolder(false);
if (!this.selfTarget && stages.value < 0) {
// TODO: Include simulate boolean when tag applications can be simulated
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, 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) {

View File

@ -45,7 +45,7 @@ export class TurnStartPhase extends FieldPhase {
// Next, a check for Trick Room is applied to determine sort order.
const speedReversed = new Utils.BooleanHolder(false);
this.scene.arena.applyTags(TrickRoomTag, speedReversed);
this.scene.arena.applyTags(TrickRoomTag, false, speedReversed);
// Adjust the sort function based on whether Trick Room is active.
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
@ -152,55 +152,55 @@ export class TurnStartPhase extends FieldPhase {
}
switch (turnCommand?.command) {
case Command.FIGHT:
const queuedMove = turnCommand.move;
pokemon.turnData.order = orderIndex++;
if (!queuedMove) {
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);
case Command.FIGHT:
const queuedMove = turnCommand.move;
pokemon.turnData.order = orderIndex++;
if (!queuedMove) {
continue;
}
} else {
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here?
}
break;
case Command.BALL:
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();
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 {
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);
}
});
// 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;
} else {
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here?
}
}
this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex()));
break;
break;
case Command.BALL:
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

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

View File

@ -43,10 +43,9 @@ import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
import { WeatherType } from "#enums/weather-type";
import { TerrainType } from "#app/data/terrain";
import { OutdatedPhase } from "#app/phases/outdated-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter";
import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter";
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
@ -68,22 +67,22 @@ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet n
export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string {
switch (dataType) {
case GameDataType.SYSTEM:
return "data";
case GameDataType.SESSION:
let ret = "sessionData";
if (slotId) {
ret += slotId;
}
return ret;
case GameDataType.SETTINGS:
return "settings";
case GameDataType.TUTORIALS:
return "tutorials";
case GameDataType.SEEN_DIALOGUES:
return "seenDialogues";
case GameDataType.RUN_HISTORY:
return "runHistoryData";
case GameDataType.SYSTEM:
return "data";
case GameDataType.SESSION:
let ret = "sessionData";
if (slotId) {
ret += slotId;
}
return ret;
case GameDataType.SETTINGS:
return "settings";
case GameDataType.TUTORIALS:
return "tutorials";
case GameDataType.SEEN_DIALOGUES:
return "seenDialogues";
case GameDataType.RUN_HISTORY:
return "runHistoryData";
}
}
@ -403,10 +402,7 @@ export class GameData {
.then(error => {
this.scene.ui.savingIcon.hide();
if (error) {
if (error.startsWith("client version out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
@ -482,7 +478,7 @@ export class GameData {
localStorage.setItem(lsItemKey, "");
}
applySystemDataPatches(systemData);
applySystemVersionMigration(systemData);
this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId;
@ -857,7 +853,7 @@ export class GameData {
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
applySettingsDataPatches(settings);
applySettingsVersionMigration(settings);
for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]);
@ -1313,7 +1309,7 @@ export class GameData {
return v;
}) as SessionSaveData;
applySessionDataPatches(sessionData);
applySessionVersionMigration(sessionData);
return sessionData;
}
@ -1354,10 +1350,7 @@ export class GameData {
this.scene.ui.savingIcon.hide();
}
if (error) {
if (error.startsWith("client version out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
@ -1381,9 +1374,9 @@ export class GameData {
const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
const handleData = (dataStr: string) => {
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr, true);
break;
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr, true);
break;
}
const encryptedData = AES.encrypt(dataStr, saveKey);
const blob = new Blob([ encryptedData.toString() ], { type: "text/json" });
@ -1441,28 +1434,28 @@ export class GameData {
try {
dataName = GameDataType[dataType].toLowerCase();
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr);
valid = !!systemData.dexData && !!systemData.timestamp;
break;
case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr);
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
break;
case GameDataType.RUN_HISTORY:
const data = JSON.parse(dataStr);
const keys = Object.keys(data);
dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase();
keys.forEach((key) => {
const entryKeys = Object.keys(data[key]);
valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3;
});
break;
case GameDataType.SETTINGS:
case GameDataType.TUTORIALS:
valid = true;
break;
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr);
valid = !!systemData.dexData && !!systemData.timestamp;
break;
case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr);
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
break;
case GameDataType.RUN_HISTORY:
const data = JSON.parse(dataStr);
const keys = Object.keys(data);
dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase();
keys.forEach((key) => {
const entryKeys = Object.keys(data[key]);
valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3;
});
break;
case GameDataType.SETTINGS:
case GameDataType.TUTORIALS:
valid = true;
break;
}
} catch (ex) {
console.error(ex);

View File

@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags";
import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
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 {
public id: integer;
@ -33,7 +33,6 @@ export default class PokemonData {
public stats: integer[];
public ivs: integer[];
public nature: Nature;
public natureOverride: Nature | -1;
public moveset: (PokemonMove | null)[];
public status: Status | null;
public friendship: integer;
@ -54,14 +53,20 @@ export default class PokemonData {
public fusionVariant: Variant;
public fusionGender: Gender;
public fusionLuck: integer;
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData;
public boss: boolean;
public bossSegments?: integer;
public summonData: PokemonSummonData;
/** 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) {
const sourcePokemon = source instanceof Pokemon ? source : null;
@ -107,9 +112,13 @@ export default class PokemonData {
this.fusionVariant = source.fusionVariant;
this.fusionGender = source.fusionGender;
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
this.usedTMs = source.usedTMs ?? [];
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData);
this.customPokemonData = new CustomPokemonData(source.customPokemonData);
this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData);
this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData);
if (!forHistory) {
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
@ -128,7 +137,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));
if (!forHistory) {
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;
}

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