From e089fd28f2497dab862654f1325539b7292afb77 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Thu, 5 Jun 2025 23:02:55 -0400 Subject: [PATCH] Added overloads to modifier functions, cleaned up existing uses theroef --- src/@types/modifier-predicate.ts | 4 ++ src/battle-scene.ts | 53 +++++++++---------- src/battle.ts | 10 ++-- src/data/abilities/ability.ts | 16 +++--- src/data/moves/move.ts | 20 ++++--- .../encounters/absolute-avarice-encounter.ts | 4 +- .../encounters/berries-abound-encounter.ts | 13 +++-- .../encounters/bug-type-superfan-encounter.ts | 4 +- .../encounters/delibirdy-encounter.ts | 10 ++-- .../encounters/trash-to-treasure-encounter.ts | 27 ++++------ .../encounters/uncommon-breed-encounter.ts | 4 +- .../mystery-encounter-requirements.ts | 6 +-- .../utils/encounter-pokemon-utils.ts | 4 +- src/field/pokemon.ts | 23 ++++---- src/modifier/modifier-type.ts | 5 +- src/modifier/modifier.ts | 8 +-- src/phases/battle-end-phase.ts | 5 +- src/phases/select-biome-phase.ts | 2 +- src/phases/select-modifier-phase.ts | 14 +---- src/phases/switch-summon-phase.ts | 7 ++- src/ui/party-ui-handler.ts | 13 ++--- test/abilities/harvest.test.ts | 4 +- test/items/dire_hit.test.ts | 4 +- .../absolute-avarice-encounter.test.ts | 5 +- 24 files changed, 126 insertions(+), 139 deletions(-) create mode 100644 src/@types/modifier-predicate.ts diff --git a/src/@types/modifier-predicate.ts b/src/@types/modifier-predicate.ts new file mode 100644 index 00000000000..82727dca60b --- /dev/null +++ b/src/@types/modifier-predicate.ts @@ -0,0 +1,4 @@ +import type { Modifier } from "../modifier/modifier"; + +export type ModifierPredicate = (modifier: Modifier) => boolean; +export type ModifierIdentityPredicate = (modifier: Modifier) => modifier is T; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index bdf63cfbf96..7c4b840deda 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -19,12 +19,8 @@ import { type Constructor, } from "#app/utils/common"; import { deepMergeSpriteData } from "#app/utils/data"; -import type { - Modifier, - ModifierIdentityPredicate, - ModifierPredicate, - TurnHeldItemTransferModifier, -} from "./modifier/modifier"; +import type { Modifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import type { ModifierIdentityPredicate, ModifierPredicate } from "./@types/modifier-predicate"; import { ConsumableModifier, ConsumablePokemonModifier, @@ -2125,9 +2121,10 @@ export default class BattleScene extends SceneBase { enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: number, total: number) => (total += iv), 0) / 93) * 0.2 + 0.8); - this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map( - m => (scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()), - ); + this.findModifiers( + (m): m is PokemonHeldItemModifier => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, + false, + ).forEach(m => (scoreIncrease *= m.getScoreMultiplier())); if (enemy.isBoss()) { scoreIncrease *= Math.sqrt(enemy.bossSegments); } @@ -3070,9 +3067,10 @@ export default class BattleScene extends SceneBase { const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; newItemModifier.pokemonId = target.id; const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer(), - ) as PokemonHeldItemModifier; + ); if (matchingModifier) { const maxStackCount = matchingModifier.getMaxStackCount(); @@ -3137,9 +3135,10 @@ export default class BattleScene extends SceneBase { } const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, target.isPlayer(), - ) as PokemonHeldItemModifier; + ); if (matchingModifier) { const maxStackCount = matchingModifier.getMaxStackCount(); @@ -3365,28 +3364,29 @@ export default class BattleScene extends SceneBase { * @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier} * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the list of all modifiers that matched `modifierType`. + * @deprecated - use `findModifiers` */ getModifiers(modifierType: Constructor, player = true): T[] { return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); } + findModifiers(modifierFilter: ModifierIdentityPredicate, isPlayer?: boolean): T[]; + findModifiers(modifierFilter: ModifierPredicate, isPlayer?: boolean): PersistentModifier[]; /** * Get all of the modifiers that pass the `modifierFilter` function * @param modifierFilter - The function used to filter a target's modifiers * @param isPlayer - Whether to search the player (`true`) or the enemy (`false`) party; default `true`. * @returns - An array containing all modifiers that passed the `modifierFilter` function. */ - findModifiers(modifierFilter: ModifierPredicate, isPlayer?: boolean): PersistentModifier[]; - findModifiers(modifierFilter: ModifierIdentityPredicate, isPlayer?: boolean): T[]; findModifiers(modifierFilter: ModifierPredicate, isPlayer = true): PersistentModifier[] { return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); } - findModifier(modifierFilter: ModifierPredicate, player?: boolean): PersistentModifier | undefined; findModifier( modifierFilter: ModifierIdentityPredicate, player?: boolean, ): T | undefined; + findModifier(modifierFilter: ModifierPredicate, player?: boolean): PersistentModifier | undefined; /** * Get the first modifier that passes the `modifierFilter` function. * @param modifierFilter - The function used to filter a target's modifiers. @@ -3503,13 +3503,10 @@ export default class BattleScene extends SceneBase { let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === SpeciesId.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. - const formChangeItemModifiers = ( - this.findModifiers( - m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id, - ) as PokemonFormChangeItemModifier[] - ) - .filter(m => m.active) - .map(m => m.formChangeItem); + const formChangeItemModifiers = this.findModifiers( + (m): m is PokemonFormChangeItemModifier => + m instanceof PokemonFormChangeItemModifier && m.active && m.pokemonId === pokemon.id, + ).map(m => m.formChangeItem); matchingFormChange = formChangeItemModifiers.includes(FormChangeItem.N_LUNARIZER) ? matchingFormChangeOpts[0] @@ -3691,13 +3688,13 @@ export default class BattleScene extends SceneBase { ): void { const participantIds = pokemonParticipantIds ?? this.currentBattle.playerParticipantIds; const party = this.getPlayerParty(); - const expShareModifier = this.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; + const expShareModifier = this.findModifier(m => m instanceof ExpShareModifier); + const expBalanceModifier = this.findModifier(m => m instanceof ExpBalanceModifier); const multipleParticipantExpBonusModifier = this.findModifier( m => m instanceof MultipleParticipantExpBonusModifier, - ) as MultipleParticipantExpBonusModifier; - const nonFaintedPartyMembers = party.filter(p => p.hp); - const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.getMaxExpLevel()); + ); + const nonFaintedPartyMembers = party.filter(p => p.hp > 0); + const expPartyMembers = party.filter(p => p.hp > 0 && p.level < this.getMaxExpLevel()); const partyMemberExp: number[] = []; // EXP value calculation is based off Pokemon.getExpValue if (useWaveIndexMultiplier) { diff --git a/src/battle.ts b/src/battle.ts index 8e63a680c06..b70fdf22340 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -178,14 +178,14 @@ export default class Battle { this.postBattleLoot.push( ...globalScene .findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false, ) .map(i => { - const ret = i as PokemonHeldItemModifier; - //@ts-ignore - this is awful to fix/change - ret.pokemonId = null; - return ret; + // @ts-ignore - this is awful to fix/change + i.pokemonId = null; + return i; }), ); } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8f5f267f7ef..2d1b8aa3493 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1246,7 +1246,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { /** * Determine if the move type change attribute can be applied - * + * * Can be applied if: * - The ability's condition is met, e.g. pixilate only boosts normal moves, * - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK} @@ -1262,7 +1262,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { */ override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { return (!this.condition || this.condition(pokemon, _defender, move)) && - !noAbilityTypeOverrideMoves.has(move.id) && + !noAbilityTypeOverrideMoves.has(move.id) && (!pokemon.isTerastallized || (move.id !== MoveId.TERA_BLAST && (move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))); @@ -1777,8 +1777,10 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers((m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier + && m.pokemonId === target.id, target.isPlayer() + ); } } @@ -1904,8 +1906,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers((m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier + && m.pokemonId === target.id, target.isPlayer() + ); } } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7e9f99e28c1..86d3c07d31c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -815,7 +815,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.hasModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } @@ -2570,8 +2570,10 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers((m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier + && m.pokemonId === target.id, target.isPlayer() + ); } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -2652,8 +2654,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers((m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier + && m.pokemonId === target.id, target.isPlayer() + ); } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -2712,8 +2716,10 @@ export class EatBerryAttr extends MoveEffectAttr { } getTargetHeldBerries(target: Pokemon): BerryModifier[] { - return globalScene.findModifiers(m => m instanceof BerryModifier - && (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[]; + return globalScene.findModifiers((m): m is BerryModifier => + m instanceof BerryModifier + && m.pokemonId === target.id, target.isPlayer() + ); } reduceBerryModifier(target: Pokemon) { diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index f46e1360b0d..7d5996ead20 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -191,7 +191,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3"); // Get all player berry items, remove from party, and store reference - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier); // Sort berries by party member ID to more easily re-add later if necessary const berryItemsMap = new Map(); @@ -256,7 +256,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Remove the berries from the party // Session has been safely saved at this point, so data won't be lost - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier); berryItems.forEach(berryMod => { globalScene.removeModifier(berryMod); }); diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 7f54e51565e..0fbee9c40df 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -314,12 +314,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { // Will try to apply to prioritized pokemon first, then do normal application method if it fails if (prioritizedPokemon) { const heldBerriesOfType = globalScene.findModifier( - m => - m instanceof BerryModifier && - m.pokemonId === prioritizedPokemon.id && - (m as BerryModifier).berryType === berryType, + (m): m is BerryModifier => + m instanceof BerryModifier && m.pokemonId === prioritizedPokemon.id && m.berryType === berryType, true, - ) as BerryModifier; + ); if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); @@ -330,9 +328,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { // Iterate over the party until berry was successfully given for (const pokemon of party) { const heldBerriesOfType = globalScene.findModifier( - m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, + (m): m is BerryModifier => + m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true, - ) as BerryModifier; + ); if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { applyModifierTypeToPlayerPokemon(pokemon, berry); diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c080122f922..884a4bb8b61 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -367,10 +367,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!]; const specialOptions: ModifierTypeOption[] = []; - if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + if (!globalScene.hasModifier(m => m instanceof MegaEvolutionAccessModifier)) { modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!); } - if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + if (!globalScene.hasModifier(m => m instanceof GigantamaxAccessModifier)) { modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!); } const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM); diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 8d3d30bcd66..e013bbda9b3 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -166,7 +166,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with .withOptionPhase(async () => { // Give the player an Amulet Coin // Check if the player has max stacks of that item already - const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; + const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier); if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -247,9 +247,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed if (modifier instanceof BerryModifier) { // Check if the player has max stacks of that Candy Jar already - const existing = globalScene.findModifier( - m => m instanceof LevelIncrementBoosterModifier, - ) as LevelIncrementBoosterModifier; + const existing = globalScene.findModifier(m => m instanceof LevelIncrementBoosterModifier); if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -271,7 +269,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with } } else { // Check if the player has max stacks of that Berry Pouch already - const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; + const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier); if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -357,7 +355,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check if the player has max stacks of Healing Charm already - const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; + const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier); if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index a0051058d02..e41db659866 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -17,7 +17,8 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { SpeciesId } from "#enums/species-id"; -import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier"; +import { PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier"; +import type { HitHealModifier } from "#app/modifier/modifier"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; @@ -231,12 +232,10 @@ async function tryApplyDigRewardItems() { // Iterate over the party until an item was successfully given // First leftovers for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, + const existingLeftovers = globalScene.findModifier( + (m): m is TurnHealModifier => m instanceof TurnHealModifier && m.pokemonId === pokemon.id, true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - + ); if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { await applyModifierTypeToPlayerPokemon(pokemon, leftovers); break; @@ -245,12 +244,10 @@ async function tryApplyDigRewardItems() { // Second leftovers for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, + const existingLeftovers = globalScene.findModifier( + (m): m is TurnHealModifier => m instanceof TurnHealModifier && m.pokemonId === pokemon.id, true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - + ); if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { await applyModifierTypeToPlayerPokemon(pokemon, leftovers); break; @@ -270,12 +267,10 @@ async function tryApplyDigRewardItems() { // Only Shell bell for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, + const existingShellBell = globalScene.findModifier( + (m): m is HitHealModifier => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true, - ) as PokemonHeldItemModifier[]; - const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier; - + ); if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) { await applyModifierTypeToPlayerPokemon(pokemon, shellBell); break; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index e51a8554120..803f28f73cf 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -204,9 +204,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. // Remove 4 random berries from player's party // Get all player berry items, remove from party, and store reference - const berryItems: BerryModifier[] = globalScene.findModifiers( - m => m instanceof BerryModifier, - ) as BerryModifier[]; + const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier); for (let i = 0; i < 4; i++) { const index = randSeedInt(berryItems.length); const randBerry = berryItems[index]; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index ec78408ea83..69760602778 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -377,10 +377,8 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { let modifierCount = 0; for (const modifier of this.requiredHeldItemModifiers) { const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier); - if (matchingMods?.length > 0) { - for (const matchingMod of matchingMods) { - modifierCount += matchingMod.stackCount; - } + for (const matchingMod of matchingMods) { + modifierCount += matchingMod.stackCount; } } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index d4102c045c0..3815c85b2dc 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -404,12 +404,12 @@ export async function applyModifierTypeToPlayerPokemon( // Check if the Pokemon has max stacks of that item already const modifier = modType.newModifier(pokemon); const existing = globalScene.findModifier( - m => + (m): m is PokemonHeldItemModifier => m instanceof PokemonHeldItemModifier && m.type.id === modType.id && m.pokemonId === pokemon.id && m.matchType(modifier), - ) as PokemonHeldItemModifier; + ); // At max stacks if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cd8563cfb30..33d92c32a2b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1206,14 +1206,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); } - getHeldItems(): PokemonHeldItemModifier[] { + /** + * Return all of this Pokemon's held modifiers. + * @param transferrableOnly - Whether to only consider transferrable held items; default `false` + * @returns An array of all {@linkcode PokemonHeldItemModifier}s held by this Pokemon. + */ + getHeldItems(transferrableOnly = this.getFusionIconAtlasKey): PokemonHeldItemModifier[] { if (!globalScene) { return []; } return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.pokemonId === this.id && !(transferrableOnly && !m.isTransferable()), this.isPlayer(), - ) as PokemonHeldItemModifier[]; + ); } updateScale(): void { @@ -5872,10 +5878,7 @@ export class PlayerPokemon extends Pokemon { globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); - const modifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, - true, - ) as PokemonHeldItemModifier[]; + const modifiers = this.getHeldItems(); modifiers.forEach(m => { const clonedModifier = m.clone() as PokemonHeldItemModifier; clonedModifier.pokemonId = newPokemon.id; @@ -5993,11 +5996,7 @@ export class PlayerPokemon extends Pokemon { } // combine the two mons' held items - const fusedPartyMemberHeldModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - for (const modifier of fusedPartyMemberHeldModifiers) { + for (const modifier of pokemon.getHeldItems()) { globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); } globalScene.updateModifiers(true, true); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ccbc202407b..2cc8c7af1a1 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -394,8 +394,9 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { (pokemon: PlayerPokemon) => { const dummyModifier = this.newModifier(pokemon); const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier), - ) as PokemonHeldItemModifier; + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier), + ); const maxStackCount = dummyModifier.getMaxStackCount(); if (!maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 8c8cf777cb8..63573ed31e5 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -54,9 +54,6 @@ import { } from "#app/data/abilities/ability"; import { globalScene } from "#app/global-scene"; -export type ModifierPredicate = (modifier: Modifier) => boolean; -export type ModifierIdentityPredicate = (modifier: Modifier) => modifier is T; - const iconOverflowIndex = 24; export const modifierSortFunc = (a: Modifier, b: Modifier): number => { @@ -3253,10 +3250,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } const transferredModifierTypes: ModifierType[] = []; - const itemModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, - targetPokemon.isPlayer(), - ) as PokemonHeldItemModifier[]; + const itemModifiers = targetPokemon.getHeldItems(); for (let i = 0; i < transferredItemCount; i++) { if (!itemModifiers.length) { diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index b4bb28fe55e..1409892d30a 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -82,8 +82,9 @@ export class BattleEndPhase extends BattlePhase { } const lapsingModifiers = globalScene.findModifiers( - m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier, - ) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + (m): m is LapsingPersistentModifier | LapsingPokemonHeldItemModifier => + m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier, + ); for (const m of lapsingModifiers) { const args: any[] = []; if (m instanceof LapsingPokemonHeldItemModifier) { diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index a7736b16811..7669cb29c4d 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -40,7 +40,7 @@ export class SelectBiomePhase extends BattlePhase { .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) .map(b => (!Array.isArray(b) ? b : b[0])); - if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { + if (biomes.length > 1 && globalScene.hasModifier(m => m instanceof MapModifier)) { const biomeSelectItems = biomes.map(b => { const ret: OptionSelectItem = { label: getBiomeName(b), diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5f11441333b..a0e7c96e094 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -15,12 +15,7 @@ import { getPlayerModifierTypeOptions, } from "#app/modifier/modifier-type"; import type { Modifier } from "#app/modifier/modifier"; -import { - ExtraModifierModifier, - HealShopCostModifier, - PokemonHeldItemModifier, - TempExtraModifierModifier, -} from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; @@ -150,12 +145,7 @@ export class SelectModifierPhase extends BattlePhase { fromSlotIndex !== toSlotIndex && itemIndex > -1 ) { - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; + const itemModifiers = party[toSlotIndex].getHeldItems(true); const itemModifier = itemModifiers[itemIndex]; globalScene.tryTransferHeldItemModifier( itemModifier, diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 6bdbb66be14..35f655333ab 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -138,7 +138,6 @@ export class SwitchSummonPhase extends SummonPhase { return; } - if (this.switchType === SwitchType.BATON_PASS) { // If switching via baton pass, update opposing tags coming from the prior pokemon (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) => @@ -147,17 +146,17 @@ export class SwitchSummonPhase extends SummonPhase { // If the recipient pokemon lacks a baton, give our baton to it during the swap if ( - !globalScene.findModifier( + !globalScene.hasModifier( m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id, ) ) { const batonPassModifier = globalScene.findModifier( - m => + (m): m is SwitchEffectTransferModifier => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id, - ) as SwitchEffectTransferModifier; + ); if (batonPassModifier) { globalScene.tryTransferHeldItemModifier( diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 452ffcf5192..8503aacc2b8 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -225,8 +225,9 @@ export default class PartyUiHandler extends MessageUiHandler { public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier), - ) as PokemonHeldItemModifier; + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier), + ); if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } @@ -552,11 +553,11 @@ export default class PartyUiHandler extends MessageUiHandler { // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon `p` // this returns `undefined` if the new pokemon doesn't have the item at all, otherwise it returns the `pokemonHeldItemModifier` for that item const matchingModifier = globalScene.findModifier( - m => + (m): m is PokemonHeldItemModifier => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), - ) as PokemonHeldItemModifier; + ); const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer @@ -1161,9 +1162,9 @@ export default class PartyUiHandler extends MessageUiHandler { } private allowBatonModifierSwitch(): boolean { - return !!( + return ( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && - globalScene.findModifier( + globalScene.hasModifier( m => m instanceof SwitchEffectTransferModifier && m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, ) diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 5a6ddf35459..17fea873c1c 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -19,7 +19,9 @@ describe("Abilities - Harvest", () => { let game: GameManager; const getPlayerBerries = () => - game.scene.getModifiers(BerryModifier, true).filter(b => b.pokemonId === game.scene.getPlayerPokemon()?.id); + game.scene.findModifiers( + (b): b is BerryModifier => b instanceof BerryModifier && b.pokemonId === game.scene.getPlayerPokemon()!.id, + ); /** Check whether the player's Modifiers contains the specified berries and nothing else. */ function expectBerriesContaining(...berries: ModifierOverride[]): void { diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index f60adebac36..68e53cdf33b 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -64,8 +64,8 @@ describe("Items - Dire Hit", () => { await game.phaseInterceptor.to(BattleEndPhase); - const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier; - expect(modifier.getBattleCount()).toBe(4); + const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier); + expect(modifier?.getBattleCount()).toBe(4); // Forced DIRE_HIT to spawn in the first slot with override game.onNextPrompt( diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index f13f6e0b072..326689e0b44 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -157,9 +157,10 @@ describe("Absolute Avarice - Mystery Encounter", () => { for (const partyPokemon of scene.getPlayerParty()) { const pokemonId = partyPokemon.id; const pokemonItems = scene.findModifiers( - m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, true, - ) as PokemonHeldItemModifier[]; + ); const revSeed = pokemonItems.find( i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), );