From 0b32cfb622775486b8215adf7f1c4e909d691459 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:29:14 +0200 Subject: [PATCH] Added multi lens and wide lens --- src/data/moves/move.ts | 4 +- src/field/pokemon.ts | 27 ++--- src/items/all-held-items.ts | 6 + src/items/held-item.ts | 2 + src/items/held-items/accuracy-booster.ts | 47 ++++++++ src/items/held-items/multi-hit.ts | 84 ++++++++++++++ src/modifier/modifier.ts | 136 ----------------------- src/phases/move-effect-phase.ts | 3 +- 8 files changed, 151 insertions(+), 158 deletions(-) create mode 100644 src/items/held-items/accuracy-booster.ts create mode 100644 src/items/held-items/multi-hit.ts diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7faf1778367..8bcfb7c0a2c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -68,10 +68,8 @@ import { } from "../abilities/ability"; import { allAbilities, allMoves } from "../data-lists"; import { - AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, - PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier, } from "../../modifier/modifier"; @@ -781,7 +779,7 @@ export default class Move implements Localizable { const isOhko = this.hasAttr(OneHitKOAccuracyAttr); if (!isOhko) { - globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + applyHeldItems(ITEM_EFFECT.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy }); } if (globalScene.arena.weather?.weatherType === WeatherType.FOG) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d684f85e7e6..b32a30b7884 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -96,7 +96,6 @@ import { PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, - PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; @@ -3725,14 +3724,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiLensMultiplier, - ); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiLensMultiplier, + }); fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { @@ -3776,14 +3772,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ const multiStrikeEnhancementMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiStrikeEnhancementMultiplier, - ); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiStrikeEnhancementMultiplier, + }); if (!ignoreSourceAbility) { applyPreAttackAbAttrs( AddSecondStrikeAbAttr, diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index e06c3778cd8..5d8a234213c 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -6,6 +6,7 @@ import { SpeciesId } from "#enums/species-id"; import { Stat, type PermanentStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { ITEM_EFFECT } from "./held-item"; +import { type ACCURACY_BOOST_PARAMS, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; import { type ATTACK_TYPE_BOOST_PARAMS, AttackTypeBoosterHeldItem, @@ -25,6 +26,7 @@ import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/fl import { type FRIENDSHIP_BOOST_PARAMS, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; +import { type MULTI_HIT_PARAMS, MultiHitHeldItem } from "./held-items/multi-hit"; import { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; import { ResetNegativeStatStageHeldItem, @@ -115,6 +117,8 @@ export function initHeldItems() { allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2); allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10); + allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5); + allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -144,6 +148,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; [ITEM_EFFECT.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; [ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; + [ITEM_EFFECT.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; + [ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_PARAMS; }; export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { diff --git a/src/items/held-item.ts b/src/items/held-item.ts index f8afc9553f3..5c4b39a6b67 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -23,6 +23,8 @@ export const ITEM_EFFECT = { FIELD_EFFECT: 15, FRIENDSHIP_BOOSTER: 16, NATURE_WEIGHT_BOOSTER: 17, + ACCURACY_BOOSTER: 18, + MULTI_HIT: 19, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts new file mode 100644 index 00000000000..90325a18bc8 --- /dev/null +++ b/src/items/held-items/accuracy-booster.ts @@ -0,0 +1,47 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface ACCURACY_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + moveAccuracy: NumberHolder; +} + +export class AccuracyBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.ACCURACY_BOOSTER]; + + private accuracyAmount: number; + + constructor(type: HeldItemId, maxStackCount = 1, accuracy: number) { + super(type, maxStackCount); + this.accuracyAmount = accuracy; + } + + /** + * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + * @param pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { + // return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; + // } + + /** + * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns always `true` + */ + apply(params: ACCURACY_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const moveAccuracy = params.moveAccuracy; + const stackCount = pokemon.heldItemManager.getStack(this.type); + moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * stackCount; + + return true; + } +} diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts new file mode 100644 index 00000000000..8bb48e190c4 --- /dev/null +++ b/src/items/held-items/multi-hit.ts @@ -0,0 +1,84 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; +import type { MoveId } from "#enums/move-id"; +import { allMoves } from "#app/data/data-lists"; + +export interface MULTI_HIT_PARAMS { + pokemon: Pokemon; + moveId: MoveId; + count?: NumberHolder; + damageMultiplier?: NumberHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class MultiHitHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.MULTI_HIT]; + + /** + * For each stack, converts 25 percent of attack damage into an additional strike. + * @param pokemon The {@linkcode Pokemon} using the move + * @param moveId The {@linkcode MoveId | identifier} for the move being used + * @param count {@linkcode NumberHolder} holding the move's hit count for this turn + * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move + * @returns always `true` + */ + apply(params: MULTI_HIT_PARAMS): boolean { + const pokemon = params.pokemon; + const move = allMoves[params.moveId]; + /** + * The move must meet Parental Bond's restrictions for this item + * to apply. This means + * - Only attacks are boosted + * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted + * (though Multi-Lens can still affect moves boosted by Parental Bond) + * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon + * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted + */ + if (!move.canBeMultiStrikeEnhanced(pokemon)) { + return false; + } + + if (!isNullOrUndefined(params.count)) { + return this.applyHitCountBoost(pokemon, params.count); + } + if (!isNullOrUndefined(params.damageMultiplier)) { + return this.applyDamageModifier(pokemon, params.damageMultiplier); + } + + return false; + } + + /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ + private applyHitCountBoost(pokemon: Pokemon, count: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + count.value += stackCount; + return true; + } + + /** + * If applied to the first hit of a move, sets the damage multiplier + * equal to (1 - the number of stacked Multi-Lenses). + * Additional strikes beyond that are given a 0.25x damage multiplier + */ + private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { + // Reduce first hit by 25% for each stack count + damageMultiplier.value *= 1 - 0.25 * stackCount; + return true; + } + if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== stackCount + 1) { + // Deal 25% damage for each remaining Multi Lens hit + damageMultiplier.value *= 0.25; + return true; + } + // An extra hit not caused by Multi Lens -- assume it is Parental Bond + return false; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 630bd0d441a..c51d6a99e2c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,7 +1,6 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; @@ -17,7 +16,6 @@ import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; -import type { MoveId } from "#enums/move-id"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; @@ -32,8 +30,6 @@ import { type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, - type PokemonMoveAccuracyBoosterModifierType, - type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, @@ -1692,138 +1688,6 @@ export class ExpBalanceModifier extends PersistentModifier { } } -export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonMoveAccuracyBoosterModifierType; - private accuracyAmount: number; - - constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.accuracyAmount = accuracy; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof PokemonMoveAccuracyBoosterModifier) { - const pokemonAccuracyBoosterModifier = modifier as PokemonMoveAccuracyBoosterModifier; - return pokemonAccuracyBoosterModifier.accuracyAmount === this.accuracyAmount; - } - return false; - } - - clone(): PersistentModifier { - return new PokemonMoveAccuracyBoosterModifier(this.type, this.pokemonId, this.accuracyAmount, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.accuracyAmount); - } - - /** - * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied - * @param pokemon The {@linkcode Pokemon} to apply the move accuracy boost to - * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost - * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { - return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; - } - - /** - * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} - * @param _pokemon The {@linkcode Pokemon} to apply the move accuracy boost to - * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost - * @returns always `true` - */ - override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { - moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount(); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class PokemonMultiHitModifier extends PokemonHeldItemModifier { - public override type: PokemonMultiHitModifierType; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonMultiHitModifier; - } - - clone(): PersistentModifier { - return new PokemonMultiHitModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * For each stack, converts 25 percent of attack damage into an additional strike. - * @param pokemon The {@linkcode Pokemon} using the move - * @param moveId The {@linkcode MoveId | identifier} for the move being used - * @param count {@linkcode NumberHolder} holding the move's hit count for this turn - * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move - * @returns always `true` - */ - override apply( - pokemon: Pokemon, - moveId: MoveId, - count: NumberHolder | null = null, - damageMultiplier: NumberHolder | null = null, - ): boolean { - const move = allMoves[moveId]; - /** - * The move must meet Parental Bond's restrictions for this item - * to apply. This means - * - Only attacks are boosted - * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted - * (though Multi-Lens can still affect moves boosted by Parental Bond) - * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon - * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted - */ - if (!move.canBeMultiStrikeEnhanced(pokemon)) { - return false; - } - - if (!isNullOrUndefined(count)) { - return this.applyHitCountBoost(count); - } - if (!isNullOrUndefined(damageMultiplier)) { - return this.applyDamageModifier(pokemon, damageMultiplier); - } - - return false; - } - - /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ - private applyHitCountBoost(count: NumberHolder): boolean { - count.value += this.getStackCount(); - return true; - } - - /** - * If applied to the first hit of a move, sets the damage multiplier - * equal to (1 - the number of stacked Multi-Lenses). - * Additional strikes beyond that are given a 0.25x damage multiplier - */ - private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { - if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { - // Reduce first hit by 25% for each stack count - damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); - return true; - } - if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { - // Deal 25% damage for each remaining Multi Lens hit - damageMultiplier.value *= 0.25; - return true; - } - // An extra hit not caused by Multi Lens -- assume it is Parental Bond - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { public override type: FormChangeItemModifierType; public formChangeItem: FormChangeItem; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 69e96383a57..2563b8d85ee 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -57,7 +57,6 @@ import { DamageMoneyRewardModifier, EnemyAttackStatusEffectChanceModifier, EnemyEndureChanceModifier, - PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils/common"; @@ -322,7 +321,7 @@ export class MoveEffectPhase extends PokemonPhase { // If Parental Bond is applicable, add another hit applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value;