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_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001
# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN
VITE_DISCORD_CLIENT_ID=1234567890
VITE_GOOGLE_CLIENT_ID=1234567890
VITE_I18N_DEBUG=0

View File

@ -48,7 +48,7 @@ async function promptTestType() {
{
type: "list",
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"],
},
]);

View File

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

View File

@ -4,7 +4,8 @@ import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { PokemonSpeciesFilter } 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 {
fixedInt,
getIvsFromId,
@ -2104,12 +2105,15 @@ export default class BattleScene extends SceneBase {
}
getMaxExpLevel(ignoreLevelCap = false): number {
if (Overrides.LEVEL_CAP_OVERRIDE > 0) {
return Overrides.LEVEL_CAP_OVERRIDE;
const capOverride = Overrides.LEVEL_CAP_OVERRIDE ?? 0;
if (capOverride > 0) {
return capOverride;
}
if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) {
if (ignoreLevelCap || capOverride < 0) {
return Number.MAX_SAFE_INTEGER;
}
const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10;
const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex);
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);
}
if (
modifier instanceof PokemonHeldItemModifier &&
!isNullOrUndefined(modifier.getSpecies()) &&
!this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!)
) {
modifiers.splice(m--, 1);
}
}
for (const modifier of modifiers) {
if (modifier instanceof PersistentModifier) {
@ -3502,17 +3513,13 @@ export default class BattleScene extends SceneBase {
sessionEncounterRate +
Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2);
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE)
? favoredEncounterRate
: Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
// 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 =
encounteredEvents.length === 0 ||
waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3 ||
!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
encounteredEvents.length === 0 || waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3;
if (canSpawn) {
if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) {
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
this.executeWithSeedOffset(

View File

@ -24,11 +24,11 @@ import { allMoves } from "../data-lists";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { TerrainType } from "#app/data/terrain";
import { pokemonFormChanges } from "../pokemon-forms";
import {
SpeciesFormChangeRevertWeatherFormTrigger,
SpeciesFormChangeWeatherTrigger,
SpeciesFormChangeAbilityTrigger,
} from "../pokemon-forms/form-change-triggers";
import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers";
import i18next from "i18next";
import { Command } from "#enums/command";
import { BerryModifierType } from "#app/modifier/modifier-type";
@ -3971,27 +3971,32 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
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 {
const isCastformWithForecast =
pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST;
const isCherrimWithFlowerGift =
pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT;
return isCastformWithForecast || isCherrimWithFlowerGift;
return !!pokemonFormChanges[pokemon.species.speciesId]?.some(
fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon),
);
}
/**
* Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both
* {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and
* {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it
* is the specific Pokemon and ability
* @param {Pokemon} pokemon the Pokemon with this ability
* @param _passive n/a
* @param _args n/a
* Trigger the pokemon's forme change by invoking
* {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange}
*
* @param pokemon - The Pokemon with this ability
* @param _passive - unused
* @param simulated - unused
* @param _args - unused
*/
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
if (!simulated) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger);
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger);
}
}
}
@ -4617,7 +4622,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA
* @param stat The stat being affected
* @param cancelled Holds whether the stat change was already prevented.
* @param args Args[0] is the target pokemon of the stat change.
* @returns
* @returns `true` if the ability can be applied
*/
override canApplyPreStatStageChange(
_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
* @param args - [0] is a boolean holder representing whether the attack can crit
* Apply the block crit ability by setting the value in the provided boolean holder to `true`.
* @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting.
*/
override apply(
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_cancelled: BooleanHolder,
args: [BooleanHolder, ...any],
args: [BooleanHolder],
): 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
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
* @param {Pokemon} _pokemon the Pokemon with this ability
* @param _passive n/a
* @param _weather n/a
* @param _args n/a
* @param _pokemon - The Pokemon with this ability
* @param _passive - unused
* @param simulated - unused
* @param _weather - unused
* @param _args - unused
*/
override applyPostWeatherChange(
_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 { defaultStarterSpecies } from "#app/constants";
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 type Pokemon from "#app/field/pokemon";
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 { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common";
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 { pokerogueApi } from "#app/plugins/api/pokerogue-api";
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 { Ability } from "./abilities/ability";
import type Move from "./moves/move";
export const allAbilities: Ability[] = [];
export const allMoves: Move[] = [];
export const allSpecies: PokemonSpecies[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;

View File

@ -1,7 +1,7 @@
import type BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
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 { VariantTier } from "#enums/variant-tier";
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 { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
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 { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common";

View File

@ -18,7 +18,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
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 { SpeciesId } from "#enums/species-id";
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 {
applyAbilityOverrideToPokemon,

View File

@ -19,7 +19,7 @@ import {
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} 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 type { PlayerPokemon } 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 { globalScene } from "#app/global-scene";
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 { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";

View File

@ -15,7 +15,7 @@ import {
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-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 Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";

View File

@ -20,7 +20,7 @@ import {
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
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 { PokemonType } from "#enums/pokemon-type";
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 Pokemon from "#app/field/pokemon";
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 { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
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 { SpeciesId } from "#enums/species-id";
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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
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 { SpeciesId } from "#enums/species-id";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -14,7 +14,7 @@ import {
getHighestLevelPlayerPokemon,
koPlayerPokemon,
} 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 { ModifierTier } from "#enums/modifier-tier";
import { randSeedInt } from "#app/utils/common";

View File

@ -17,7 +17,7 @@ import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender";
import { NumberHolder, randSeedInt } from "#app/utils/common";
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 {
doPlayerFlee,

View File

@ -24,7 +24,7 @@ import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/data/moves/pokemon-move";
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 { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
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 i18next from "i18next";
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 { Nature } from "#enums/nature";
import { MoveId } from "#enums/move-id";

View File

@ -15,7 +15,7 @@ import {
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
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 { SpeciesId } from "#enums/species-id";
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 type MysteryEncounter 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 { Nature } from "#enums/nature";
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 { SpeciesId } from "#enums/species-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 { Nature } from "#enums/nature";
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 i18next from "#app/plugins/i18n";
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 { BattlerIndex } from "#enums/battler-index";
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 { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
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 { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv";

View File

@ -1,6 +1,5 @@
import { globalScene } from "#app/global-scene";
import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
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 type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { SpeciesFormKey } from "#enums/species-form-key";
import { TimeOfDay } from "#enums/time-of-day";
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 {
requiredHeldItemModifiers: string[];
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 { StatusEffect } from "#enums/status-effect";
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 { getNatureName } from "#app/data/nature";
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 type { PokemonType } from "#enums/pokemon-type";
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 {
getEncounterText,

View File

@ -42,6 +42,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives";
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
import { hasExpSprite } from "#app/sprites/sprite-utils";
import { Gender } from "./gender";
import { allSpecies } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
export enum Region {
NORMAL,
@ -82,24 +84,6 @@ export const normalForm: SpeciesId[] = [
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 {
const retSpecies: PokemonSpecies =
species >= 2000
@ -1449,8 +1433,6 @@ export function getPokerusStarters(): PokemonSpecies[] {
return pokerusStarters;
}
export const allSpecies: PokemonSpecies[] = [];
// biome-ignore format: manually formatted
export function initSpecies() {
allSpecies.push(

View File

@ -10,7 +10,7 @@ import {
randSeedIntRange,
} from "#app/utils/common";
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 { doubleBattleDialogue } from "../double-battle-dialogue";
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 { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import {
getTerrainClearMessage,
getTerrainStartMessage,

View File

@ -15,12 +15,8 @@ import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import {
default as PokemonSpecies,
getFusedSpeciesName,
getPokemonSpecies,
getPokemonSpeciesForm,
} from "#app/data/pokemon-species";
import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import {
NumberHolder,
@ -79,11 +75,12 @@ import {
import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender";
import { Status, getRandomStatus } from "#app/data/status-effect";
import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions";
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import {
pokemonEvolutions,
pokemonPrevolutions,
FusionSpeciesFormEvolution,
validateShedinjaEvo,
} from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import {
@ -370,7 +367,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0);
this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus;
this.evoCounter = dataSource.evoCounter ?? 0;
this.fusionSpecies =
dataSource.fusionSpecies instanceof PokemonSpecies
? 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
* the given source
* Calculate the critical-hit stage of a move used **against** this pokemon by
* the given source.
*
* @param source - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used
* @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);
const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) {
critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1;
} else {
critStage.value += 2;
}
// Dragon cheer only gives +1 crit stage to non-dragon types
critStage.value +=
critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2;
}
console.log(`crit stage: +${critStage.value}`);
@ -2519,14 +2514,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) {
if (
!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;
}
if (e.validate(this)) {
return e;
}
}
}
@ -2536,14 +2525,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
e => new FusionSpeciesFormEvolution(this.species.speciesId, e),
);
for (const fe of fusionEvolutions) {
if (
!fe.item &&
this.level >= fe.level &&
(isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey)
) {
if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) {
return fe;
}
if (fe.validate(this)) {
return fe;
}
}
}
@ -2785,17 +2768,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
if (!this.shiny) {
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride;
}
const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE);
if (applyModifiersToOverride) {
if (timedEventManager.isEventActive()) {
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
}
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
} else {
shinyThreshold.value = thresholdOverride;
}
this.shiny = randSeedInt(65536) < shinyThreshold.value;
@ -2864,16 +2842,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.species.abilityHidden) {
return false;
}
const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
haThreshold.value = thresholdOverride;
}
const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
if (applyModifiersToOverride) {
if (!this.hasTrainer()) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
}
} else {
haThreshold.value = thresholdOverride;
}
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 move - The {@linkcode Move} being used
* @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`)
* @returns whether the move critically hits the pokemon
* @returns Whether the move will critically hit the defender.
*/
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
getCriticalHitResult(source: Pokemon, move: Move): boolean {
if (move.hasAttr("FixedDamageAttr")) {
// fixed damage moves (Dragon Rage, etc.) will nevet crit
return false;
}
const isCritical = new BooleanHolder(false);
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
isCritical.value = true;
}
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move);
if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
}
const alwaysCrit = new BooleanHolder(false);
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move);
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
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);
}
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 {
@ -5830,7 +5816,7 @@ export class PlayerPokemon extends Pokemon {
if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) {
const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1];
if (newEvolution.condition?.predicate(this)) {
if (validateShedinjaEvo()) {
const newPokemon = globalScene.addPlayerPokemon(
this.species,
this.level,
@ -5860,7 +5846,6 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionLuck = this.fusionLuck;
newPokemon.fusionTeraType = this.fusionTeraType;
newPokemon.usedTMs = this.usedTMs;
newPokemon.evoCounter = this.evoCounter;
globalScene.getPlayerParty().push(newPokemon);
newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies);
@ -5949,7 +5934,6 @@ export class PlayerPokemon extends Pokemon {
this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck;
this.fusionCustomPokemonData = pokemon.customPokemonData;
this.evoCounter = Math.max(pokemon.evoCounter, this.evoCounter);
if (pokemon.pauseEvolutions || this.pauseEvolutions) {
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);
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) {
const { waveIndex } = globalScene.currentBattle;
const ivs: number[] = [];

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
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 { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
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 { ChallengeType } from "#enums/challenge-type";
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 Overrides from "#app/overrides";
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:
* - override from overrides.ts
* - starting level override from overrides.ts
* - 20 for Daily Runs
* - 5 for all other modes
*/
getStartingLevel(): number {
if (Overrides.STARTING_LEVEL_OVERRIDE) {
if (Overrides.STARTING_LEVEL_OVERRIDE > 0) {
return Overrides.STARTING_LEVEL_OVERRIDE;
}
switch (this.modeId) {

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { applyChallenges } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
import { Gender } from "#app/data/gender";
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 Overrides from "#app/overrides";
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 { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
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 { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common";
import Overrides from "#app/overrides";

View File

@ -3,7 +3,8 @@ import { globalScene } from "#app/global-scene";
import type { Gender } from "../data/gender";
import { Nature } from "#enums/nature";
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 Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
@ -45,7 +46,6 @@ export default class PokemonData {
public pauseEvolutions: boolean;
public pokerus: boolean;
public usedTMs: MoveId[];
public evoCounter: number;
public teraType: PokemonType;
public isTerastallized: boolean;
public stellarTypesBoosted: PokemonType[];
@ -118,7 +118,6 @@ export default class PokemonData {
this.pauseEvolutions = !!source.pauseEvolutions;
this.pokerus = !!source.pokerus;
this.usedTMs = source.usedTMs ?? [];
this.evoCounter = source.evoCounter ?? 0;
this.teraType = source.teraType as PokemonType;
this.isTerastallized = !!source.isTerastallized;
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 { AbilityAttr } from "#enums/ability-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 { isNullOrUndefined } from "#app/utils/common";
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";

View File

@ -1,6 +1,7 @@
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
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 type { SessionSaveData, SystemSaveData } from "#app/system/game-data";
import { DexAttr } from "#enums/dex-attr";

View File

@ -1,5 +1,5 @@
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 { DexAttr } from "#enums/dex-attr";
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 { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg";
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 { Tutorial, handleTutorial } from "../tutorial";
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 { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
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 { starterPassiveAbilities } from "#app/data/balance/passives";
import { PokemonType } from "#enums/pokemon-type";

View File

@ -8,7 +8,7 @@ import { UiMode } from "#enums/ui-mode";
import { FilterTextRow } from "./filter-text";
import { allAbilities } 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";
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 type { PokemonForm } 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 { catchableSpecies } from "#app/data/balance/biomes";
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 { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
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 { PokemonType } from "#enums/pokemon-type";
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 { globalScene } from "#app/global-scene";
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 { 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])
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.disableCrits()
.criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,26 +21,10 @@ describe("Abilities - Forecast", () => {
const RAINY_FORM = 2;
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
* @param {GameManager} game The game manager instance
* @param {AbilityId} ability The ability that is active on the field
* @param game - The game manager instance
* @param ability - The ability that is active on the field
*/
const testRevertFormAgainstAbility = async (game: GameManager, ability: AbilityId) => {
game.override.starterForms({ [SpeciesId.CASTFORM]: SUNNY_FORM }).enemyAbility(ability);
@ -191,10 +175,6 @@ describe("Abilities - Forecast", () => {
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 () => {
await testRevertFormAgainstAbility(game, AbilityId.AIR_LOCK);
});
@ -277,4 +257,20 @@ describe("Abilities - Forecast", () => {
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])
.ability(AbilityId.GOOD_AS_GOLD)
.battleStyle("single")
.disableCrits()
.criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ describe("Abilities - Magic Bounce", () => {
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.moveset([MoveId.GROWL, MoveId.SPLASH])
.disableCrits()
.criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.MAGIC_BOUNCE)
.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 () => {
game.override
.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED])
.statusEffect(StatusEffect.POISON);
game.override.enemyMoveset(MoveId.WORRY_SEED).statusEffect(StatusEffect.POISON);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ describe("Abilities - Sheer Force", () => {
.enemySpecies(SpeciesId.ONIX)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset([MoveId.SPLASH])
.disableCrits();
.criticalHits(false);
});
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])
.ability(AbilityId.STAKEOUT)
.battleStyle("single")
.disableCrits()
.criticalHits(false)
.startingLevel(100)
.enemyLevel(100)
.enemySpecies(SpeciesId.SNORLAX)

View File

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

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