From 1063184f70d60879728d63c47f7332585efdd854 Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 03:54:13 +0800 Subject: [PATCH 01/14] refactor power calc, fix battery & power spot --- src/data/move.ts | 75 ++++++++++++++++++++++-- src/field/pokemon.ts | 61 +++---------------- src/test/abilities/aura_break.test.ts | 59 +++++-------------- src/test/abilities/battery.test.ts | 49 +++------------- src/test/abilities/power_spot.test.ts | 46 +++------------ src/test/abilities/steely_spirit.test.ts | 53 ++++++----------- src/test/moves/hard_press.test.ts | 23 +------- 7 files changed, 130 insertions(+), 236 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 2e0f39278bc9..5b60be08b736 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,7 +1,7 @@ import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { EncoreTag, SemiInvulnerableTag } from "./battler-tags"; +import { EncoreTag, HelpingHandTag, SemiInvulnerableTag, TypeBoostTag } from "./battler-tags"; import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect"; @@ -9,10 +9,10 @@ import { Type } from "./type"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "./weather"; -import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; -import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr } from "./ability"; +import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; +import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability"; import { allAbilities } from "./ability"; -import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier"; +import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier"; import { BattlerIndex } from "../battle"; import { Stat } from "./pokemon-stat"; import { TerrainType } from "./terrain"; @@ -655,6 +655,73 @@ export default class Move implements Localizable { return score; } + + /** + * Calculates the power of a move based on various conditions and attributes. + * + * @param source {@linkcode Pokemon} The Pokémon using the move. + * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. + * @returns The calculated power of the move. + */ + calculatePower(source: Pokemon, target: Pokemon): number { + const power = new Utils.NumberHolder(this.power); + + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, typeChangeMovePowerMultiplier); + + const sourceTeraType = source.getTeraType(); + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + power.value = 60; + } + + applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, power); + + if (source.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, power); + } + + const fieldAuras = new Set( + source.scene.getField(true) + .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) + .flat(), + ); + for (const aura of fieldAuras) { + // The only relevant values are `move` and the `power` holder + aura.applyPreAttack(null, null, null, this, [power]); + } + + const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField(); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, power)); + + power.value *= typeChangeMovePowerMultiplier.value; + + const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === this.type) as TypeBoostTag; + if (typeBoost) { + power.value *= typeBoost.boostValue; + if (typeBoost.oneUse) { + source.removeTag(typeBoost.tagType); + } + } + + if (source.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && this.type === Type.GROUND && this.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { + power.value /= 2; + } + + applyMoveAttrs(VariablePowerAttr, source, target, this, power); + + source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); + + if (!this.hasAttr(TypelessAttr)) { + source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power); + source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); + } + + if (source.getTag(HelpingHandTag)) { + power.value *= 1.5; + } + + return power.value; + } } export class AttackMove extends Move { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 024d27f9e8f5..21799e2dae1d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,14 +3,14 @@ import BattleScene, { AnySound } from "../battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags, NeutralDamageAgainstFlyingTypeMultiplierAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags, NeutralDamageAgainstFlyingTypeMultiplierAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { getLevelTotalExp } from "../data/exp"; import { Stat } from "../data/pokemon-stat"; -import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier } from "../modifier/modifier"; +import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier } from "../modifier/modifier"; import { PokeballType } from "../data/pokeball"; import { Gender } from "../data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; @@ -19,11 +19,11 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases"; import { BattleStat } from "../data/battle-stat"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { TempBattleStat } from "../data/temp-battle-stat"; -import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AllyMoveCategoryPowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AddSecondStrikeAbAttr, UserFieldMoveTypePowerBoostAbAttr } from "../data/ability"; +import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag"; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr } from "../data/ability"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; import { Mode } from "../ui/ui"; @@ -1747,9 +1747,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); const moveCategory = variableCategory.value as MoveCategory; - const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); applyMoveAttrs(VariableMoveTypeAttr, source, this, move); - applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, move, typeChangeMovePowerMultiplier); const types = this.getTypes(true, true); const cancelled = new Utils.BooleanHolder(false); @@ -1778,31 +1776,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: const isPhysical = moveCategory === MoveCategory.PHYSICAL; - const power = new Utils.NumberHolder(move.power); + const power = move.calculatePower(source, this); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { - power.value = 60; - } - applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, move, power); - - if (source.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { - applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source, this, move, power); - } - - const fieldAuras = new Set( - this.scene.getField(true) - .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) - .flat(), - ); - for (const aura of fieldAuras) { - // The only relevant values are `move` and the `power` holder - aura.applyPreAttack(null, null, null, move, [power]); - } - - const alliedField: Pokemon[] = source instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField(); - alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, this, move, power)); - - power.value *= typeChangeMovePowerMultiplier.value; if (!typeless) { applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); @@ -1817,29 +1792,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.stopMultiHit(this); result = HitResult.NO_EFFECT; } else { - const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; - if (typeBoost) { - power.value *= typeBoost.boostValue; - if (typeBoost.oneUse) { - source.removeTag(typeBoost.tagType); - } - } const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded())); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); - if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { - power.value /= 2; - } - applyMoveAttrs(VariablePowerAttr, source, this, move, power); - - this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); - if (!typeless) { - this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); - this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power); - } - if (source.getTag(HelpingHandTag)) { - power.value *= 1.5; - } let isCritical: boolean; const critOnly = new Utils.BooleanHolder(false); const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT); @@ -1910,7 +1865,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!isTypeImmune) { damage.value = Math.ceil( - ((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) + ((((2 * source.level / 5 + 2) * power * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * ((this.scene.randBattleSeedInt(16) + 85) / 100) * criticalMultiplier.value); if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) { if (!move.hasAttr(BypassBurnDamageReductionAttr)) { @@ -1981,7 +1936,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, damage); - console.log("damage", damage.value, move.name, power.value, sourceAtk, targetDef); + console.log("damage", damage.value, move.name, power, sourceAtk, targetDef); // In case of fatal damage, this tag would have gotten cleared before we could lapse it. const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND); diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index bfd1fdf59fe2..768dda3a9bbe 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -7,15 +7,18 @@ import { MoveEffectPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; -import Move, { allMoves } from "#app/data/move.js"; -import Pokemon from "#app/field/pokemon.js"; -import { FieldMoveTypePowerBoostAbAttr } from "#app/data/ability.js"; -import { NumberHolder } from "#app/utils.js"; +import { allMoves } from "#app/data/move.js"; describe("Abilities - Aura Break", () => { let phaserGame: Phaser.Game; let game: GameManager; - const multiplier = 9 / 16; + const auraBreakMultiplier = 9 / 16; + const auraMultiplier = 4 / 3; + /** + * Apparently, the auraMultiplier is being multiplied first to the move's power then multiplied again to + * the auraBreakMultiplier. This means we can't net the multiplier like so: + * power * (auraMultiplier * auraBreakMultiplier). Doing so will make the result off by a decimal value. + */ beforeAll(() => { phaserGame = new Phaser.Game({ @@ -37,59 +40,29 @@ describe("Abilities - Aura Break", () => { it("reverses the effect of fairy aura", async () => { vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.FAIRY_AURA); - const basePower = allMoves[Moves.MOONBLAST].power; + const moveToCheck = allMoves[Moves.MOONBLAST]; + const basePower = moveToCheck.power; await game.startBattle([Species.MAGIKARP]); game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); - const appliedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[Moves.MOONBLAST]); - + const movePower = moveToCheck.calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); await game.phaseInterceptor.to(MoveEffectPhase); - expect(appliedPower).not.toBe(undefined); - expect(appliedPower).not.toBe(basePower); - expect(appliedPower).toBe(basePower * multiplier); - + expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); }); it("reverses the effect of dark aura", async () => { vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DARK_AURA); - const basePower = allMoves[Moves.DARK_PULSE].power; + const moveToCheck = allMoves[Moves.DARK_PULSE]; + const basePower = moveToCheck.power; await game.startBattle([Species.MAGIKARP]); game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); - const appliedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[Moves.DARK_PULSE]); - + const movePower = moveToCheck.calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); await game.phaseInterceptor.to(MoveEffectPhase); - expect(appliedPower).not.toBe(undefined); - expect(appliedPower).not.toBe(basePower); - expect(appliedPower).toBe(basePower * multiplier); + expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); }); }); - -/** - * Calculates the mocked power of a move in a Pokémon battle, taking into account certain abilities. - * - * @param defender - The defending Pokémon. - * @param attacker - The attacking Pokémon. - * @param move - The move being used in the attack. - * @returns The calculated power of the move after applying any relevant ability effects. - * - * @remarks - * This function creates a NumberHolder with the initial power of the move. - * It then checks if the defender has an ability with the FieldMoveTypePowerBoostAbAttr. - * If so, it applies a power modification of 9/16 using an instance of FieldMoveTypePowerBoostAbAttr. - * The final calculated power is then returned. - */ -const getMockedMovePower = (defender: Pokemon, attacker: Pokemon, move: Move): number => { - const powerHolder = new NumberHolder(move.power); - - if (defender.hasAbilityWithAttr(FieldMoveTypePowerBoostAbAttr)) { - const auraBreakInstance = new FieldMoveTypePowerBoostAbAttr(move.type, 9 / 16); - auraBreakInstance.applyPreAttack(attacker, false, defender, move, [powerHolder]); - } - - return powerHolder.value; -}; diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index 53a04732b748..34965c8095d1 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -5,10 +5,10 @@ import * as overrides from "#app/overrides"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; -import Move, { allMoves, MoveCategory } from "#app/data/move.js"; +import { allMoves } from "#app/data/move.js"; import { AllyMoveCategoryPowerBoostAbAttr } from "#app/data/ability.js"; -import { NumberHolder } from "#app/utils.js"; import Pokemon from "#app/field/pokemon.js"; +import { Abilities } from "#app/enums/abilities.js"; describe("Abilities - Battery", () => { let phaserGame: Phaser.Game; @@ -27,6 +27,7 @@ describe("Abilities - Battery", () => { beforeEach(() => { game = new GameManager(phaserGame); vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.ROCK_SLIDE, Moves.SPLASH, Moves.HEAT_WAVE]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); }); @@ -41,11 +42,9 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).not.toBe(basePower); - expect(mockedPower).toBe(basePower * multiplier); + expect(movePower).toBe(basePower * multiplier); }); it("does not raise the power of allies' non-special moves", async () => { @@ -57,12 +56,9 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).toBe(basePower); - expect(mockedPower).not.toBe(basePower * multiplier); + expect(movePower).toBe(basePower); }); it("does not raise the power of the ability owner's special moves", async () => { @@ -74,39 +70,12 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[0]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).toBe(basePower); - expect(mockedPower).not.toBe(basePower * multiplier); + expect(movePower).toBe(basePower); }); }); -/** - * Calculates the mocked power of a move. - * Note this does not consider other damage calculations - * except the power multiplier from Battery. - * - * @param defender - The defending Pokémon. - * @param attacker - The attacking Pokémon. - * @param move - The move being used by the attacker. - * @returns The adjusted power of the move. - */ -const getMockedMovePower = (defender: Pokemon, attacker: Pokemon, move: Move) => { - const powerHolder = new NumberHolder(move.power); - - /** - * @see AllyMoveCategoryPowerBoostAbAttr - */ - if (attacker.getAlly().hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { - const batteryInstance = new AllyMoveCategoryPowerBoostAbAttr([MoveCategory.SPECIAL], 1.3); - batteryInstance.applyPreAttack(attacker, false, defender, move, [ powerHolder ]); - } - - return powerHolder.value; -}; - /** * Retrieves the power multiplier from a Pokémon's ability attribute. * diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index 0ed2b10f4be0..ed0f706cb982 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -5,9 +5,8 @@ import * as overrides from "#app/overrides"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; -import Move, { allMoves, MoveCategory } from "#app/data/move.js"; +import { allMoves } from "#app/data/move.js"; import { AllyMoveCategoryPowerBoostAbAttr } from "#app/data/ability.js"; -import { NumberHolder } from "#app/utils.js"; import Pokemon from "#app/field/pokemon.js"; describe("Abilities - Power Spot", () => { @@ -41,11 +40,9 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).not.toBe(basePower); - expect(mockedPower).toBe(basePower * multiplier); + expect(movePower).toBe(basePower * multiplier); }); it("raises the power of allies' physical moves by 30%", async () => { @@ -58,11 +55,9 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).not.toBe(basePower); - expect(mockedPower).toBe(basePower * multiplier); + expect(movePower).toBe(basePower * multiplier); }); it("does not raise the power of the ability owner's moves", async () => { @@ -74,39 +69,12 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[0]); - const mockedPower = getMockedMovePower(game.scene.getEnemyField()[0], game.scene.getPlayerField()[0], allMoves[moveToBeUsed]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - expect(mockedPower).not.toBe(undefined); - expect(mockedPower).toBe(basePower); - expect(mockedPower).not.toBe(basePower * multiplier); + expect(movePower).toBe(basePower); }); }); -/** - * Calculates the mocked power of a move. - * Note this does not consider other damage calculations - * except the power multiplier from Power Spot. - * - * @param defender - The defending Pokémon. - * @param attacker - The attacking Pokémon. - * @param move - The move being used by the attacker. - * @returns The adjusted power of the move. - */ -const getMockedMovePower = (defender: Pokemon, attacker: Pokemon, move: Move) => { - const powerHolder = new NumberHolder(move.power); - - /** - * @see AllyMoveCategoryPowerBoostAbAttr - */ - if (attacker.getAlly().hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { - const powerSpotInstance = new AllyMoveCategoryPowerBoostAbAttr([MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3); - powerSpotInstance.applyPreAttack(attacker, false, defender, move, [ powerHolder ]); - } - - return powerHolder.value; -}; - /** * Retrieves the power multiplier from a Pokémon's ability attribute. * diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index e9c673a102af..4cde63ddf3cb 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -5,11 +5,10 @@ import * as overrides from "#app/overrides"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon.js"; -import Move, { allMoves } from "#app/data/move.js"; -import { NumberHolder } from "#app/utils.js"; -import { allAbilities, applyPreAttackAbAttrs, UserFieldMoveTypePowerBoostAbAttr } from "#app/data/ability.js"; +import { allMoves } from "#app/data/move.js"; +import { allAbilities } from "#app/data/ability.js"; import { Abilities } from "#app/enums/abilities.js"; +import { SelectTargetPhase } from "#app/phases.js"; describe("Abilities - Steely Spirit", () => { let phaserGame: Phaser.Game; @@ -38,21 +37,25 @@ describe("Abilities - Steely Spirit", () => { it("increases Steel-type moves used by the user and its allies", async () => { await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); const perserrker = game.scene.getPlayerField()[1]; + const enemyToCheck = game.scene.getEnemyField()[0]; vi.spyOn(perserrker, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); expect(perserrker.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); + await game.phaseInterceptor.to(SelectTargetPhase, false); + game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const mockedMovePower = getMockedMovePower(game.scene.getEnemyPokemon(), perserrker, allMoves[moveToCheck]); + const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); - expect(mockedMovePower).toBe(allMoves[moveToCheck].power * steelySpiritMultiplier); + expect(movePower).toBe(allMoves[moveToCheck].power * steelySpiritMultiplier); }); it("stacks if multiple users with this ability are on the field.", async () => { await game.startBattle([Species.PERRSERKER, Species.PERRSERKER]); + const enemyToCheck = game.scene.getEnemyField()[0]; game.scene.getPlayerField().forEach(p => { vi.spyOn(p, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); @@ -61,16 +64,19 @@ describe("Abilities - Steely Spirit", () => { expect(game.scene.getPlayerField().every(p => p.hasAbility(Abilities.STEELY_SPIRIT))).toBe(true); game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); + await game.phaseInterceptor.to(SelectTargetPhase, false); + game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const mockedMovePower = getMockedMovePower(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[moveToCheck]); + const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); - expect(mockedMovePower).toBe(allMoves[moveToCheck].power * Math.pow(steelySpiritMultiplier, 2)); + expect(movePower).toBe(allMoves[moveToCheck].power * Math.pow(steelySpiritMultiplier, 2)); }); it("does not take effect when suppressed", async () => { await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); const perserrker = game.scene.getPlayerField()[1]; + const enemyToCheck = game.scene.getEnemyField()[0]; vi.spyOn(perserrker, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); expect(perserrker.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); @@ -81,35 +87,12 @@ describe("Abilities - Steely Spirit", () => { expect(perserrker.summonData.abilitySuppressed).toBe(true); game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); + await game.phaseInterceptor.to(SelectTargetPhase, false); + game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const mockedMovePower = getMockedMovePower(game.scene.getEnemyPokemon(), perserrker, allMoves[moveToCheck]); + const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); - expect(mockedMovePower).toBe(allMoves[moveToCheck].power); + expect(movePower).toBe(allMoves[moveToCheck].power); }); }); - -/** - * Calculates the mocked power of a move. - * Note this does not consider other damage calculations - * except the power multiplier from Steely Spirit. - * - * @param defender - The defending Pokémon. - * @param attacker - The attacking Pokémon. - * @param move - The move being used by the attacker. - * @returns The adjusted power of the move. - */ -const getMockedMovePower = (defender: Pokemon, attacker: Pokemon, move: Move) => { - const powerHolder = new NumberHolder(move.power); - - /** - * Check if pokemon has the specified ability and is in effect. - * See Pokemon.hasAbility {@linkcode Pokemon.hasAbility} - */ - if (attacker.hasAbility(Abilities.STEELY_SPIRIT)) { - const alliedField: Pokemon[] = attacker instanceof PlayerPokemon ? attacker.scene.getPlayerField() : attacker.scene.getEnemyField(); - alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, this, move, powerHolder)); - } - - return powerHolder.value; -}; diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index c6f071a699f0..cbf882997533 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -9,9 +9,7 @@ import { import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; -import { NumberHolder } from "#app/utils.js"; import Move from "#app/data/move.js"; -import Pokemon from "#app/field/pokemon.js"; import { allMoves, OpponentHighHpPowerAttr } from "#app/data/move.js"; describe("Moves - Hard Press", () => { @@ -44,32 +42,13 @@ describe("Moves - Hard Press", () => { await game.phaseInterceptor.to(MoveEffectPhase); const enemy = game.scene.getEnemyPokemon(); - const movePower = getMockedMovePower(enemy, game.scene.getPlayerPokemon(), moveToBeUsed); + const movePower = moveToBeUsed.calculatePower(game.scene.getPlayerPokemon(), enemy); const moveMaxBasePower = getMoveMaxBasePower(moveToBeUsed); expect(movePower).toBe(moveMaxBasePower * enemy.getHpRatio()); }); }); -/** - * Calculates the mocked move power based on the attributes of the move and the opponent's high HP. - * - * @param defender - The defending Pokémon. - * @param attacker - The attacking Pokémon. - * @param move - The move being used. - * @returns The calculated move power. - */ -const getMockedMovePower = (defender: Pokemon, attacker: Pokemon, move: Move) => { - const powerHolder = new NumberHolder(move.power); - - if (move.hasAttr(OpponentHighHpPowerAttr)) { - const attr = move.getAttrs(OpponentHighHpPowerAttr); - attr[0].apply(attacker, defender, move, [ powerHolder ]); - } - - return powerHolder.value; -}; - /** * Retrieves the maximum base power of a move based on its attributes. * From b8e56a778d2b2ae018ef1f3b918e50d62f69adda Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 04:05:58 +0800 Subject: [PATCH 02/14] fix hard press unit test --- src/test/moves/hard_press.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index cbf882997533..54d24bf6daba 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -38,7 +38,7 @@ describe("Moves - Hard Press", () => { await game.startBattle([Species.GRAVELER]); const moveToBeUsed = allMoves[Moves.HARD_PRESS]; - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); + game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(MoveEffectPhase); const enemy = game.scene.getEnemyPokemon(); From fee77226cd42b9e0be82c29d4589a272d0dde446 Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 04:53:14 +0800 Subject: [PATCH 03/14] fix hard press --- src/test/moves/hard_press.test.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index 54d24bf6daba..3e7a0886ece0 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -29,23 +29,27 @@ describe("Moves - Hard Press", () => { beforeEach(() => { game = new GameManager(phaserGame); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); - vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX); - vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MUNCHLAX); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.HARD_PRESS]); }); it("power varies between 1 and 100, and is greater the more HP the target has", async () => { - await game.startBattle([Species.GRAVELER]); - const moveToBeUsed = allMoves[Moves.HARD_PRESS]; + await game.startBattle([Species.PIKACHU]); + const moveToCheck = allMoves[Moves.HARD_PRESS]; + const enemy = game.scene.getEnemyPokemon(); + const ally = game.scene.getPlayerPokemon(); + const moveMaxBasePower = getMoveMaxBasePower(moveToCheck); + + const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); + expect(fullHpMovePower).toBe(100); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(MoveEffectPhase); - const enemy = game.scene.getEnemyPokemon(); - const movePower = moveToBeUsed.calculatePower(game.scene.getPlayerPokemon(), enemy); - const moveMaxBasePower = getMoveMaxBasePower(moveToBeUsed); - - expect(movePower).toBe(moveMaxBasePower * enemy.getHpRatio()); + const reducedHpMovePower = moveToCheck.calculatePower(ally, enemy); + expect(reducedHpMovePower).toBe(Math.max(Math.floor(moveMaxBasePower * enemy.getHpRatio()), 1) ); }); }); From 99b881fe429afb2016ae8a55835ac21294fb5fcf Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 12:23:53 +0800 Subject: [PATCH 04/14] refactor tests --- src/test/abilities/battery.test.ts | 9 ++++---- src/test/abilities/power_spot.test.ts | 7 +++--- src/test/abilities/steely_spirit.test.ts | 28 ++++++++++++------------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index 34965c8095d1..b1f1f6129941 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -41,8 +41,9 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const [boostedAlly, boostSource] = game.scene.getPlayerField(); + const multiplier = getAttrPowerMultiplier(boostSource); + const movePower = allMoves[moveToBeUsed].calculatePower(boostedAlly, game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower * multiplier); }); @@ -56,7 +57,7 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); @@ -70,7 +71,7 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index ed0f706cb982..60701b9a38b4 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -54,8 +54,9 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const [boostedAlly, boostSource] = game.scene.getPlayerField(); + const multiplier = getAttrPowerMultiplier(boostSource); + const movePower = allMoves[moveToBeUsed].calculatePower(boostedAlly, game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower * multiplier); }); @@ -69,7 +70,7 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 4cde63ddf3cb..9c3d055578fa 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -36,26 +36,26 @@ describe("Abilities - Steely Spirit", () => { it("increases Steel-type moves used by the user and its allies", async () => { await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); - const perserrker = game.scene.getPlayerField()[1]; + const [boostedAlly, boostSource] = game.scene.getPlayerField(); const enemyToCheck = game.scene.getEnemyField()[0]; - vi.spyOn(perserrker, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); + vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); - expect(perserrker.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); + expect(boostSource.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); + const movePower = allMoves[moveToCheck].calculatePower(boostedAlly, enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power * steelySpiritMultiplier); }); it("stacks if multiple users with this ability are on the field.", async () => { await game.startBattle([Species.PERRSERKER, Species.PERRSERKER]); - const enemyToCheck = game.scene.getEnemyField()[0]; + const enemyToCheck = game.scene.getEnemyPokemon(); game.scene.getPlayerField().forEach(p => { vi.spyOn(p, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); @@ -68,30 +68,30 @@ describe("Abilities - Steely Spirit", () => { game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); + const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerPokemon(), enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power * Math.pow(steelySpiritMultiplier, 2)); }); it("does not take effect when suppressed", async () => { await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); - const perserrker = game.scene.getPlayerField()[1]; - const enemyToCheck = game.scene.getEnemyField()[0]; + const [boostedAlly, boostSource] = game.scene.getPlayerField(); + const enemyToCheck = game.scene.getEnemyPokemon(); - vi.spyOn(perserrker, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); - expect(perserrker.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); + vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); + expect(boostSource.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); - perserrker.summonData.abilitySuppressed = true; + boostSource.summonData.abilitySuppressed = true; - expect(perserrker.hasAbility(Abilities.STEELY_SPIRIT)).toBe(false); - expect(perserrker.summonData.abilitySuppressed).toBe(true); + expect(boostSource.hasAbility(Abilities.STEELY_SPIRIT)).toBe(false); + expect(boostSource.summonData.abilitySuppressed).toBe(true); game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerField()[0], enemyToCheck); + const movePower = allMoves[moveToCheck].calculatePower(boostedAlly, enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power); }); From bba66048f262beed667c0ef861ab68696c219b0d Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 13:48:31 +0800 Subject: [PATCH 05/14] use sypOn hp instead --- src/test/moves/hard_press.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index 547989f0b998..df864607ea44 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -58,7 +58,7 @@ describe("Moves - Hard Press", () => { const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); - enemy.hp /= 2; + vi.spyOn(enemy, "getHpRatio").mockReturnValue(.5); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(TurnStartPhase); @@ -75,7 +75,7 @@ describe("Moves - Hard Press", () => { const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); - enemy.hp = 1; + vi.spyOn(enemy, "getHpRatio").mockReturnValue(.1); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(TurnStartPhase); From 45296413014368235779e8069ea0a2f4f9b8161e Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 14:07:46 +0800 Subject: [PATCH 06/14] rename method --- src/data/move.ts | 4 ++-- src/field/pokemon.ts | 2 +- src/test/abilities/aura_break.test.ts | 4 ++-- src/test/abilities/battery.test.ts | 6 +++--- src/test/abilities/power_spot.test.ts | 6 +++--- src/test/abilities/steely_spirit.test.ts | 6 +++--- src/test/moves/hard_press.test.ts | 10 +++++----- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 331ee01d7641..ac8051a0920f 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -657,13 +657,13 @@ export default class Move implements Localizable { } /** - * Calculates the power of a move based on various conditions and attributes. + * Calculates the power of a move in battle based on various conditions and attributes. * * @param source {@linkcode Pokemon} The Pokémon using the move. * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. * @returns The calculated power of the move. */ - calculatePower(source: Pokemon, target: Pokemon): number { + calculateBattlePower(source: Pokemon, target: Pokemon): number { const power = new Utils.NumberHolder(this.power); const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 21799e2dae1d..51bdc3131bd8 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1776,7 +1776,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: const isPhysical = moveCategory === MoveCategory.PHYSICAL; - const power = move.calculatePower(source, this); + const power = move.calculateBattlePower(source, this); const sourceTeraType = source.getTeraType(); if (!typeless) { diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index 768dda3a9bbe..2020d73b8e9a 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -46,7 +46,7 @@ describe("Abilities - Aura Break", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); - const movePower = moveToCheck.calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + const movePower = moveToCheck.calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); await game.phaseInterceptor.to(MoveEffectPhase); expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); @@ -60,7 +60,7 @@ describe("Abilities - Aura Break", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); - const movePower = moveToCheck.calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + const movePower = moveToCheck.calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); await game.phaseInterceptor.to(MoveEffectPhase); expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index b1f1f6129941..6561a92a1c91 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -43,7 +43,7 @@ describe("Abilities - Battery", () => { const [boostedAlly, boostSource] = game.scene.getPlayerField(); const multiplier = getAttrPowerMultiplier(boostSource); - const movePower = allMoves[moveToBeUsed].calculatePower(boostedAlly, game.scene.getEnemyPokemon()); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(boostedAlly, game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower * multiplier); }); @@ -57,7 +57,7 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); @@ -71,7 +71,7 @@ describe("Abilities - Battery", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index 60701b9a38b4..2b584b6e9c01 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -40,7 +40,7 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); expect(movePower).toBe(basePower * multiplier); }); @@ -56,7 +56,7 @@ describe("Abilities - Power Spot", () => { const [boostedAlly, boostSource] = game.scene.getPlayerField(); const multiplier = getAttrPowerMultiplier(boostSource); - const movePower = allMoves[moveToBeUsed].calculatePower(boostedAlly, game.scene.getEnemyPokemon()); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(boostedAlly, game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower * multiplier); }); @@ -70,7 +70,7 @@ describe("Abilities - Power Spot", () => { game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToBeUsed].calculatePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); expect(movePower).toBe(basePower); }); diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 9c3d055578fa..430f1a863574 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -48,7 +48,7 @@ describe("Abilities - Steely Spirit", () => { game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(boostedAlly, enemyToCheck); + const movePower = allMoves[moveToCheck].calculateBattlePower(boostedAlly, enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power * steelySpiritMultiplier); }); @@ -68,7 +68,7 @@ describe("Abilities - Steely Spirit", () => { game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(game.scene.getPlayerPokemon(), enemyToCheck); + const movePower = allMoves[moveToCheck].calculateBattlePower(game.scene.getPlayerPokemon(), enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power * Math.pow(steelySpiritMultiplier, 2)); }); @@ -91,7 +91,7 @@ describe("Abilities - Steely Spirit", () => { game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - const movePower = allMoves[moveToCheck].calculatePower(boostedAlly, enemyToCheck); + const movePower = allMoves[moveToCheck].calculateBattlePower(boostedAlly, enemyToCheck); expect(movePower).toBe(allMoves[moveToCheck].power); }); diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index df864607ea44..27d4c2b36156 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -43,7 +43,7 @@ describe("Moves - Hard Press", () => { const enemy = game.scene.getEnemyPokemon(); const ally = game.scene.getPlayerPokemon(); - const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); + const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); @@ -55,7 +55,7 @@ describe("Moves - Hard Press", () => { const enemy = game.scene.getEnemyPokemon(); const ally = game.scene.getPlayerPokemon(); - const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); + const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); vi.spyOn(enemy, "getHpRatio").mockReturnValue(.5); @@ -63,7 +63,7 @@ describe("Moves - Hard Press", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(TurnStartPhase); - const halfHpMovePower = moveToCheck.calculatePower(ally, enemy); + const halfHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(halfHpMovePower).toBe(Math.max(Math.floor(moveMaxBasePower * enemy.getHpRatio()), 1) ); }); @@ -72,7 +72,7 @@ describe("Moves - Hard Press", () => { const enemy = game.scene.getEnemyPokemon(); const ally = game.scene.getPlayerPokemon(); - const fullHpMovePower = moveToCheck.calculatePower(ally, enemy); + const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); vi.spyOn(enemy, "getHpRatio").mockReturnValue(.1); @@ -80,7 +80,7 @@ describe("Moves - Hard Press", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(TurnStartPhase); - const oneHpMovePower = moveToCheck.calculatePower(ally, enemy); + const oneHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(oneHpMovePower).toBe(Math.max(Math.floor(moveMaxBasePower * enemy.getHpRatio()), 1) ); }); }); From f7781af89c7e9c69b81d62140c4a6fed1299f895 Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 16:22:52 +0800 Subject: [PATCH 07/14] cleanup tests --- src/test/moves/hard_press.test.ts | 78 +++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index 27d4c2b36156..c0999bc7ef34 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -4,6 +4,7 @@ import GameManager from "#app/test/utils/gameManager"; import * as overrides from "#app/overrides"; import { Species } from "#enums/species"; import { + MoveEffectPhase, TurnStartPhase, } from "#app/phases"; import { Moves } from "#enums/moves"; @@ -15,9 +16,35 @@ import { allMoves, OpponentHighHpPowerAttr } from "#app/data/move.js"; describe("Moves - Hard Press", () => { let phaserGame: Phaser.Game; let game: GameManager; + + /** + * Retrieves the maximum base power of a move based on its attributes. + * + * @param move - The move which maximum base power is being retrieved. + * @returns The maximum base power of the move. + */ + const getMoveMaxBasePower = (move: Move): number => { + const attr = move.getAttrs(OpponentHighHpPowerAttr); + + return (attr[0] as OpponentHighHpPowerAttr)["maxBasePower"]; + }; + + /** + * Computes Hard Press's power based on formula from Bulbapedia. + * {@link https://bulbapedia.bulbagarden.net/wiki/Hard_Press_(move)} + * + * @param targetHpRatio - Target's HP ratio. + * @returns The computed power of the move. + */ + const computeHardPressMovePower = (targetHpRatio: number): number => { + getMoveMaxBasePower(moveToCheck); + return Math.max(Math.floor(moveMaxBasePower * targetHpRatio), 1); + }; + const moveToCheck = allMoves[Moves.HARD_PRESS]; const moveMaxBasePower = getMoveMaxBasePower(moveToCheck); + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -38,6 +65,21 @@ describe("Moves - Hard Press", () => { vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.HARD_PRESS]); }); + it("power varies between 1 and 100 based on target health ratio (random)", async () => { + await game.startBattle([Species.PIKACHU]); + const enemy = game.scene.getEnemyPokemon(); + const ally = game.scene.getPlayerPokemon(); + + const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); + expect(fullHpMovePower).toBe(moveMaxBasePower); + + game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); + await game.phaseInterceptor.to(MoveEffectPhase); + + const reducedHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); + expect(reducedHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); + }); + it("power varies between 1 and 100 based on target health ratio (100%)", async () => { await game.startBattle([Species.PIKACHU]); const enemy = game.scene.getEnemyPokemon(); @@ -64,7 +106,7 @@ describe("Moves - Hard Press", () => { await game.phaseInterceptor.to(TurnStartPhase); const halfHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(halfHpMovePower).toBe(Math.max(Math.floor(moveMaxBasePower * enemy.getHpRatio()), 1) ); + expect(halfHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio()) ); }); it("power varies between 1 and 100 based on target health ratio (1%)", async () => { @@ -75,24 +117,30 @@ describe("Moves - Hard Press", () => { const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); expect(fullHpMovePower).toBe(moveMaxBasePower); - vi.spyOn(enemy, "getHpRatio").mockReturnValue(.1); + vi.spyOn(enemy, "getMaxHp").mockReturnValue(100); + vi.spyOn(enemy, "getHpRatio").mockReturnValue(.01); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(TurnStartPhase); const oneHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(oneHpMovePower).toBe(Math.max(Math.floor(moveMaxBasePower * enemy.getHpRatio()), 1) ); + expect(oneHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); }); -}); -/** - * Retrieves the maximum base power of a move based on its attributes. - * - * @param move - The move which maximum base power is being retrieved. - * @returns The maximum base power of the move. - */ -const getMoveMaxBasePower = (move: Move) => { - const attr = move.getAttrs(OpponentHighHpPowerAttr); - - return (attr[0] as OpponentHighHpPowerAttr)["maxBasePower"]; -}; + it("power varies between 1 and 100 based on target health ratio (less than 1%)", async () => { + await game.startBattle([Species.PIKACHU]); + const enemy = game.scene.getEnemyPokemon(); + const ally = game.scene.getPlayerPokemon(); + + const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); + expect(fullHpMovePower).toBe(moveMaxBasePower); + + vi.spyOn(enemy, "getHpRatio").mockReturnValue(.005); + + game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); + await game.phaseInterceptor.to(TurnStartPhase); + + const lessThanOnePercentHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); + expect(lessThanOnePercentHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); + }); +}); From b9af959382e46889c279c0fef6d5a97bdb338266 Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 22:33:45 +0800 Subject: [PATCH 08/14] improve tests --- src/test/abilities/aura_break.test.ts | 20 ++--- src/test/abilities/battery.test.ts | 67 +++++++--------- src/test/abilities/power_spot.test.ts | 67 +++++++--------- src/test/abilities/steely_spirit.test.ts | 36 ++++----- src/test/moves/hard_press.test.ts | 97 +++++++----------------- 5 files changed, 111 insertions(+), 176 deletions(-) diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index 2020d73b8e9a..a6c71985643c 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -39,30 +39,30 @@ describe("Abilities - Aura Break", () => { }); it("reverses the effect of fairy aura", async () => { - vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.FAIRY_AURA); const moveToCheck = allMoves[Moves.MOONBLAST]; const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP]); - game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.FAIRY_AURA); + vi.spyOn(moveToCheck, "calculateBattlePower"); - const movePower = moveToCheck.calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + await game.startBattle([Species.MAGIKARP]); + game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); await game.phaseInterceptor.to(MoveEffectPhase); - expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * auraMultiplier * auraBreakMultiplier); }); it("reverses the effect of dark aura", async () => { - vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DARK_AURA); const moveToCheck = allMoves[Moves.DARK_PULSE]; const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP]); - game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DARK_AURA); + vi.spyOn(moveToCheck, "calculateBattlePower"); - const movePower = moveToCheck.calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + await game.startBattle([Species.MAGIKARP]); + game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); await game.phaseInterceptor.to(MoveEffectPhase); - expect(movePower).toBe(basePower * auraMultiplier * auraBreakMultiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * auraMultiplier * auraBreakMultiplier); }); }); diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index 6561a92a1c91..d9d65902d1b3 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -6,14 +6,15 @@ import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { allMoves } from "#app/data/move.js"; -import { AllyMoveCategoryPowerBoostAbAttr } from "#app/data/ability.js"; -import Pokemon from "#app/field/pokemon.js"; import { Abilities } from "#app/enums/abilities.js"; +import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; describe("Abilities - Battery", () => { let phaserGame: Phaser.Game; let game: GameManager; + const batteryMultiplier = 1.3; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -27,64 +28,54 @@ describe("Abilities - Battery", () => { beforeEach(() => { game = new GameManager(phaserGame); vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE); vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); - vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.ROCK_SLIDE, Moves.SPLASH, Moves.HEAT_WAVE]); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.BREAKING_SWIPE, Moves.SPLASH, Moves.DAZZLING_GLEAM]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); }); it("raises the power of allies' special moves by 30%", async () => { - const moveToBeUsed = Moves.HEAT_WAVE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.DAZZLING_GLEAM]; + const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP, Species.CHARJABUG]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); - game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.startBattle([Species.PIKACHU, Species.CHARJABUG]); - const [boostedAlly, boostSource] = game.scene.getPlayerField(); - const multiplier = getAttrPowerMultiplier(boostSource); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(boostedAlly, game.scene.getEnemyPokemon()); + game.doAttack(getMovePosition(game.scene, 0, Moves.DAZZLING_GLEAM)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - expect(movePower).toBe(basePower * multiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * batteryMultiplier); }); it("does not raise the power of allies' non-special moves", async () => { - const moveToBeUsed = Moves.ROCK_SLIDE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.BREAKING_SWIPE]; + const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP, Species.CHARJABUG]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); - game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.startBattle([Species.PIKACHU, Species.CHARJABUG]); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + game.doAttack(getMovePosition(game.scene, 0, Moves.BREAKING_SWIPE)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - expect(movePower).toBe(basePower); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower); }); it("does not raise the power of the ability owner's special moves", async () => { - const moveToBeUsed = Moves.HEAT_WAVE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.DAZZLING_GLEAM]; + const basePower = moveToCheck.power; - await game.startBattle([Species.CHARJABUG, Species.MAGIKARP]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); - game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.startBattle([Species.CHARJABUG, Species.PIKACHU]); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); + game.doAttack(getMovePosition(game.scene, 0, Moves.DAZZLING_GLEAM)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(TurnEndPhase); - expect(movePower).toBe(basePower); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower); }); }); - -/** - * Retrieves the power multiplier from a Pokémon's ability attribute. - * - * @param pokemon - The Pokémon whose ability attributes are being queried. - * @returns The power multiplier of the `AllyMoveCategoryPowerBoostAbAttr` attribute. - */ -const getAttrPowerMultiplier = (pokemon: Pokemon) => { - const attr = pokemon.getAbilityAttrs(AllyMoveCategoryPowerBoostAbAttr); - - return (attr[0] as AllyMoveCategoryPowerBoostAbAttr)["powerMultiplier"]; -}; diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index 2b584b6e9c01..480f2a514a92 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -6,13 +6,15 @@ import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { allMoves } from "#app/data/move.js"; -import { AllyMoveCategoryPowerBoostAbAttr } from "#app/data/ability.js"; -import Pokemon from "#app/field/pokemon.js"; +import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; +import { Abilities } from "#app/enums/abilities.js"; describe("Abilities - Power Spot", () => { let phaserGame: Phaser.Game; let game: GameManager; + const powerSpotMultiplier = 1.3; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -26,64 +28,51 @@ describe("Abilities - Power Spot", () => { beforeEach(() => { game = new GameManager(phaserGame); vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); - vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.ROCK_SLIDE, Moves.SPLASH, Moves.HEAT_WAVE]); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.BREAKING_SWIPE, Moves.SPLASH, Moves.DAZZLING_GLEAM]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); }); it("raises the power of allies' special moves by 30%", async () => { - const moveToBeUsed = Moves.HEAT_WAVE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.DAZZLING_GLEAM]; + const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP, Species.STONJOURNER]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); + await game.startBattle([Species.PIKACHU, Species.STONJOURNER]); + game.doAttack(getMovePosition(game.scene, 0, Moves.DAZZLING_GLEAM)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - const multiplier = getAttrPowerMultiplier(game.scene.getPlayerField()[1]); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerField()[0], game.scene.getEnemyField()[0]); - - expect(movePower).toBe(basePower * multiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * powerSpotMultiplier); }); it("raises the power of allies' physical moves by 30%", async () => { - const moveToBeUsed = Moves.ROCK_SLIDE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.BREAKING_SWIPE]; + const basePower = moveToCheck.power; - await game.startBattle([Species.MAGIKARP, Species.STONJOURNER]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); + await game.startBattle([Species.PIKACHU, Species.STONJOURNER]); + game.doAttack(getMovePosition(game.scene, 0, Moves.BREAKING_SWIPE)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - const [boostedAlly, boostSource] = game.scene.getPlayerField(); - const multiplier = getAttrPowerMultiplier(boostSource); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(boostedAlly, game.scene.getEnemyPokemon()); - - expect(movePower).toBe(basePower * multiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * powerSpotMultiplier); }); it("does not raise the power of the ability owner's moves", async () => { - const moveToBeUsed = Moves.HEAT_WAVE; - const basePower = allMoves[moveToBeUsed].power; + const moveToCheck = allMoves[Moves.BREAKING_SWIPE]; + const basePower = moveToCheck.power; - await game.startBattle([Species.STONJOURNER, Species.MAGIKARP]); + vi.spyOn(moveToCheck, "calculateBattlePower"); - game.doAttack(getMovePosition(game.scene, 0, moveToBeUsed)); + await game.startBattle([Species.STONJOURNER, Species.PIKACHU]); + game.doAttack(getMovePosition(game.scene, 0, Moves.BREAKING_SWIPE)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(TurnEndPhase); - const movePower = allMoves[moveToBeUsed].calculateBattlePower(game.scene.getPlayerPokemon(), game.scene.getEnemyPokemon()); - - expect(movePower).toBe(basePower); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower); }); }); - -/** - * Retrieves the power multiplier from a Pokémon's ability attribute. - * - * @param pokemon - The Pokémon whose ability attributes are being queried. - * @returns The power multiplier of the `AllyMoveCategoryPowerBoostAbAttr` attribute. - */ -const getAttrPowerMultiplier = (pokemon: Pokemon) => { - const attr = pokemon.getAbilityAttrs(AllyMoveCategoryPowerBoostAbAttr); - - return (attr[0] as AllyMoveCategoryPowerBoostAbAttr)["powerMultiplier"]; -}; diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 430f1a863574..27830fbd5c93 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -8,13 +8,14 @@ import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { allMoves } from "#app/data/move.js"; import { allAbilities } from "#app/data/ability.js"; import { Abilities } from "#app/enums/abilities.js"; -import { SelectTargetPhase } from "#app/phases.js"; +import { MoveEffectPhase, SelectTargetPhase } from "#app/phases.js"; describe("Abilities - Steely Spirit", () => { let phaserGame: Phaser.Game; let game: GameManager; const steelySpiritMultiplier = 1.5; const moveToCheck = Moves.IRON_HEAD; + const ironHeadPower = allMoves[moveToCheck].power; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -29,15 +30,17 @@ describe("Abilities - Steely Spirit", () => { beforeEach(() => { game = new GameManager(phaserGame); vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); - vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.IRON_HEAD, Moves.SPLASH]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); + vi.spyOn(allMoves[moveToCheck], "calculateBattlePower"); }); - it("increases Steel-type moves used by the user and its allies", async () => { - await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); - const [boostedAlly, boostSource] = game.scene.getPlayerField(); - const enemyToCheck = game.scene.getEnemyField()[0]; + it("increases Steel-type moves' power used by the user and its allies by 50%", async () => { + await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); + const boostSource = game.scene.getPlayerField()[1]; + const enemyToCheck = game.scene.getEnemyPokemon(); vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); @@ -47,14 +50,13 @@ describe("Abilities - Steely Spirit", () => { await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - const movePower = allMoves[moveToCheck].calculateBattlePower(boostedAlly, enemyToCheck); - - expect(movePower).toBe(allMoves[moveToCheck].power * steelySpiritMultiplier); + expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * steelySpiritMultiplier); }); it("stacks if multiple users with this ability are on the field.", async () => { - await game.startBattle([Species.PERRSERKER, Species.PERRSERKER]); + await game.startBattle([Species.PIKACHU, Species.PIKACHU]); const enemyToCheck = game.scene.getEnemyPokemon(); game.scene.getPlayerField().forEach(p => { @@ -67,15 +69,14 @@ describe("Abilities - Steely Spirit", () => { await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - const movePower = allMoves[moveToCheck].calculateBattlePower(game.scene.getPlayerPokemon(), enemyToCheck); - - expect(movePower).toBe(allMoves[moveToCheck].power * Math.pow(steelySpiritMultiplier, 2)); + expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2)); }); it("does not take effect when suppressed", async () => { - await game.startBattle([Species.MAGIKARP, Species.PERRSERKER]); - const [boostedAlly, boostSource] = game.scene.getPlayerField(); + await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); + const boostSource = game.scene.getPlayerField()[1]; const enemyToCheck = game.scene.getEnemyPokemon(); vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); @@ -90,9 +91,8 @@ describe("Abilities - Steely Spirit", () => { await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + await game.phaseInterceptor.to(MoveEffectPhase); - const movePower = allMoves[moveToCheck].calculateBattlePower(boostedAlly, enemyToCheck); - - expect(movePower).toBe(allMoves[moveToCheck].power); + expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower); }); }); diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index c0999bc7ef34..45414456a51c 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -4,46 +4,19 @@ import GameManager from "#app/test/utils/gameManager"; import * as overrides from "#app/overrides"; import { Species } from "#enums/species"; import { - MoveEffectPhase, - TurnStartPhase, + MoveEffectPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; -import Move from "#app/data/move.js"; -import { allMoves, OpponentHighHpPowerAttr } from "#app/data/move.js"; +import { allMoves } from "#app/data/move.js"; +import { randInt } from "#app/utils.js"; describe("Moves - Hard Press", () => { let phaserGame: Phaser.Game; let game: GameManager; - /** - * Retrieves the maximum base power of a move based on its attributes. - * - * @param move - The move which maximum base power is being retrieved. - * @returns The maximum base power of the move. - */ - const getMoveMaxBasePower = (move: Move): number => { - const attr = move.getAttrs(OpponentHighHpPowerAttr); - - return (attr[0] as OpponentHighHpPowerAttr)["maxBasePower"]; - }; - - /** - * Computes Hard Press's power based on formula from Bulbapedia. - * {@link https://bulbapedia.bulbagarden.net/wiki/Hard_Press_(move)} - * - * @param targetHpRatio - Target's HP ratio. - * @returns The computed power of the move. - */ - const computeHardPressMovePower = (targetHpRatio: number): number => { - getMoveMaxBasePower(moveToCheck); - return Math.max(Math.floor(moveMaxBasePower * targetHpRatio), 1); - }; - const moveToCheck = allMoves[Moves.HARD_PRESS]; - const moveMaxBasePower = getMoveMaxBasePower(moveToCheck); - beforeAll(() => { phaserGame = new Phaser.Game({ @@ -63,84 +36,66 @@ describe("Moves - Hard Press", () => { vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.HARD_PRESS]); + vi.spyOn(moveToCheck, "calculateBattlePower"); }); - it("power varies between 1 and 100 based on target health ratio (random)", async () => { + it("should return power between 1 and 100 based on target health ratio", async () => { await game.startBattle([Species.PIKACHU]); const enemy = game.scene.getEnemyPokemon(); - const ally = game.scene.getPlayerPokemon(); - const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(fullHpMovePower).toBe(moveMaxBasePower); + vi.spyOn(enemy, "getHpRatio").mockReturnValue(randInt(99, 1)); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); await game.phaseInterceptor.to(MoveEffectPhase); - const reducedHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(reducedHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(Math.max(Math.floor(100 * enemy.getHpRatio()), 1)); }); - it("power varies between 1 and 100 based on target health ratio (100%)", async () => { + it("should return 100 power if target HP ratio is at 100%", async () => { await game.startBattle([Species.PIKACHU]); - const enemy = game.scene.getEnemyPokemon(); - const ally = game.scene.getPlayerPokemon(); - - const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(fullHpMovePower).toBe(moveMaxBasePower); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); - await game.phaseInterceptor.to(TurnStartPhase); + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(100); }); - it("power varies between 1 and 100 based on target health ratio (50%)", async () => { + it("should return 50 power if target HP ratio is at 50%", async () => { await game.startBattle([Species.PIKACHU]); + const targetHpRatio = .5; const enemy = game.scene.getEnemyPokemon(); - const ally = game.scene.getPlayerPokemon(); - const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(fullHpMovePower).toBe(moveMaxBasePower); - - vi.spyOn(enemy, "getHpRatio").mockReturnValue(.5); + vi.spyOn(enemy, "getHpRatio").mockReturnValue(targetHpRatio); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); - await game.phaseInterceptor.to(TurnStartPhase); + await game.phaseInterceptor.to(MoveEffectPhase); - const halfHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(halfHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio()) ); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(50); }); - it("power varies between 1 and 100 based on target health ratio (1%)", async () => { + it("should return 1 power if target HP ratio is at 1%", async () => { await game.startBattle([Species.PIKACHU]); + const targetHpRatio = .01; const enemy = game.scene.getEnemyPokemon(); - const ally = game.scene.getPlayerPokemon(); - - const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(fullHpMovePower).toBe(moveMaxBasePower); - vi.spyOn(enemy, "getMaxHp").mockReturnValue(100); - vi.spyOn(enemy, "getHpRatio").mockReturnValue(.01); + vi.spyOn(enemy, "getHpRatio").mockReturnValue(targetHpRatio); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); - await game.phaseInterceptor.to(TurnStartPhase); + await game.phaseInterceptor.to(MoveEffectPhase); - const oneHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(oneHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(1); }); - it("power varies between 1 and 100 based on target health ratio (less than 1%)", async () => { + it("should return 1 power if target HP ratio is less than 1%", async () => { await game.startBattle([Species.PIKACHU]); + const targetHpRatio = .005; const enemy = game.scene.getEnemyPokemon(); - const ally = game.scene.getPlayerPokemon(); - - const fullHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(fullHpMovePower).toBe(moveMaxBasePower); - vi.spyOn(enemy, "getHpRatio").mockReturnValue(.005); + vi.spyOn(enemy, "getHpRatio").mockReturnValue(targetHpRatio); game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); - await game.phaseInterceptor.to(TurnStartPhase); + await game.phaseInterceptor.to(MoveEffectPhase); - const lessThanOnePercentHpMovePower = moveToCheck.calculateBattlePower(ally, enemy); - expect(lessThanOnePercentHpMovePower).toBe(computeHardPressMovePower(enemy.getHpRatio())); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(1); }); }); From 7b92e1498fc59d4dc03fb22af530c15f176fe78d Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 22:44:27 +0800 Subject: [PATCH 09/14] use slow vs fast pokemon --- src/test/abilities/aura_break.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index a6c71985643c..e6a686b7276b 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -36,6 +36,7 @@ describe("Abilities - Aura Break", () => { vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.MOONBLAST, Moves.DARK_PULSE, Moves.MOONBLAST, Moves.DARK_PULSE]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.AURA_BREAK); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE); }); it("reverses the effect of fairy aura", async () => { @@ -45,7 +46,7 @@ describe("Abilities - Aura Break", () => { vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.FAIRY_AURA); vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.MAGIKARP]); + await game.startBattle([Species.PIKACHU]); game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); await game.phaseInterceptor.to(MoveEffectPhase); @@ -59,7 +60,7 @@ describe("Abilities - Aura Break", () => { vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DARK_AURA); vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.MAGIKARP]); + await game.startBattle([Species.PIKACHU]); game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); await game.phaseInterceptor.to(MoveEffectPhase); From 2dc18ed13608251c416f91a4e273b813718a99a7 Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 22:53:35 +0800 Subject: [PATCH 10/14] fix steely spirit test --- src/test/abilities/steely_spirit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 27830fbd5c93..61ff7427b1e7 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -68,7 +68,7 @@ describe("Abilities - Steely Spirit", () => { game.doAttack(getMovePosition(game.scene, 0, moveToCheck)); await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); - game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + game.doAttack(getMovePosition(game.scene, 1, moveToCheck)); await game.phaseInterceptor.to(MoveEffectPhase); expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2)); From 64fabbd8c68aaf54c323080a26dcbc44b01055ff Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 23:01:43 +0800 Subject: [PATCH 11/14] fix steely spirit for real this time --- src/test/abilities/steely_spirit.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 61ff7427b1e7..e0979ca8884a 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -69,6 +69,8 @@ describe("Abilities - Steely Spirit", () => { await game.phaseInterceptor.to(SelectTargetPhase, false); game.doSelectTarget(enemyToCheck.getBattlerIndex()); game.doAttack(getMovePosition(game.scene, 1, moveToCheck)); + await game.phaseInterceptor.to(SelectTargetPhase, false); + game.doSelectTarget(enemyToCheck.getBattlerIndex()); await game.phaseInterceptor.to(MoveEffectPhase); expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2)); From 0a1d1ebca806f4a645a905db9139eebcb4fa108c Mon Sep 17 00:00:00 2001 From: torranx Date: Fri, 12 Jul 2024 23:15:06 +0800 Subject: [PATCH 12/14] remove unnecessary test --- src/test/moves/hard_press.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index 45414456a51c..c6c6f691ac11 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -10,7 +10,6 @@ import { Moves } from "#enums/moves"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; import { allMoves } from "#app/data/move.js"; -import { randInt } from "#app/utils.js"; describe("Moves - Hard Press", () => { let phaserGame: Phaser.Game; @@ -39,18 +38,6 @@ describe("Moves - Hard Press", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); }); - it("should return power between 1 and 100 based on target health ratio", async () => { - await game.startBattle([Species.PIKACHU]); - const enemy = game.scene.getEnemyPokemon(); - - vi.spyOn(enemy, "getHpRatio").mockReturnValue(randInt(99, 1)); - - game.doAttack(getMovePosition(game.scene, 0, Moves.HARD_PRESS)); - await game.phaseInterceptor.to(MoveEffectPhase); - - expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(Math.max(Math.floor(100 * enemy.getHpRatio()), 1)); - }); - it("should return 100 power if target HP ratio is at 100%", async () => { await game.startBattle([Species.PIKACHU]); From 0d4aef376869145653e2f27cde0b791287cbcd99 Mon Sep 17 00:00:00 2001 From: torranx Date: Sat, 13 Jul 2024 18:15:21 +0800 Subject: [PATCH 13/14] address pr feedback --- src/data/move.ts | 2 +- src/test/abilities/aura_break.test.ts | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 92f63fa07cb4..c3311cd62e71 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -676,7 +676,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, power); - if (source.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + if (source.getAlly()) { applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, power); } diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index 81b01b2093af..fa7f34edb2fc 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -12,13 +12,8 @@ import { allMoves } from "#app/data/move.js"; describe("Abilities - Aura Break", () => { let phaserGame: Phaser.Game; let game: GameManager; - const auraBreakMultiplier = 9 / 16; - const auraMultiplier = 4 / 3; - /** - * Apparently, the auraMultiplier is being multiplied first to the move's power then multiplied again to - * the auraBreakMultiplier. This means we can't net the multiplier like so: - * power * (auraMultiplier * auraBreakMultiplier). Doing so will make the result off by a decimal value. - */ + + const auraBreakMultiplier = 9/16 * 4/3; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -50,7 +45,7 @@ describe("Abilities - Aura Break", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.MOONBLAST)); await game.phaseInterceptor.to(MoveEffectPhase); - expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * auraMultiplier * auraBreakMultiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier)); }); it("reverses the effect of dark aura", async () => { @@ -64,6 +59,6 @@ describe("Abilities - Aura Break", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.DARK_PULSE)); await game.phaseInterceptor.to(MoveEffectPhase); - expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower * auraMultiplier * auraBreakMultiplier); + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier)); }); }); From e55d5086126af0b7513134c28675187931fca69d Mon Sep 17 00:00:00 2001 From: torranx Date: Sun, 14 Jul 2024 04:39:13 +0800 Subject: [PATCH 14/14] add removed code --- src/data/move.ts | 3 --- src/field/pokemon.ts | 7 ++++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index c3311cd62e71..0262f60370e5 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -698,9 +698,6 @@ export default class Move implements Localizable { const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === this.type) as TypeBoostTag; if (typeBoost) { power.value *= typeBoost.boostValue; - if (typeBoost.oneUse) { - source.removeTag(typeBoost.tagType); - } } if (source.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && this.type === Type.GROUND && this.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6c9019f873d0..adcecbf72fdf 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -19,7 +19,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases"; import { BattleStat } from "../data/battle-stat"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { TempBattleStat } from "../data/temp-battle-stat"; import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag"; @@ -1792,6 +1792,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.stopMultiHit(this); result = HitResult.NO_EFFECT; } else { + const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; + if (typeBoost?.oneUse) { + source.removeTag(typeBoost.tagType); + } + const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded())); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier);