[Enhancement] Decouple move accuracy and accuracy multiplier calculation from phases.ts (#2899)
* refactor accuracy calc * update doc * move accuracy multiplier calculation outside phases * update wonder skin unit test * rename method * add docs * add unit tests * address feedback * rename method * fix imports * improve tests * add test for ohko move accuracy
This commit is contained in:
parent
4ad53024b4
commit
f298ec3111
|
@ -10,9 +10,9 @@ import { Constructor } from "#app/utils";
|
|||
import * as Utils from "../utils";
|
||||
import { WeatherType } from "./weather";
|
||||
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 { 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, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability";
|
||||
import { allAbilities } from "./ability";
|
||||
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
|
||||
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Stat } from "./pokemon-stat";
|
||||
import { TerrainType } from "./terrain";
|
||||
|
@ -656,6 +656,44 @@ export default class Move implements Localizable {
|
|||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the accuracy of a move in battle based on various conditions and attributes.
|
||||
*
|
||||
* @param user {@linkcode Pokemon} The Pokémon using the move.
|
||||
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
|
||||
* @returns The calculated accuracy of the move.
|
||||
*/
|
||||
calculateBattleAccuracy(user: Pokemon, target: Pokemon) {
|
||||
const moveAccuracy = new Utils.NumberHolder(this.accuracy);
|
||||
|
||||
applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy);
|
||||
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, moveAccuracy);
|
||||
|
||||
if (moveAccuracy.value === -1) {
|
||||
return moveAccuracy.value;
|
||||
}
|
||||
|
||||
const isOhko = this.hasAttr(OneHitKOAccuracyAttr);
|
||||
|
||||
if (!isOhko) {
|
||||
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
||||
}
|
||||
|
||||
if (user.scene.arena.weather?.weatherType === WeatherType.FOG) {
|
||||
/**
|
||||
* The 0.9 multiplier is PokeRogue-only implementation, Bulbapedia uses 3/5
|
||||
* See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog}
|
||||
*/
|
||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9);
|
||||
}
|
||||
|
||||
if (!isOhko && user.scene.arena.getTag(ArenaTagType.GRAVITY)) {
|
||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
|
||||
}
|
||||
|
||||
return moveAccuracy.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the power of a move in battle based on various conditions and attributes.
|
||||
*
|
||||
|
|
|
@ -23,7 +23,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo
|
|||
import { WeatherType } from "../data/weather";
|
||||
import { TempBattleStat } from "../data/temp-battle-stat";
|
||||
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 { 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, IgnoreOpponentEvasionAbAttr } from "../data/ability";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Mode } from "../ui/ui";
|
||||
|
@ -1738,6 +1738,43 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the accuracy multiplier of the user against a target.
|
||||
*
|
||||
* This method considers various factors such as the user's accuracy level, the target's evasion level,
|
||||
* abilities, and modifiers to compute the final accuracy multiplier.
|
||||
*
|
||||
* @param target {@linkcode Pokemon} - The target Pokémon against which the move is used.
|
||||
* @param sourceMove {@linkcode Move} - The move being used by the user.
|
||||
* @returns The calculated accuracy multiplier.
|
||||
*/
|
||||
getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number {
|
||||
const userAccuracyLevel = new Utils.IntegerHolder(this.summonData.battleStats[BattleStat.ACC]);
|
||||
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
|
||||
|
||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel);
|
||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, targetEvasionLevel);
|
||||
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvasionLevel);
|
||||
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel);
|
||||
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel);
|
||||
|
||||
const accuracyMultiplier = new Utils.NumberHolder(1);
|
||||
if (userAccuracyLevel.value !== targetEvasionLevel.value) {
|
||||
accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value
|
||||
? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3
|
||||
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
|
||||
}
|
||||
|
||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, sourceMove);
|
||||
|
||||
const evasionMultiplier = new Utils.NumberHolder(1);
|
||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier);
|
||||
|
||||
accuracyMultiplier.value /= evasionMultiplier.value;
|
||||
|
||||
return accuracyMultiplier.value;
|
||||
}
|
||||
|
||||
apply(source: Pokemon, move: Move): HitResult {
|
||||
let result: HitResult;
|
||||
const damage = new Utils.NumberHolder(0);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import BattleScene, { bypassLogin } from "./battle-scene";
|
||||
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
|
||||
import * as Utils from "./utils";
|
||||
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
|
||||
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
|
||||
import { Mode } from "./ui/ui";
|
||||
import { Command } from "./ui/command-ui-handler";
|
||||
import { Stat } from "./data/pokemon-stat";
|
||||
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier } from "./modifier/modifier";
|
||||
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier } from "./modifier/modifier";
|
||||
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
|
||||
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
|
||||
|
@ -24,9 +24,8 @@ import { getPokemonMessage, getPokemonNameWithAffix } from "./messages";
|
|||
import { Starter } from "./ui/starter-select-ui-handler";
|
||||
import { Gender } from "./data/gender";
|
||||
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
|
||||
import { TempBattleStat } from "./data/temp-battle-stat";
|
||||
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
|
||||
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
|
||||
import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
|
||||
import { Unlockables, getUnlockableName } from "./system/unlockables";
|
||||
import { getBiomeKey } from "./field/arena";
|
||||
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
|
||||
|
@ -3079,54 +3078,16 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
return false;
|
||||
}
|
||||
|
||||
const moveAccuracy = new Utils.NumberHolder(this.move.getMove().accuracy);
|
||||
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user, target);
|
||||
|
||||
applyMoveAttrs(VariableAccuracyAttr, user, target, this.move.getMove(), moveAccuracy);
|
||||
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this.move.getMove(), { value: false }, moveAccuracy);
|
||||
|
||||
if (moveAccuracy.value === -1) {
|
||||
if (moveAccuracy === -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isOhko = this.move.getMove().hasAttr(OneHitKOAccuracyAttr);
|
||||
|
||||
if (!isOhko) {
|
||||
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
||||
}
|
||||
|
||||
if (this.scene.arena.weather?.weatherType === WeatherType.FOG) {
|
||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9);
|
||||
}
|
||||
|
||||
if (!isOhko && this.scene.arena.getTag(ArenaTagType.GRAVITY)) {
|
||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
|
||||
}
|
||||
|
||||
const userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]);
|
||||
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
|
||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel);
|
||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel);
|
||||
applyAbAttrs(IgnoreOpponentEvasionAbAttr, user, null, targetEvasionLevel);
|
||||
applyMoveAttrs(IgnoreOpponentStatChangesAttr, user, target, this.move.getMove(), targetEvasionLevel);
|
||||
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel);
|
||||
|
||||
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
|
||||
const rand = user.randSeedInt(100, 1);
|
||||
|
||||
const accuracyMultiplier = new Utils.NumberHolder(1);
|
||||
if (userAccuracyLevel.value !== targetEvasionLevel.value) {
|
||||
accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value
|
||||
? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3
|
||||
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
|
||||
}
|
||||
|
||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, this.move.getMove());
|
||||
|
||||
const evasionMultiplier = new Utils.NumberHolder(1);
|
||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier);
|
||||
|
||||
accuracyMultiplier.value /= evasionMultiplier.value;
|
||||
|
||||
return rand <= moveAccuracy.value * accuracyMultiplier.value;
|
||||
return rand <= moveAccuracy * accuracyMultiplier;
|
||||
}
|
||||
|
||||
getUserPokemon(): Pokemon {
|
||||
|
|
|
@ -3,14 +3,12 @@ import Phaser from "phaser";
|
|||
import GameManager from "#app/test/utils/gameManager";
|
||||
import overrides from "#app/overrides";
|
||||
import { Species } from "#enums/species";
|
||||
import { TurnEndPhase, } from "#app/phases";
|
||||
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 { MoveAbilityBypassAbAttr, WonderSkinAbAttr } from "#app/data/ability.js";
|
||||
import { NumberHolder } from "#app/utils.js";
|
||||
import Pokemon from "#app/field/pokemon.js";
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { allAbilities } from "#app/data/ability.js";
|
||||
|
||||
describe("Abilities - Wonder Skin", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
|
@ -29,113 +27,51 @@ describe("Abilities - Wonder Skin", () => {
|
|||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.WONDER_SKIN);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.CHARM]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.WONDER_SKIN);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("lowers accuracy of status moves to 50%", async () => {
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
const moveToCheck = allMoves[Moves.CHARM];
|
||||
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
const mockedAccuracy = getMockedMoveAccuracy(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[Moves.CHARM]);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(mockedAccuracy).not.toBe(undefined);
|
||||
expect(mockedAccuracy).not.toBe(100);
|
||||
expect(mockedAccuracy).toBe(50);
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(50);
|
||||
});
|
||||
|
||||
it("does not lower accuracy of non-status moves", async () => {
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
const moveToCheck = allMoves[Moves.TACKLE];
|
||||
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
const mockedAccuracy = getMockedMoveAccuracy(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[Moves.TACKLE]);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(mockedAccuracy).not.toBe(undefined);
|
||||
expect(mockedAccuracy).toBe(100);
|
||||
expect(mockedAccuracy).not.toBe(50);
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(100);
|
||||
});
|
||||
|
||||
it("does not affect pokemon with Mold Breaker", async () => {
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOLD_BREAKER);
|
||||
const bypassAbilities = [Abilities.MOLD_BREAKER, Abilities.TERAVOLT, Abilities.TURBOBLAZE];
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
bypassAbilities.forEach(ability => {
|
||||
it(`does not affect pokemon with ${allAbilities[ability].name}`, async () => {
|
||||
const moveToCheck = allMoves[Moves.CHARM];
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(ability);
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
const mockedAccuracy = getMockedMoveAccuracy(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[Moves.CHARM]);
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(mockedAccuracy).not.toBe(undefined);
|
||||
expect(mockedAccuracy).toBe(100);
|
||||
expect(mockedAccuracy).not.toBe(50);
|
||||
});
|
||||
|
||||
it("does not affect pokemon with Teravolt", async () => {
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.TERAVOLT);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
|
||||
|
||||
const mockedAccuracy = getMockedMoveAccuracy(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[Moves.CHARM]);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(mockedAccuracy).not.toBe(undefined);
|
||||
expect(mockedAccuracy).toBe(100);
|
||||
expect(mockedAccuracy).not.toBe(50);
|
||||
});
|
||||
|
||||
it("does not affect pokemon with Turboblaze", async () => {
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.TURBOBLAZE);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
|
||||
|
||||
const mockedAccuracy = getMockedMoveAccuracy(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[Moves.CHARM]);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(mockedAccuracy).not.toBe(undefined);
|
||||
expect(mockedAccuracy).toBe(100);
|
||||
expect(mockedAccuracy).not.toBe(50);
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Calculates the mocked accuracy of a move.
|
||||
* Note this does not consider other accuracy calculations
|
||||
* except the power multiplier from Wonder Skin.
|
||||
* Bypassed by MoveAbilityBypassAbAttr {@linkcode MoveAbilityBypassAbAttr}
|
||||
*
|
||||
* @param defender - The defending Pokémon.
|
||||
* @param attacker - The attacking Pokémon.
|
||||
* @param move - The move being used by the attacker.
|
||||
* @returns The adjusted accuracy of the move.
|
||||
*/
|
||||
const getMockedMoveAccuracy = (defender: Pokemon, attacker: Pokemon, move: Move) => {
|
||||
const accuracyHolder = new NumberHolder(move.accuracy);
|
||||
|
||||
/**
|
||||
* Simulate ignoring ability
|
||||
* @see MoveAbilityBypassAbAttr
|
||||
*/
|
||||
if (attacker.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) {
|
||||
return accuracyHolder.value;
|
||||
}
|
||||
|
||||
const wonderSkinInstance = new WonderSkinAbAttr();
|
||||
|
||||
wonderSkinInstance.applyPreDefend(defender, false, attacker, move, { value: false }, [ accuracyHolder ]);
|
||||
|
||||
return accuracyHolder.value;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import overrides from "#app/overrides";
|
||||
import { Species } from "#enums/species";
|
||||
import {
|
||||
MoveEffectPhase,
|
||||
TurnEndPhase,
|
||||
} from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { ArenaTagType } from "#app/enums/arena-tag-type.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
|
||||
describe("Arena - Gravity", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.GRAVITY, Moves.FISSURE]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.UNNERVE);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(new Array(4).fill(Moves.SPLASH));
|
||||
});
|
||||
|
||||
it("non-OHKO move accuracy is multiplied by 1.67", async () => {
|
||||
const moveToCheck = allMoves[Moves.TACKLE];
|
||||
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
// Setup Gravity on first turn
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined();
|
||||
|
||||
// Use non-OHKO move on second turn
|
||||
await game.toNextTurn();
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(100 * 1.67);
|
||||
});
|
||||
|
||||
it("OHKO move accuracy is not affected", async () => {
|
||||
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5);
|
||||
vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(5);
|
||||
|
||||
/** See Fissure {@link https://bulbapedia.bulbagarden.net/wiki/Fissure_(move)} */
|
||||
const moveToCheck = allMoves[Moves.FISSURE];
|
||||
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
// Setup Gravity on first turn
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined();
|
||||
|
||||
// Use OHKO move on second turn
|
||||
await game.toNextTurn();
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(30);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import overrides from "#app/overrides";
|
||||
import { Species } from "#enums/species";
|
||||
import {
|
||||
MoveEffectPhase,
|
||||
} from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { WeatherType } from "#app/data/weather.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
|
||||
describe("Weather - Fog", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(WeatherType.FOG);
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(new Array(4).fill(Moves.SPLASH));
|
||||
});
|
||||
|
||||
it("move accuracy is multiplied by 90%", async () => {
|
||||
const moveToCheck = allMoves[Moves.TACKLE];
|
||||
|
||||
vi.spyOn(moveToCheck, "calculateBattleAccuracy");
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
|
||||
expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(100 * 0.9);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import overrides from "#app/overrides";
|
||||
import { Species } from "#enums/species";
|
||||
import {
|
||||
TurnEndPhase,
|
||||
} from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||
import { BattleStat } from "#app/data/battle-stat.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
|
||||
describe("Moves - Double Team", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DOUBLE_TEAM]);
|
||||
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SHUCKLE);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
||||
});
|
||||
|
||||
it("increases the user's evasion by one stage.", async () => {
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const ally = game.scene.getPlayerPokemon();
|
||||
const enemy = game.scene.getEnemyPokemon();
|
||||
|
||||
vi.spyOn(enemy, "getAccuracyMultiplier");
|
||||
expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(0);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_TEAM));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(1);
|
||||
expect(enemy.getAccuracyMultiplier).toHaveReturnedWith(.75);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue