Merge remote-tracking branch 'upstream/beta' into modifier-fixes

This commit is contained in:
Bertie690 2025-06-16 13:19:06 -04:00
commit b353890d77
214 changed files with 857 additions and 898 deletions

View File

@ -1,6 +1,7 @@
VITE_BYPASS_LOGIN=1 VITE_BYPASS_LOGIN=1
VITE_BYPASS_TUTORIAL=0 VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001 VITE_SERVER_URL=http://localhost:8001
# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN
VITE_DISCORD_CLIENT_ID=1234567890 VITE_DISCORD_CLIENT_ID=1234567890
VITE_GOOGLE_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890
VITE_I18N_DEBUG=0 VITE_I18N_DEBUG=0

View File

@ -48,7 +48,7 @@ async function promptTestType() {
{ {
type: "list", type: "list",
name: "selectedOption", name: "selectedOption",
message: "What type of test would you like to create:", message: "What type of test would you like to create?",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
}, },
]); ]);

View File

@ -24,7 +24,7 @@ describe("{{description}}", () => {
game.override game.override
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)

View File

@ -4,7 +4,8 @@ import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { import {
fixedInt, fixedInt,
getIvsFromId, getIvsFromId,
@ -2104,12 +2105,15 @@ export default class BattleScene extends SceneBase {
} }
getMaxExpLevel(ignoreLevelCap = false): number { getMaxExpLevel(ignoreLevelCap = false): number {
if (Overrides.LEVEL_CAP_OVERRIDE > 0) { const capOverride = Overrides.LEVEL_CAP_OVERRIDE ?? 0;
return Overrides.LEVEL_CAP_OVERRIDE; if (capOverride > 0) {
return capOverride;
} }
if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) {
if (ignoreLevelCap || capOverride < 0) {
return Number.MAX_SAFE_INTEGER; return Number.MAX_SAFE_INTEGER;
} }
const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10;
const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex);
const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2;
@ -2973,6 +2977,13 @@ export default class BattleScene extends SceneBase {
) { ) {
modifiers.splice(m--, 1); modifiers.splice(m--, 1);
} }
if (
modifier instanceof PokemonHeldItemModifier &&
!isNullOrUndefined(modifier.getSpecies()) &&
!this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!)
) {
modifiers.splice(m--, 1);
}
} }
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier instanceof PersistentModifier) { if (modifier instanceof PersistentModifier) {
@ -3502,17 +3513,13 @@ export default class BattleScene extends SceneBase {
sessionEncounterRate + sessionEncounterRate +
Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2); Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2);
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
? favoredEncounterRate
: Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME // MEs can only spawn 3 or more waves after the previous ME, barring overrides
const canSpawn = const canSpawn =
encounteredEvents.length === 0 || encounteredEvents.length === 0 || waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3;
waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3 ||
!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
if (canSpawn) { if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) {
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
// Always rolls the check on the same offset to ensure no RNG changes from reloading session // Always rolls the check on the same offset to ensure no RNG changes from reloading session
this.executeWithSeedOffset( this.executeWithSeedOffset(

View File

@ -24,11 +24,11 @@ import { allMoves } from "../data-lists";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { pokemonFormChanges } from "../pokemon-forms";
import { import {
SpeciesFormChangeRevertWeatherFormTrigger,
SpeciesFormChangeWeatherTrigger, SpeciesFormChangeWeatherTrigger,
SpeciesFormChangeAbilityTrigger,
} from "../pokemon-forms/form-change-triggers"; } from "../pokemon-forms/form-change-triggers";
import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers";
import i18next from "i18next"; import i18next from "i18next";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { BerryModifierType } from "#app/modifier/modifier-type"; import { BerryModifierType } from "#app/modifier/modifier-type";
@ -3971,27 +3971,32 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
this.ability = ability; this.ability = ability;
} }
/**
* Determine if the pokemon has a forme change that is triggered by the weather
*
* @param pokemon - The pokemon with the forme change ability
* @param _passive - unused
* @param _simulated - unused
* @param _args - unused
*/
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
const isCastformWithForecast = return !!pokemonFormChanges[pokemon.species.speciesId]?.some(
pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon),
const isCherrimWithFlowerGift = );
pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT;
return isCastformWithForecast || isCherrimWithFlowerGift;
} }
/** /**
* Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both * Trigger the pokemon's forme change by invoking
* {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and * {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange}
* {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it *
* is the specific Pokemon and ability * @param pokemon - The Pokemon with this ability
* @param {Pokemon} pokemon the Pokemon with this ability * @param _passive - unused
* @param _passive n/a * @param simulated - unused
* @param _args n/a * @param _args - unused
*/ */
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
if (!simulated) { if (!simulated) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger);
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger);
} }
} }
} }
@ -4617,7 +4622,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA
* @param stat The stat being affected * @param stat The stat being affected
* @param cancelled Holds whether the stat change was already prevented. * @param cancelled Holds whether the stat change was already prevented.
* @param args Args[0] is the target pokemon of the stat change. * @param args Args[0] is the target pokemon of the stat change.
* @returns * @returns `true` if the ability can be applied
*/ */
override canApplyPreStatStageChange( override canApplyPreStatStageChange(
_pokemon: Pokemon, _pokemon: Pokemon,
@ -4778,17 +4783,17 @@ export class BlockCritAbAttr extends AbAttr {
} }
/** /**
* Apply the block crit ability by setting the value in the provided boolean holder to false * Apply the block crit ability by setting the value in the provided boolean holder to `true`.
* @param args - [0] is a boolean holder representing whether the attack can crit * @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting.
*/ */
override apply( override apply(
_pokemon: Pokemon, _pokemon: Pokemon,
_passive: boolean, _passive: boolean,
_simulated: boolean, _simulated: boolean,
_cancelled: BooleanHolder, _cancelled: BooleanHolder,
args: [BooleanHolder, ...any], args: [BooleanHolder],
): void { ): void {
args[0].value = false; args[0].value = true;
} }
} }
@ -5301,10 +5306,11 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
/** /**
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
* @param {Pokemon} _pokemon the Pokemon with this ability * @param _pokemon - The Pokemon with this ability
* @param _passive n/a * @param _passive - unused
* @param _weather n/a * @param simulated - unused
* @param _args n/a * @param _weather - unused
* @param _args - unused
*/ */
override applyPostWeatherChange( override applyPostWeatherChange(
_pokemon: Pokemon, _pokemon: Pokemon,

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,8 @@ import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/constants"; import { defaultStarterSpecies } from "#app/constants";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "./moves/pokemon-move"; import { PokemonMove } from "./moves/pokemon-move";

View File

@ -5,7 +5,8 @@ import { PlayerPokemon } from "#app/field/pokemon";
import type { Starter } from "#app/ui/starter-select-ui-handler"; import type { Starter } from "#app/ui/starter-select-ui-handler";
import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common"; import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";

View File

@ -1,9 +1,11 @@
import type PokemonSpecies from "#app/data/pokemon-species";
import type { ModifierTypes } from "#app/modifier/modifier-type"; import type { ModifierTypes } from "#app/modifier/modifier-type";
import type { Ability } from "./abilities/ability"; import type { Ability } from "./abilities/ability";
import type Move from "./moves/move"; import type Move from "./moves/move";
export const allAbilities: Ability[] = []; export const allAbilities: Ability[] = [];
export const allMoves: Move[] = []; export const allMoves: Move[] = [];
export const allSpecies: PokemonSpecies[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment // TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes; export const modifierTypes = {} as ModifierTypes;

View File

@ -1,7 +1,7 @@
import type BattleScene from "#app/battle-scene"; import type BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { VariantTier } from "#enums/variant-tier"; import { VariantTier } from "#enums/variant-tier";
import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common"; import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common";

View File

@ -22,7 +22,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common"; import { randInt } from "#app/utils/common";

View File

@ -18,7 +18,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -22,7 +22,7 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { import {
applyAbilityOverrideToPokemon, applyAbilityOverrideToPokemon,

View File

@ -19,7 +19,7 @@ import {
getEncounterPokemonLevelForWave, getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/data/data-lists"; import { modifierTypes } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";

View File

@ -15,7 +15,7 @@ import {
updatePlayerMoney, updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";

View File

@ -20,7 +20,7 @@ import {
TypeRequirement, TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";

View File

@ -14,7 +14,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#enums/field-position"; import { FieldPosition } from "#enums/field-position";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -16,7 +16,8 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -1,4 +1,4 @@
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -14,7 +14,7 @@ import {
getHighestLevelPlayerPokemon, getHighestLevelPlayerPokemon,
koPlayerPokemon, koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#enums/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";

View File

@ -17,7 +17,7 @@ import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { NumberHolder, randSeedInt } from "#app/utils/common"; import { NumberHolder, randSeedInt } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { import {
doPlayerFlee, doPlayerFlee,

View File

@ -24,7 +24,7 @@ import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { AiType } from "#enums/ai-type"; import { AiType } from "#enums/ai-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";

View File

@ -15,7 +15,7 @@ import { BiomeId } from "#enums/biome-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";

View File

@ -15,7 +15,7 @@ import {
getSpriteKeysFromPokemon, getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";

View File

@ -13,7 +13,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -17,7 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";

View File

@ -22,7 +22,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#enums/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";

View File

@ -19,7 +19,8 @@ import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";

View File

@ -1,6 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { allAbilities } from "../data-lists"; import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers";
@ -16,7 +15,6 @@ import type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { SpeciesFormKey } from "#enums/species-form-key";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
export interface EncounterRequirement { export interface EncounterRequirement {
@ -834,70 +832,6 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
} }
} }
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
requiredEvolutionItem: EvolutionItem[];
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredEvolutionItem = coerceArray(evolutionItems);
}
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
return false;
}
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
filterByEvo(pokemon, evolutionItem) {
if (
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
pokemonEvolutions[pokemon.species.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
) {
return true;
}
return (
pokemon.isFusion() &&
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
);
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem));
if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
}
return ["evolutionItem", ""];
}
}
export class HeldItemRequirement extends EncounterPokemonRequirement { export class HeldItemRequirement extends EncounterPokemonRequirement {
requiredHeldItemModifiers: string[]; requiredHeldItemModifiers: string[];
minNumberOfPokemon: number; minNumberOfPokemon: number;

View File

@ -48,7 +48,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";

View File

@ -20,7 +20,7 @@ import { PartyUiMode } from "#app/ui/party-ui-handler";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { import {
getEncounterText, getEncounterText,

View File

@ -42,6 +42,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives";
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
import { hasExpSprite } from "#app/sprites/sprite-utils"; import { hasExpSprite } from "#app/sprites/sprite-utils";
import { Gender } from "./gender"; import { Gender } from "./gender";
import { allSpecies } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
export enum Region { export enum Region {
NORMAL, NORMAL,
@ -82,24 +84,6 @@ export const normalForm: SpeciesId[] = [
SpeciesId.CALYREX, SpeciesId.CALYREX,
]; ];
/**
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
* @param species - The {@linkcode SpeciesId} to fetch.
* If an array of `SpeciesId`s is passed (such as for named trainer spawn pools),
* one will be selected at random.
* @returns The associated {@linkcode PokemonSpecies} object
*/
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
if (Array.isArray(species)) {
// TODO: this RNG roll should not be handled by this function
species = species[Math.floor(Math.random() * species.length)];
}
if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
}
return allSpecies[species - 1];
}
export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = const retSpecies: PokemonSpecies =
species >= 2000 species >= 2000
@ -1449,8 +1433,6 @@ export function getPokerusStarters(): PokemonSpecies[] {
return pokerusStarters; return pokerusStarters;
} }
export const allSpecies: PokemonSpecies[] = [];
// biome-ignore format: manually formatted // biome-ignore format: manually formatted
export function initSpecies() { export function initSpecies() {
allSpecies.push( allSpecies.push(

View File

@ -10,7 +10,7 @@ import {
randSeedIntRange, randSeedIntRange,
} from "#app/utils/common"; } from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
import { doubleBattleDialogue } from "../double-battle-dialogue"; import { doubleBattleDialogue } from "../double-battle-dialogue";
import { TrainerVariant } from "#enums/trainer-variant"; import { TrainerVariant } from "#enums/trainer-variant";

View File

@ -3,7 +3,7 @@ import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biom
import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes";
import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common"; import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { import {
getTerrainClearMessage, getTerrainClearMessage,
getTerrainStartMessage, getTerrainStartMessage,

View File

@ -15,12 +15,8 @@ import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import { import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpeciesForm } from "#app/data/pokemon-species";
default as PokemonSpecies, import { getPokemonSpecies } from "#app/utils/pokemon-utils";
getFusedSpeciesName,
getPokemonSpecies,
getPokemonSpeciesForm,
} from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { import {
NumberHolder, NumberHolder,
@ -79,11 +75,12 @@ import {
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Status, getRandomStatus } from "#app/data/status-effect"; import { Status, getRandomStatus } from "#app/data/status-effect";
import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { import {
pokemonEvolutions, pokemonEvolutions,
pokemonPrevolutions, pokemonPrevolutions,
FusionSpeciesFormEvolution, FusionSpeciesFormEvolution,
validateShedinjaEvo,
} from "#app/data/balance/pokemon-evolutions"; } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { import {
@ -370,7 +367,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0); this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0);
this.pauseEvolutions = dataSource.pauseEvolutions; this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus; this.pokerus = !!dataSource.pokerus;
this.evoCounter = dataSource.evoCounter ?? 0;
this.fusionSpecies = this.fusionSpecies =
dataSource.fusionSpecies instanceof PokemonSpecies dataSource.fusionSpecies instanceof PokemonSpecies
? dataSource.fusionSpecies ? dataSource.fusionSpecies
@ -1356,8 +1352,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Calculate the critical-hit stage of a move used against this pokemon by * Calculate the critical-hit stage of a move used **against** this pokemon by
* the given source * the given source.
*
* @param source - The {@linkcode Pokemon} using the move * @param source - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @returns The final critical-hit stage value * @returns The final critical-hit stage value
@ -1370,11 +1367,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage); applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) { // Dragon cheer only gives +1 crit stage to non-dragon types
critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; critStage.value +=
} else { critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2;
critStage.value += 2;
}
} }
console.log(`crit stage: +${critStage.value}`); console.log(`crit stage: +${critStage.value}`);
@ -2519,34 +2514,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId]; const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) { for (const e of evolutions) {
if ( if (e.validate(this)) {
!e.item &&
this.level >= e.level &&
(isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey)
) {
if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) {
return e; return e;
} }
} }
} }
}
if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) { if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) {
const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map( const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(
e => new FusionSpeciesFormEvolution(this.species.speciesId, e), e => new FusionSpeciesFormEvolution(this.species.speciesId, e),
); );
for (const fe of fusionEvolutions) { for (const fe of fusionEvolutions) {
if ( if (fe.validate(this)) {
!fe.item &&
this.level >= fe.level &&
(isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey)
) {
if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) {
return fe; return fe;
} }
} }
} }
}
return null; return null;
} }
@ -2785,17 +2768,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
if (!this.shiny) { if (!this.shiny) {
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) { if (applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride;
}
if (timedEventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
shinyThreshold.value *= timedEventManager.getShinyMultiplier(); shinyThreshold.value *= timedEventManager.getShinyMultiplier();
} }
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
} else {
shinyThreshold.value = thresholdOverride;
} }
this.shiny = randSeedInt(65536) < shinyThreshold.value; this.shiny = randSeedInt(65536) < shinyThreshold.value;
@ -2864,16 +2842,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.species.abilityHidden) { if (!this.species.abilityHidden) {
return false; return false;
} }
const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) { if (applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
haThreshold.value = thresholdOverride;
}
if (!this.hasTrainer()) { if (!this.hasTrainer()) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
} }
} else {
haThreshold.value = thresholdOverride;
} }
if (randSeedInt(65536) < haThreshold.value) { if (randSeedInt(65536) < haThreshold.value) {
@ -3888,33 +3861,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
} }
/** Calculate whether the given move critically hits this pokemon /**
* Determine whether the given move will score a critical hit **against** this Pokemon.
* @param source - The {@linkcode Pokemon} using the move * @param source - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`) * @returns Whether the move will critically hit the defender.
* @returns whether the move critically hits the pokemon
*/ */
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { getCriticalHitResult(source: Pokemon, move: Move): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (move.hasAttr("FixedDamageAttr")) {
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); // fixed damage moves (Dragon Rage, etc.) will nevet crit
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
return false; return false;
} }
const isCritical = new BooleanHolder(false);
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { const alwaysCrit = new BooleanHolder(false);
isCritical.value = true; applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
} applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move);
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move); const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
}
applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical); let isCritical = alwaysCrit.value || alwaysCritTag || critChance === 1;
return isCritical.value; // If we aren't already guaranteed to crit, do a random roll & check overrides
isCritical ||= Overrides.CRITICAL_HIT_OVERRIDE ?? globalScene.randBattleSeedInt(critChance) === 0;
// apply crit block effects from lucky chant & co., overriding previous effects
const blockCrit = new BooleanHolder(false);
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit);
const blockCritTag = globalScene.arena.getTagOnSide(
NoCritTag,
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
);
isCritical &&= !blockCritTag && !blockCrit.value; // need to roll a crit and not be blocked by either crit prevention effect
return isCritical;
} }
/** /**
@ -5492,6 +5471,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.turnData.berriesEaten.push(berryType); this.turnData.berriesEaten.push(berryType);
} }
getPersistentTreasureCount(): number {
return (
this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length +
globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length
);
}
} }
export class PlayerPokemon extends Pokemon { export class PlayerPokemon extends Pokemon {
@ -5830,7 +5816,7 @@ export class PlayerPokemon extends Pokemon {
if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) { if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) {
const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1];
if (newEvolution.condition?.predicate(this)) { if (validateShedinjaEvo()) {
const newPokemon = globalScene.addPlayerPokemon( const newPokemon = globalScene.addPlayerPokemon(
this.species, this.species,
this.level, this.level,
@ -5860,7 +5846,6 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionLuck = this.fusionLuck;
newPokemon.fusionTeraType = this.fusionTeraType; newPokemon.fusionTeraType = this.fusionTeraType;
newPokemon.usedTMs = this.usedTMs; newPokemon.usedTMs = this.usedTMs;
newPokemon.evoCounter = this.evoCounter;
globalScene.getPlayerParty().push(newPokemon); globalScene.getPlayerParty().push(newPokemon);
newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies);
@ -5949,7 +5934,6 @@ export class PlayerPokemon extends Pokemon {
this.fusionGender = pokemon.gender; this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck; this.fusionLuck = pokemon.luck;
this.fusionCustomPokemonData = pokemon.customPokemonData; this.fusionCustomPokemonData = pokemon.customPokemonData;
this.evoCounter = Math.max(pokemon.evoCounter, this.evoCounter);
if (pokemon.pauseEvolutions || this.pauseEvolutions) { if (pokemon.pauseEvolutions || this.pauseEvolutions) {
this.pauseEvolutions = true; this.pauseEvolutions = true;
} }
@ -6105,18 +6089,6 @@ export class EnemyPokemon extends Pokemon {
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
let prevolution: SpeciesId;
let speciesId = species.speciesId;
while ((prevolution = pokemonPrevolutions[speciesId])) {
const evolution = pokemonEvolutions[prevolution].find(
pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()),
);
if (evolution?.condition?.enforceFunc) {
evolution.condition.enforceFunc(this);
}
speciesId = prevolution;
}
if (this.hasTrainer() && globalScene.currentBattle) { if (this.hasTrainer() && globalScene.currentBattle) {
const { waveIndex } = globalScene.currentBattle; const { waveIndex } = globalScene.currentBattle;
const ivs: number[] = []; const ivs: number[] = [];

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import type { TrainerConfig } from "#app/data/trainers/trainer-config"; import type { TrainerConfig } from "#app/data/trainers/trainer-config";
import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";

View File

@ -5,7 +5,7 @@ import type { Challenge } from "./data/challenge";
import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge";
import { ChallengeType } from "#enums/challenge-type"; import { ChallengeType } from "#enums/challenge-type";
import type PokemonSpecies from "./data/pokemon-species"; import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species"; import { allSpecies } from "#app/data/data-lists";
import type { Arena } from "./field/arena"; import type { Arena } from "./field/arena";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common";
@ -90,13 +90,14 @@ export class GameMode implements GameModeConfig {
} }
/** /**
* Helper function to get starting level for game mode.
* @returns either: * @returns either:
* - override from overrides.ts * - starting level override from overrides.ts
* - 20 for Daily Runs * - 20 for Daily Runs
* - 5 for all other modes * - 5 for all other modes
*/ */
getStartingLevel(): number { getStartingLevel(): number {
if (Overrides.STARTING_LEVEL_OVERRIDE) { if (Overrides.STARTING_LEVEL_OVERRIDE > 0) {
return Overrides.STARTING_LEVEL_OVERRIDE; return Overrides.STARTING_LEVEL_OVERRIDE;
} }
switch (this.modeId) { switch (this.modeId) {

View File

@ -1218,12 +1218,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge
(pokemon: PlayerPokemon) => { (pokemon: PlayerPokemon) => {
if ( if (
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
pokemonEvolutions[pokemon.species.speciesId].filter( pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem))
e => .length &&
e.item === this.evolutionItem &&
(!e.condition || e.condition.predicate(pokemon)) &&
(e.preFormKey === null || e.preFormKey === pokemon.getFormKey()),
).length &&
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
) { ) {
return null; return null;
@ -1232,12 +1228,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge
pokemon.isFusion() && pokemon.isFusion() &&
pokemon.fusionSpecies && pokemon.fusionSpecies &&
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem))
e => .length &&
e.item === this.evolutionItem &&
(!e.condition || e.condition.predicate(pokemon)) &&
(e.preFormKey === null || e.preFormKey === pokemon.getFusionFormKey()),
).length &&
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
) { ) {
return null; return null;
@ -1597,12 +1589,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
) )
.flatMap(p => { .flatMap(p => {
const evolutions = pokemonEvolutions[p.species.speciesId]; const evolutions = pokemonEvolutions[p.species.speciesId];
return evolutions.filter( return evolutions.filter(e => e.isValidItemEvolution(p));
e =>
e.item !== EvolutionItem.NONE &&
(e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) &&
(!e.condition || e.condition.predicate(p)),
);
}), }),
party party
.filter( .filter(
@ -1616,16 +1603,11 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
) )
.flatMap(p => { .flatMap(p => {
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
return evolutions.filter( return evolutions.filter(e => e.validate(p, true));
e =>
e.item !== EvolutionItem.NONE &&
(e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) &&
(!e.condition || e.condition.predicate(p)),
);
}), }),
] ]
.flat() .flat()
.flatMap(e => e.item) .flatMap(e => e.evoItem)
.filter(i => (!!i && i > 50) === rare); .filter(i => (!!i && i > 50) === rare);
if (!evolutionItemPool.length) { if (!evolutionItemPool.length) {
@ -1892,7 +1874,8 @@ const modifierTypeInitObj = Object.freeze({
new PokemonHeldItemModifierType( new PokemonHeldItemModifierType(
"modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", "modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL",
"relic_gold", "relic_gold",
(type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10), (type, args) =>
new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10, (args[1] as number) ?? 1),
), ),
MEGA_BRACELET: () => MEGA_BRACELET: () =>

View File

@ -772,6 +772,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return this.getMaxHeldItemCount(pokemon); return this.getMaxHeldItemCount(pokemon);
} }
getSpecies(): SpeciesId | null {
return null;
}
abstract getMaxHeldItemCount(pokemon?: Pokemon): number; abstract getMaxHeldItemCount(pokemon?: Pokemon): number;
} }
@ -918,27 +922,14 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
return true; return true;
} }
getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null {
if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { const pokemon = this.getPokemon();
return null;
}
const pokemon = globalScene.getPokemonById(this.pokemonId); const count = (pokemon?.getPersistentTreasureCount() || 0) + this.getStackCount();
this.stackCount = pokemon const text = globalScene.add.bitmapText(10, 15, "item-count", count.toString(), 11);
? pokemon.evoCounter +
pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length +
globalScene.findModifiers(
m =>
m instanceof MoneyMultiplierModifier ||
m instanceof ExtraModifierModifier ||
m instanceof TempExtraModifierModifier,
).length
: this.stackCount;
const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11);
text.letterSpacing = -0.5; text.letterSpacing = -0.5;
if (this.getStackCount() >= this.required) { if (count >= this.required) {
text.setTint(0xf89890); text.setTint(0xf89890);
} }
text.setOrigin(0, 0); text.setOrigin(0, 0);
@ -946,18 +937,13 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
return text; return text;
} }
getMaxHeldItemCount(pokemon: Pokemon): number { getMaxHeldItemCount(_pokemon: Pokemon): number {
this.stackCount =
pokemon.evoCounter +
pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length +
globalScene.findModifiers(
m =>
m instanceof MoneyMultiplierModifier ||
m instanceof ExtraModifierModifier ||
m instanceof TempExtraModifierModifier,
).length;
return 999; return 999;
} }
override getSpecies(): SpeciesId {
return this.species;
}
} }
/** /**
@ -2402,19 +2388,13 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier {
override apply(playerPokemon: PlayerPokemon): boolean { override apply(playerPokemon: PlayerPokemon): boolean {
let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId) let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId)
? pokemonEvolutions[playerPokemon.species.speciesId].find( ? pokemonEvolutions[playerPokemon.species.speciesId].find(
e => e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, false, e.item!),
e.item === this.type.evolutionItem &&
(e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFormKey()) &&
(!e.condition || e.condition.predicate(playerPokemon)),
) )
: null; : null;
if (!matchingEvolution && playerPokemon.isFusion()) { if (!matchingEvolution && playerPokemon.isFusion()) {
matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find( matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find(
e => e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, true, e.item!),
e.item === this.type.evolutionItem && // TODO: is the bang correct?
(e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFusionFormKey()) &&
(!e.condition || e.condition.predicate(playerPokemon)),
); );
if (matchingEvolution) { if (matchingEvolution) {
matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution);
@ -2934,11 +2914,10 @@ export class MoneyRewardModifier extends ConsumableModifier {
globalScene.getPlayerParty().map(p => { globalScene.getPlayerParty().map(p => {
if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) {
p.evoCounter const factor = Math.min(Math.floor(this.moneyMultiplier), 3);
? (p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3))
: (p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3));
const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(
p, p,
factor,
) as EvoTrackerModifier; ) as EvoTrackerModifier;
globalScene.addModifier(modifier); globalScene.addModifier(modifier);
} }

View File

@ -80,7 +80,11 @@ class DefaultOverrides {
/** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps, /** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps,
or any negative number to set it to 9 quadrillion (effectively disabling it). */ or any negative number to set it to 9 quadrillion (effectively disabling it). */
readonly LEVEL_CAP_OVERRIDE: number = 0; readonly LEVEL_CAP_OVERRIDE: number = 0;
readonly NEVER_CRIT_OVERRIDE: boolean = false; /**
* If defined, overrides random critical hit rolls to always or never succeed.
* Ignored if the move is guaranteed to always/never crit.
*/
readonly CRITICAL_HIT_OVERRIDE: boolean | null = null;
/** default 1000 */ /** default 1000 */
readonly STARTING_MONEY_OVERRIDE: number = 0; readonly STARTING_MONEY_OVERRIDE: number = 0;
/** Sets all shop item prices to 0 */ /** Sets all shop item prices to 0 */

View File

@ -4,7 +4,7 @@ import { globalScene } from "#app/global-scene";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getCharVariantFromDialogue } from "#app/data/dialogue";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/data/data-lists"; import { modifierTypes } from "#app/data/data-lists";

View File

@ -821,7 +821,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @param effectiveness - The effectiveness of the move against the target * @param effectiveness - The effectiveness of the move against the target
*/ */
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult { protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult {
const isCritical = target.getCriticalHitResult(user, this.move, false); const isCritical = target.getCriticalHitResult(user, this.move);
/* /*
* Apply stat changes from {@linkcode move} and gives it to {@linkcode source} * Apply stat changes from {@linkcode move} and gives it to {@linkcode source}

View File

@ -3,7 +3,7 @@ import { applyChallenges } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type"; import { ChallengeType } from "#enums/challenge-type";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";

View File

@ -6,7 +6,8 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common"; import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";

View File

@ -3,7 +3,8 @@ import { globalScene } from "#app/global-scene";
import type { Gender } from "../data/gender"; import type { Gender } from "../data/gender";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { Status } from "../data/status-effect"; import { Status } from "../data/status-effect";
import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon"; import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
@ -45,7 +46,6 @@ export default class PokemonData {
public pauseEvolutions: boolean; public pauseEvolutions: boolean;
public pokerus: boolean; public pokerus: boolean;
public usedTMs: MoveId[]; public usedTMs: MoveId[];
public evoCounter: number;
public teraType: PokemonType; public teraType: PokemonType;
public isTerastallized: boolean; public isTerastallized: boolean;
public stellarTypesBoosted: PokemonType[]; public stellarTypesBoosted: PokemonType[];
@ -118,7 +118,6 @@ export default class PokemonData {
this.pauseEvolutions = !!source.pauseEvolutions; this.pauseEvolutions = !!source.pauseEvolutions;
this.pokerus = !!source.pokerus; this.pokerus = !!source.pokerus;
this.usedTMs = source.usedTMs ?? []; this.usedTMs = source.usedTMs ?? [];
this.evoCounter = source.evoCounter ?? 0;
this.teraType = source.teraType as PokemonType; this.teraType = source.teraType as PokemonType;
this.isTerastallized = !!source.isTerastallized; this.isTerastallized = !!source.isTerastallized;
this.stellarTypesBoosted = source.stellarTypesBoosted ?? []; this.stellarTypesBoosted = source.stellarTypesBoosted ?? [];

View File

@ -3,7 +3,7 @@ import type { SystemSaveData, SessionSaveData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/constants"; import { defaultStarterSpecies } from "#app/constants";
import { AbilityAttr } from "#enums/ability-attr"; import { AbilityAttr } from "#enums/ability-attr";
import { DexAttr } from "#enums/dex-attr"; import { DexAttr } from "#enums/dex-attr";
import { allSpecies } from "#app/data/pokemon-species"; import { allSpecies } from "#app/data/data-lists";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";

View File

@ -1,6 +1,7 @@
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; import type { SessionSaveData, SystemSaveData } from "#app/system/game-data";
import { DexAttr } from "#enums/dex-attr"; import { DexAttr } from "#enums/dex-attr";

View File

@ -1,5 +1,5 @@
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import type { SystemSaveData } from "#app/system/game-data"; import type { SystemSaveData } from "#app/system/game-data";
import { DexAttr } from "#enums/dex-attr"; import { DexAttr } from "#enums/dex-attr";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";

View File

@ -5,7 +5,7 @@ import { getEnumValues, getEnumKeys, fixedInt, randSeedShuffle } from "#app/util
import type { IEggOptions } from "../data/egg"; import type { IEggOptions } from "../data/egg";
import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg"; import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg";
import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; import { VoucherType, getVoucherTypeIcon } from "../system/voucher";
import { getPokemonSpecies } from "../data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { addWindow } from "./ui-theme"; import { addWindow } from "./ui-theme";
import { Tutorial, handleTutorial } from "../tutorial"; import { Tutorial, handleTutorial } from "../tutorial";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";

View File

@ -16,7 +16,9 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives"; import { starterPassiveAbilities } from "#app/data/balance/passives";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";

View File

@ -8,7 +8,7 @@ import { UiMode } from "#enums/ui-mode";
import { FilterTextRow } from "./filter-text"; import { FilterTextRow } from "./filter-text";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { allSpecies } from "#app/data/pokemon-species"; import { allSpecies } from "#app/data/data-lists";
import i18next from "i18next"; import i18next from "i18next";
export default class PokedexScanUiHandler extends FormModalUiHandler { export default class PokedexScanUiHandler extends FormModalUiHandler {

View File

@ -7,7 +7,8 @@ import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type { PokemonForm } from "#app/data/pokemon-species"; import type { PokemonForm } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species";
import { allSpecies } from "#app/data/data-lists";
import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { catchableSpecies } from "#app/data/balance/biomes"; import { catchableSpecies } from "#app/data/balance/biomes";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";

View File

@ -19,7 +19,8 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species";
import { allSpecies } from "#app/data/data-lists";
import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { GameModes } from "#enums/game-modes"; import { GameModes } from "#enums/game-modes";

View File

@ -9,7 +9,7 @@ import { version } from "../../package.json";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { SpeciesId } from "#enums/species-id"; import type { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";

View File

@ -0,0 +1,21 @@
import { allSpecies } from "#app/data/data-lists";
import type PokemonSpecies from "#app/data/pokemon-species";
import type { SpeciesId } from "#enums/species-id";
/**
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
* @param species - The {@linkcode SpeciesId} to fetch.
* If an array of `SpeciesId`s is passed (such as for named trainer spawn pools),
* one will be selected at random.
* @returns The associated {@linkcode PokemonSpecies} object
*/
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
if (Array.isArray(species)) {
// TODO: this RNG roll should not be handled by this function
species = species[Math.floor(Math.random() * species.length)];
}
if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
}
return allSpecies[species - 1];
}

View File

@ -27,7 +27,7 @@ describe("Ability Activation Order", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -27,7 +27,7 @@ describe("Abilities - Analytic", () => {
.moveset([MoveId.SPLASH, MoveId.TACKLE]) .moveset([MoveId.SPLASH, MoveId.TACKLE])
.ability(AbilityId.ANALYTIC) .ability(AbilityId.ANALYTIC)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.startingLevel(200) .startingLevel(200)
.enemyLevel(200) .enemyLevel(200)
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)

View File

@ -35,7 +35,7 @@ describe("Abilities - Commander", () => {
.moveset([MoveId.LIQUIDATION, MoveId.MEMENTO, MoveId.SPLASH, MoveId.FLIP_TURN]) .moveset([MoveId.LIQUIDATION, MoveId.MEMENTO, MoveId.SPLASH, MoveId.FLIP_TURN])
.ability(AbilityId.COMMANDER) .ability(AbilityId.COMMANDER)
.battleStyle("double") .battleStyle("double")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.TACKLE); .enemyMoveset(MoveId.TACKLE);

View File

@ -24,7 +24,7 @@ describe("Abilities - Corrosion", () => {
game.override game.override
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.GRIMER) .enemySpecies(SpeciesId.GRIMER)
.enemyAbility(AbilityId.CORROSION) .enemyAbility(AbilityId.CORROSION)
.enemyMoveset(MoveId.TOXIC); .enemyMoveset(MoveId.TOXIC);

View File

@ -33,7 +33,7 @@ describe("Abilities - Cud Chew", () => {
.startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }]) .startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }])
.ability(AbilityId.CUD_CHEW) .ability(AbilityId.CUD_CHEW)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -23,7 +23,7 @@ describe("Abilities - Dry Skin", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemyAbility(AbilityId.DRY_SKIN) .enemyAbility(AbilityId.DRY_SKIN)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)
.enemySpecies(SpeciesId.CHARMANDER) .enemySpecies(SpeciesId.CHARMANDER)

View File

@ -28,7 +28,7 @@ describe("Abilities - Early Bird", () => {
.moveset([MoveId.REST, MoveId.BELLY_DRUM, MoveId.SPLASH]) .moveset([MoveId.REST, MoveId.BELLY_DRUM, MoveId.SPLASH])
.ability(AbilityId.EARLY_BIRD) .ability(AbilityId.EARLY_BIRD)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -32,7 +32,7 @@ describe("Abilities - Flash Fire", () => {
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.startingLevel(20) .startingLevel(20)
.enemyLevel(20) .enemyLevel(20)
.disableCrits(); .criticalHits(false);
}); });
it("immune to Fire-type moves", async () => { it("immune to Fire-type moves", async () => {

View File

@ -32,7 +32,7 @@ describe("Abilities - Flower Veil", () => {
.enemySpecies(SpeciesId.BULBASAUR) .enemySpecies(SpeciesId.BULBASAUR)
.ability(AbilityId.FLOWER_VEIL) .ability(AbilityId.FLOWER_VEIL)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -21,26 +21,10 @@ describe("Abilities - Forecast", () => {
const RAINY_FORM = 2; const RAINY_FORM = 2;
const SNOWY_FORM = 3; const SNOWY_FORM = 3;
/**
* Tests form changes based on weather changes
* @param {GameManager} game The game manager instance
* @param {WeatherType} weather The active weather to set
* @param form The expected form based on the active weather
* @param initialForm The initial form pre form change
*/
const testWeatherFormChange = async (game: GameManager, weather: WeatherType, form: number, initialForm?: number) => {
game.override.weather(weather).starterForms({ [SpeciesId.CASTFORM]: initialForm });
await game.classicMode.startBattle([SpeciesId.CASTFORM]);
game.move.select(MoveId.SPLASH);
expect(game.scene.getPlayerPokemon()?.formIndex).toBe(form);
};
/** /**
* Tests reverting to normal form when Cloud Nine/Air Lock is active on the field * Tests reverting to normal form when Cloud Nine/Air Lock is active on the field
* @param {GameManager} game The game manager instance * @param game - The game manager instance
* @param {AbilityId} ability The ability that is active on the field * @param ability - The ability that is active on the field
*/ */
const testRevertFormAgainstAbility = async (game: GameManager, ability: AbilityId) => { const testRevertFormAgainstAbility = async (game: GameManager, ability: AbilityId) => {
game.override.starterForms({ [SpeciesId.CASTFORM]: SUNNY_FORM }).enemyAbility(ability); game.override.starterForms({ [SpeciesId.CASTFORM]: SUNNY_FORM }).enemyAbility(ability);
@ -191,10 +175,6 @@ describe("Abilities - Forecast", () => {
30 * 1000, 30 * 1000,
); );
it("reverts to Normal Form during Clear weather", async () => {
await testWeatherFormChange(game, WeatherType.NONE, NORMAL_FORM, SUNNY_FORM);
});
it("reverts to Normal Form if a Pokémon on the field has Air Lock", async () => { it("reverts to Normal Form if a Pokémon on the field has Air Lock", async () => {
await testRevertFormAgainstAbility(game, AbilityId.AIR_LOCK); await testRevertFormAgainstAbility(game, AbilityId.AIR_LOCK);
}); });
@ -277,4 +257,20 @@ describe("Abilities - Forecast", () => {
expect(castform.formIndex).toBe(NORMAL_FORM); expect(castform.formIndex).toBe(NORMAL_FORM);
}); });
// NOTE: The following pairs of tests are intentionally testing the same scenario, switching the player and enemy pokemon
// as this is a regression test where the order of player and enemy mattered.
it("should trigger player's form change when summoned at the same time as an enemy with a weather changing ability", async () => {
game.override.enemyAbility(AbilityId.DROUGHT);
await game.classicMode.startBattle([SpeciesId.CASTFORM, SpeciesId.MAGIKARP]);
const castform = game.scene.getPlayerPokemon()!;
expect(castform.formIndex).toBe(SUNNY_FORM);
});
it("should trigger enemy's form change when summoned at the same time as a player with a weather changing ability", async () => {
game.override.ability(AbilityId.DROUGHT).enemySpecies(SpeciesId.CASTFORM).enemyAbility(AbilityId.FORECAST);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const castform = game.scene.getEnemyPokemon()!;
expect(castform.formIndex).toBe(SUNNY_FORM);
});
}); });

View File

@ -33,7 +33,7 @@ describe("Abilities - Good As Gold", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.GOOD_AS_GOLD) .ability(AbilityId.GOOD_AS_GOLD)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -41,7 +41,7 @@ describe("Abilities - Gulp Missile", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.disableCrits() .criticalHits(false)
.battleStyle("single") .battleStyle("single")
.moveset([MoveId.SURF, MoveId.DIVE, MoveId.SPLASH, MoveId.SUBSTITUTE]) .moveset([MoveId.SURF, MoveId.DIVE, MoveId.SPLASH, MoveId.SUBSTITUTE])
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)

View File

@ -47,7 +47,7 @@ describe("Abilities - Harvest", () => {
.ability(AbilityId.HARVEST) .ability(AbilityId.HARVEST)
.startingLevel(100) .startingLevel(100)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.statusActivation(false) // Since we're using nuzzle to proc both enigma and sitrus berries .statusActivation(false) // Since we're using nuzzle to proc both enigma and sitrus berries
.weather(WeatherType.SUNNY) // guaranteed recovery .weather(WeatherType.SUNNY) // guaranteed recovery
.enemyLevel(1) .enemyLevel(1)

View File

@ -30,7 +30,7 @@ describe("Abilities - Healer", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("double") .battleStyle("double")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Heatproof", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.CHARMANDER) .enemySpecies(SpeciesId.CHARMANDER)
.enemyAbility(AbilityId.HEATPROOF) .enemyAbility(AbilityId.HEATPROOF)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)

View File

@ -29,7 +29,7 @@ describe("Abilities - Honey Gather", () => {
.ability(AbilityId.HONEY_GATHER) .ability(AbilityId.HONEY_GATHER)
.passiveAbility(AbilityId.RUN_AWAY) .passiveAbility(AbilityId.RUN_AWAY)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Hustle", () => {
game.override game.override
.ability(AbilityId.HUSTLE) .ability(AbilityId.HUSTLE)
.moveset([MoveId.TACKLE, MoveId.GIGA_DRAIN, MoveId.FISSURE]) .moveset([MoveId.TACKLE, MoveId.GIGA_DRAIN, MoveId.FISSURE])
.disableCrits() .criticalHits(false)
.battleStyle("single") .battleStyle("single")
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)
.enemySpecies(SpeciesId.SHUCKLE) .enemySpecies(SpeciesId.SHUCKLE)

View File

@ -26,7 +26,7 @@ describe("Abilities - Immunity", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -31,7 +31,7 @@ describe("Abilities - Infiltrator", () => {
.moveset([MoveId.TACKLE, MoveId.WATER_GUN, MoveId.SPORE, MoveId.BABY_DOLL_EYES]) .moveset([MoveId.TACKLE, MoveId.WATER_GUN, MoveId.SPORE, MoveId.BABY_DOLL_EYES])
.ability(AbilityId.INFILTRATOR) .ability(AbilityId.INFILTRATOR)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)

View File

@ -26,7 +26,7 @@ describe("Abilities - Insomnia", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -27,7 +27,7 @@ describe("Abilities - Lightningrod", () => {
.moveset([MoveId.SPLASH, MoveId.SHOCK_WAVE]) .moveset([MoveId.SPLASH, MoveId.SHOCK_WAVE])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("double") .battleStyle("double")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Limber", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -32,7 +32,7 @@ describe("Abilities - Magic Bounce", () => {
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.moveset([MoveId.GROWL, MoveId.SPLASH]) .moveset([MoveId.GROWL, MoveId.SPLASH])
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.MAGIC_BOUNCE) .enemyAbility(AbilityId.MAGIC_BOUNCE)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -89,9 +89,7 @@ describe("Abilities - Magic Guard", () => {
}); });
it("ability effect should not persist when the ability is replaced", async () => { it("ability effect should not persist when the ability is replaced", async () => {
game.override game.override.enemyMoveset(MoveId.WORRY_SEED).statusEffect(StatusEffect.POISON);
.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED])
.statusEffect(StatusEffect.POISON);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -26,7 +26,7 @@ describe("Abilities - Magma Armor", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Mimicry", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.MIMICRY) .ability(AbilityId.MIMICRY)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);
}); });

View File

@ -27,7 +27,7 @@ describe("Abilities - Mold Breaker", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.MOLD_BREAKER) .ability(AbilityId.MOLD_BREAKER)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -25,7 +25,7 @@ describe("Abilities - Mummy", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.MUMMY) .ability(AbilityId.MUMMY)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.TACKLE); .enemyMoveset(MoveId.TACKLE);

View File

@ -26,7 +26,7 @@ describe("Abilities - Mycelium Might", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.SHUCKLE) .enemySpecies(SpeciesId.SHUCKLE)
.enemyAbility(AbilityId.CLEAR_BODY) .enemyAbility(AbilityId.CLEAR_BODY)

View File

@ -31,7 +31,7 @@ describe("Abilities - Neutralizing Gas", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.NEUTRALIZING_GAS) .ability(AbilityId.NEUTRALIZING_GAS)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -29,7 +29,7 @@ describe("Abilities - Normalize", () => {
.moveset([MoveId.TACKLE]) .moveset([MoveId.TACKLE])
.ability(AbilityId.NORMALIZE) .ability(AbilityId.NORMALIZE)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Oblivious", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -26,7 +26,7 @@ describe("Abilities - Own Tempo", () => {
.moveset([MoveId.SPLASH]) .moveset([MoveId.SPLASH])
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -28,7 +28,7 @@ describe("Abilities - Parental Bond", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.ability(AbilityId.PARENTAL_BOND) .ability(AbilityId.PARENTAL_BOND)
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)
.enemyAbility(AbilityId.FUR_COAT) .enemyAbility(AbilityId.FUR_COAT)

View File

@ -23,13 +23,13 @@ describe("Abilities - Perish Song", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.CURSOLA) .starterSpecies(SpeciesId.CURSOLA)
.ability(AbilityId.PERISH_BODY) .ability(AbilityId.PERISH_BODY)
.moveset([MoveId.SPLASH]) .moveset(MoveId.SPLASH)
.enemyMoveset([MoveId.AQUA_JET]); .enemyMoveset(MoveId.AQUA_JET);
}); });
it("should trigger when hit with damaging move", async () => { it("should trigger when hit with damaging move", async () => {

View File

@ -28,7 +28,7 @@ describe("Abilities - Protosynthesis", () => {
.moveset([MoveId.SPLASH, MoveId.TACKLE]) .moveset([MoveId.SPLASH, MoveId.TACKLE])
.ability(AbilityId.PROTOSYNTHESIS) .ability(AbilityId.PROTOSYNTHESIS)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);

View File

@ -24,7 +24,7 @@ describe("Abilities - Sand Spit", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.SILICOBRA) .starterSpecies(SpeciesId.SILICOBRA)

View File

@ -31,7 +31,7 @@ describe("Abilities - Sap Sipper", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.ability(AbilityId.SAP_SIPPER) .ability(AbilityId.SAP_SIPPER)
.enemySpecies(SpeciesId.RATTATA) .enemySpecies(SpeciesId.RATTATA)
.enemyAbility(AbilityId.SAP_SIPPER) .enemyAbility(AbilityId.SAP_SIPPER)

View File

@ -24,12 +24,12 @@ describe("Abilities - Seed Sower", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.ARBOLIVA) .starterSpecies(SpeciesId.ARBOLIVA)
.ability(AbilityId.SEED_SOWER) .ability(AbilityId.SEED_SOWER)
.moveset([MoveId.SPLASH]); .moveset(MoveId.SPLASH);
}); });
it("should trigger when hit with damaging move", async () => { it("should trigger when hit with damaging move", async () => {

View File

@ -24,7 +24,7 @@ describe("Abilities - Serene Grace", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.disableCrits() .criticalHits(false)
.battleStyle("single") .battleStyle("single")
.ability(AbilityId.SERENE_GRACE) .ability(AbilityId.SERENE_GRACE)
.moveset([MoveId.AIR_SLASH]) .moveset([MoveId.AIR_SLASH])

View File

@ -31,7 +31,7 @@ describe("Abilities - Sheer Force", () => {
.enemySpecies(SpeciesId.ONIX) .enemySpecies(SpeciesId.ONIX)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset([MoveId.SPLASH]) .enemyMoveset([MoveId.SPLASH])
.disableCrits(); .criticalHits(false);
}); });
const SHEER_FORCE_MULT = 1.3; const SHEER_FORCE_MULT = 1.3;

View File

@ -0,0 +1,69 @@
import type Move from "#app/data/moves/move";
import Pokemon from "#app/field/pokemon";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest";
describe("Abilities - Shell Armor", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let critSpy: MockInstance<(source: Pokemon, move: Move, simulated?: boolean) => boolean>;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.ability(AbilityId.SHELL_ARMOR)
.battleStyle("single")
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.statusEffect(StatusEffect.POISON);
critSpy = vi.spyOn(Pokemon.prototype, "getCriticalHitResult");
});
it("should prevent natural crit rolls from suceeding", async () => {
game.override.criticalHits(true); // force random crit rolls to always succeed
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
game.move.use(MoveId.SPLASH);
await game.move.forceEnemyMove(MoveId.TACKLE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(critSpy).toHaveReturnedWith(false);
});
it("should prevent guaranteed-crit moves from critting", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
game.move.use(MoveId.SPLASH);
await game.move.forceEnemyMove(MoveId.FLOWER_TRICK);
await game.phaseInterceptor.to("TurnEndPhase");
expect(critSpy).toHaveReturnedWith(false);
});
it("should block Merciless guaranteed crits", async () => {
game.override.enemyAbility(AbilityId.MERCILESS);
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
game.move.use(MoveId.SPLASH);
await game.move.forceEnemyMove(MoveId.TACKLE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(critSpy).toHaveReturnedWith(false);
});
});

View File

@ -27,7 +27,7 @@ describe("Abilities - Stakeout", () => {
.moveset([MoveId.SPLASH, MoveId.SURF]) .moveset([MoveId.SPLASH, MoveId.SURF])
.ability(AbilityId.STAKEOUT) .ability(AbilityId.STAKEOUT)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.startingLevel(100) .startingLevel(100)
.enemyLevel(100) .enemyLevel(100)
.enemySpecies(SpeciesId.SNORLAX) .enemySpecies(SpeciesId.SNORLAX)

View File

@ -24,7 +24,7 @@ describe("Abilities - Stall", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.REGIELEKI) .enemySpecies(SpeciesId.REGIELEKI)
.enemyAbility(AbilityId.STALL) .enemyAbility(AbilityId.STALL)
.enemyMoveset(MoveId.QUICK_ATTACK) .enemyMoveset(MoveId.QUICK_ATTACK)

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