From e582f561bbbab3e5665e896ca829ba92be7740df Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 19 Nov 2024 23:10:59 -0500 Subject: [PATCH] refactored code as requested pls tell me this isn't too much tech debt now (also RIP unnecessarily convoluted cube roots, you will be missed) --- src/data/move.ts | 25 +++++++++++++++++++++-- src/field/pokemon.ts | 34 +++++++------------------------ src/test/items/multi_lens.test.ts | 16 +++++++-------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 2ac4d74b712..aa3823eea71 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -9,7 +9,7 @@ import { Constructor, NumberHolder } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; -import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ChangeMovePriorityAbAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; +import { AddSecondStrikeAbAttr, allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ChangeMovePriorityAbAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; import { BattlerIndex, BattleType } from "../battle"; import { TerrainType } from "./terrain"; @@ -1385,12 +1385,33 @@ export class UserHpDamageAttr extends FixedDamageAttr { } export class TargetHalfHpDamageAttr extends FixedDamageAttr { + private targetHp: number; // the final amount of hp we want the target to have + private hpAfterFirstAttack: number | null; constructor() { super(0); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(target.hp / 2); + // first, determine if the hit is coming from multi lens or not + const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; + if (lensCount <= 0) { + // no multi lenses; we can just halve the target's hp and call it a day + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + return true; + } + + // Figure out what hit # we're on + if (user.turnData.hitCount === user.turnData.hitsLeft) { + // first hit of move; apply damage as normal and update targetHp accordingly + this.targetHp = target.hp * (0.5 ** (user.hasAbilityWithAttr(AddSecondStrikeAbAttr) ? 2 : 1)); + this.hpAfterFirstAttack = null; + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + } else { + // all subsequent hits split the damage evenly among themselves + this.hpAfterFirstAttack ??= Utils.toDmgValue(target.hp); // nullish coalescing assignment go brrr + const totalHits = user.turnData.hitCount - 1; + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((this.hpAfterFirstAttack - this.targetHp) / totalHits); + } return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 99de1e949ac..da7b0308b86 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2622,34 +2622,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fixedDamage = new Utils.NumberHolder(0); applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { - const lensCount = source.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; - // Apply damage fixing for hp cutting moves on multi lens hits (NOT PARENTAL BOND) - if (lensCount > 0 - && move.hasAttr(TargetHalfHpDamageAttr) - && (source.turnData.hitCount === source.turnData.hitsLeft - || source.turnData.hitCount - source.turnData.hitsLeft !== lensCount + 1)) { - // Do some unholy math to make the moves' damage values add up to 50% - // Values obtained courtesy of WolframAlpha and Desmos Graphing Calculator - // (https://www.desmos.com/calculator/wdngrksdfz) - let damageMulti = 1; - // NOTE: If multi lens ever gets updated (again) this switch case will NEED to be updated alongside it! - switch (lensCount) { - case 1: - damageMulti = 0.558481559888; - break; - case 2: - damageMulti = 0.60875846088; - break; - default: - damageMulti = 0.5; - break; - } - - fixedDamage.value = this.hp * damageMulti; + // Don't apply the multi lens damage penalty for subsequent hits of a hp cutting move + // those get handled separately in move.ts + if (!(move.hasAttr(TargetHalfHpDamageAttr) + && source.turnData.hitCount !== source.turnData.hitsLeft)) { + const multiLensMultiplier = new Utils.NumberHolder(1); + source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier); + fixedDamage.value = Utils.toDmgValue(fixedDamage.value * multiLensMultiplier.value); } - const multiLensMultiplier = new Utils.NumberHolder(1); - source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier); - fixedDamage.value = Utils.toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { cancelled: false, diff --git a/src/test/items/multi_lens.test.ts b/src/test/items/multi_lens.test.ts index 5e68d6f2450..f4b4c5712ee 100644 --- a/src/test/items/multi_lens.test.ts +++ b/src/test/items/multi_lens.test.ts @@ -140,7 +140,7 @@ describe("Items - Multi Lens", () => { game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]) .moveset(Moves.SUPER_FANG) .ability(Abilities.COMPOUND_EYES) - .enemyLevel(100000) + .enemyLevel(1000) .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy await game.classicMode.startBattle([ Species.MAGIKARP ]); @@ -150,7 +150,7 @@ describe("Items - Multi Lens", () => { game.move.select(Moves.SUPER_FANG); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 10); // unrealistically high level of precision + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); }); it("should result in correct damage for hp% attacks with 2 lenses", async () => { @@ -158,7 +158,7 @@ describe("Items - Multi Lens", () => { .moveset(Moves.SUPER_FANG) .ability(Abilities.COMPOUND_EYES) .enemyMoveset(Moves.SPLASH) - .enemyLevel(100000) + .enemyLevel(1000) .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy await game.classicMode.startBattle([ Species.MAGIKARP ]); @@ -168,15 +168,15 @@ describe("Items - Multi Lens", () => { game.move.select(Moves.SUPER_FANG); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 8); // unrealistically high level of precision + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); }); it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }, - { name: "WIDE_LENS", count: 2 }]) // ensures move always hits + game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) .moveset(Moves.SUPER_FANG) .ability(Abilities.PARENTAL_BOND) + .passiveAbility(Abilities.COMPOUND_EYES) .enemyMoveset(Moves.SPLASH) - .enemyLevel(100000) + .enemyLevel(1000) .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy await game.classicMode.startBattle([ Species.MAGIKARP ]); @@ -186,6 +186,6 @@ describe("Items - Multi Lens", () => { game.move.select(Moves.SUPER_FANG); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 8); + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5); }); });