mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-10 17:39:31 +02:00
Merge 83be3961f5
into 167e3ae38f
This commit is contained in:
commit
f4f5325a7a
@ -26,3 +26,4 @@ ignore:
|
||||
- .git
|
||||
- public
|
||||
- dist
|
||||
- .idea
|
||||
|
@ -1982,7 +1982,12 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
|
||||
* @param powerMultiplier - The multiplier to apply to the move's power.
|
||||
*/
|
||||
constructor(boostedCategories: MoveCategory[], powerMultiplier: number) {
|
||||
super((_pokemon, _defender, move) => boostedCategories.includes(move.category), powerMultiplier);
|
||||
super((_pokemon, _defender, move) => {
|
||||
if (_pokemon === null) {
|
||||
return false;
|
||||
}
|
||||
return boostedCategories.includes(_pokemon.getMoveCategory(_defender, move));
|
||||
}, powerMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2114,7 +2119,13 @@ export abstract class PostAttackAbAttr extends AbAttr {
|
||||
|
||||
/** The default `attackCondition` requires that the selected move is a damaging move */
|
||||
constructor(
|
||||
attackCondition: PokemonAttackCondition = (_user, _target, move) => move.category !== MoveCategory.STATUS,
|
||||
attackCondition: PokemonAttackCondition = (_user, _target, move) => {
|
||||
if (!_user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _user.getMoveCategory(_target, move) !== MoveCategory.STATUS;
|
||||
},
|
||||
showAbility = true,
|
||||
) {
|
||||
super(showAbility);
|
||||
@ -6933,7 +6944,12 @@ export function initAbilities() {
|
||||
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.TRUANT, 1, false),
|
||||
new Ability(AbilityId.HUSTLE, 3)
|
||||
.attr(StatMultiplierAbAttr, Stat.ATK, 1.5)
|
||||
.attr(StatMultiplierAbAttr, Stat.ACC, 0.8, (_user, _target, move) => move.category === MoveCategory.PHYSICAL),
|
||||
.attr(StatMultiplierAbAttr, Stat.ACC, 0.8, (_user, _target, move) => {
|
||||
if (_user === null) {
|
||||
return false
|
||||
}
|
||||
return _user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL
|
||||
}),
|
||||
new Ability(AbilityId.CUTE_CHARM, 3)
|
||||
.attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED),
|
||||
new Ability(AbilityId.PLUS, 3)
|
||||
@ -7165,8 +7181,8 @@ export function initAbilities() {
|
||||
.attr(AlliedFieldDamageReductionAbAttr, 0.75)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.WEAK_ARMOR, 5)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2),
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL, Stat.DEF, -1)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL, Stat.SPD, 2),
|
||||
new Ability(AbilityId.HEAVY_METAL, 5)
|
||||
.attr(WeightMultiplierAbAttr, 2)
|
||||
.ignorable(),
|
||||
@ -7177,9 +7193,15 @@ export function initAbilities() {
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, _user, _move) => target.isFullHp(), 0.5)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.TOXIC_BOOST, 5)
|
||||
.attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5),
|
||||
.attr(MovePowerBoostAbAttr, (user, _target, move) => {
|
||||
if (user===null) { return false; }
|
||||
return user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC)
|
||||
}, 1.5),
|
||||
new Ability(AbilityId.FLARE_BOOST, 5)
|
||||
.attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5),
|
||||
.attr(MovePowerBoostAbAttr, (user, _target, move) => {
|
||||
if (user===null) { return false; }
|
||||
return user.getMoveCategory(_target, move) === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN
|
||||
}, 1.5),
|
||||
new Ability(AbilityId.HARVEST, 5)
|
||||
.attr(
|
||||
PostTurnRestoreBerryAbAttr,
|
||||
@ -7240,11 +7262,11 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.MOXIE, 5)
|
||||
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
|
||||
new Ability(AbilityId.JUSTIFIED, 5)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.DARK && user.getMoveCategory(_target, move) !== MoveCategory.STATUS, Stat.ATK, 1),
|
||||
new Ability(AbilityId.RATTLED, 5)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => {
|
||||
const moveType = user.getMoveType(move);
|
||||
return move.category !== MoveCategory.STATUS
|
||||
return user.getMoveCategory(_target, move) !== MoveCategory.STATUS
|
||||
&& (moveType === PokemonType.DARK || moveType === PokemonType.BUG || moveType === PokemonType.GHOST);
|
||||
}, Stat.SPD, 1)
|
||||
.attr(PostIntimidateStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
||||
@ -7258,7 +7280,7 @@ export function initAbilities() {
|
||||
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.GRASS, Stat.ATK, 1)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.PRANKSTER, 5)
|
||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS, 1),
|
||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, move: Move) => _pokemon.getMoveCategory(_pokemon, move) === MoveCategory.STATUS, 1),
|
||||
new Ability(AbilityId.SAND_FORCE, 5)
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK, 1.3)
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.GROUND, 1.3)
|
||||
@ -7311,8 +7333,15 @@ export function initAbilities() {
|
||||
// TODO: needs testing on interaction with weather blockage
|
||||
.edgeCase(),
|
||||
new Ability(AbilityId.FUR_COAT, 6)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, 0.5)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => {
|
||||
const isPhysicalMove = _user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL
|
||||
// This is for a theoretical move where Fur Coat shouldn't apply, in case it's implemented later
|
||||
const moveIsPhysicalAndActuallyHitsDef = isPhysicalMove && !move.hasAttr("VariableDefAttr")
|
||||
const moveIsSpecialButHitsPhysicalInstead = move.hasAttr("DefDefAttr")
|
||||
return moveIsPhysicalAndActuallyHitsDef || moveIsSpecialButHitsPhysicalInstead
|
||||
}, 0.5)
|
||||
.ignorable(),
|
||||
|
||||
new Ability(AbilityId.MAGICIAN, 6)
|
||||
.attr(PostAttackStealHeldItemAbAttr),
|
||||
new Ability(AbilityId.BULLETPROOF, 6)
|
||||
@ -7382,7 +7411,7 @@ export function initAbilities() {
|
||||
.attr(PreLeaveFieldClearWeatherAbAttr)
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.STAMINA, 7)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||
new Ability(AbilityId.WIMP_OUT, 7)
|
||||
.attr(PostDamageForceSwitchAbAttr)
|
||||
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||
@ -7390,7 +7419,7 @@ export function initAbilities() {
|
||||
.attr(PostDamageForceSwitchAbAttr)
|
||||
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||
new Ability(AbilityId.WATER_COMPACTION, 7)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.WATER && user.getMoveCategory(_target, move) !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||
new Ability(AbilityId.MERCILESS, 7)
|
||||
.attr(ConditionalCritAbAttr, (_user, target, _move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
|
||||
new Ability(AbilityId.SHIELDS_DOWN, 7, -1)
|
||||
@ -7419,7 +7448,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.STEELWORKER, 7)
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL),
|
||||
new Ability(AbilityId.BERSERK, 7)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(AbilityId.SLUSH_RUSH, 7)
|
||||
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
||||
@ -7573,7 +7602,7 @@ export function initAbilities() {
|
||||
.attr(FetchBallAbAttr)
|
||||
.condition(getOncePerBattleCondition(AbilityId.BALL_FETCH)),
|
||||
new Ability(AbilityId.COTTON_DOWN, 8)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.SPD, -1, false, true)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, Stat.SPD, -1, false, true)
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.PROPELLER_TAIL, 8)
|
||||
.attr(BlockRedirectAbAttr),
|
||||
@ -7598,7 +7627,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.STEAM_ENGINE, 8)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => {
|
||||
const moveType = user.getMoveType(move);
|
||||
return move.category !== MoveCategory.STATUS
|
||||
return user.getMoveCategory(_target, move) !== MoveCategory.STATUS
|
||||
&& (moveType === PokemonType.FIRE || moveType === PokemonType.WATER);
|
||||
}, Stat.SPD, 6),
|
||||
new Ability(AbilityId.PUNK_ROCK, 8)
|
||||
@ -7606,10 +7635,10 @@ export function initAbilities() {
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.SAND_SPIT, 8)
|
||||
.attr(PostDefendWeatherChangeAbAttr, WeatherType.SANDSTORM, (_target, _user, move) => move.category !== MoveCategory.STATUS)
|
||||
.attr(PostDefendWeatherChangeAbAttr, WeatherType.SANDSTORM, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS)
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.ICE_SCALES, 8)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.SPECIAL, 0.5)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) === MoveCategory.SPECIAL, 0.5)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.RIPEN, 8)
|
||||
.attr(DoubleBerryEffectAbAttr),
|
||||
@ -7623,7 +7652,7 @@ export function initAbilities() {
|
||||
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
|
||||
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
|
||||
.attr(FormBlockDamageAbAttr,
|
||||
(target, _user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
(target, _user, move) => _user.getMoveCategory(target, move) === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
.uncopiable()
|
||||
@ -7703,13 +7732,13 @@ export function initAbilities() {
|
||||
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY)
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.THERMAL_EXCHANGE, 9)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE && user.getMoveCategory(_target, move) !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
||||
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.ANGER_SHELL, 9)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
|
||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(AbilityId.PURIFYING_SALT, 9)
|
||||
.attr(StatusEffectImmunityAbAttr)
|
||||
@ -7719,7 +7748,7 @@ export function initAbilities() {
|
||||
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.FIRE, Stat.DEF, 2)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.WIND_RIDER, 9)
|
||||
.attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||
.attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && pokemon.getMoveCategory(attacker, move) !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||
.attr(PostSummonStatStageChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.GUARD_DOG, 9)
|
||||
@ -7746,7 +7775,7 @@ export function initAbilities() {
|
||||
.unreplaceable()
|
||||
.edgeCase(), // Encore, Frenzy, and other non-`TURN_END` tags don't lapse correctly on the commanding Pokemon.
|
||||
new Ability(AbilityId.ELECTROMORPHOSIS, 9)
|
||||
.attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED),
|
||||
.attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) !== MoveCategory.STATUS, BattlerTagType.CHARGED),
|
||||
new Ability(AbilityId.PROTOSYNTHESIS, 9, -2)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), PostSummonAddBattlerTagAbAttr, BattlerTagType.PROTOSYNTHESIS, 0, true)
|
||||
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.PROTOSYNTHESIS, 0, WeatherType.SUNNY, WeatherType.HARSH_SUN)
|
||||
@ -7760,7 +7789,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.GOOD_AS_GOLD, 9)
|
||||
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) =>
|
||||
pokemon !== attacker
|
||||
&& move.category === MoveCategory.STATUS
|
||||
&& pokemon.getMoveCategory(attacker, move) === MoveCategory.STATUS
|
||||
&& ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget)
|
||||
)
|
||||
.edgeCase() // Heal Bell should not cure the status of a Pokemon with Good As Gold
|
||||
@ -7800,7 +7829,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.COSTAR, 9, -2)
|
||||
.attr(PostSummonCopyAllyStatsAbAttr),
|
||||
new Ability(AbilityId.TOXIC_DEBRIS, 9)
|
||||
.attr(PostDefendApplyArenaTrapTagAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES)
|
||||
.attr(PostDefendApplyArenaTrapTagAbAttr, (_target, _user, move) => _user.getMoveCategory(_target, move) === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES)
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.ARMOR_TAIL, 9)
|
||||
.attr(FieldPriorityMoveImmunityAbAttr)
|
||||
@ -7809,9 +7838,9 @@ export function initAbilities() {
|
||||
.attr(TypeImmunityHealAbAttr, PokemonType.GROUND)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.MYCELIUM_MIGHT, 9)
|
||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
||||
.attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS)
|
||||
.attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, move) => _pokemon.getMoveCategory(_pokemon, move) === MoveCategory.STATUS, -0.2)
|
||||
.attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => _pokemon.getMoveCategory(_pokemon, move) === MoveCategory.STATUS)
|
||||
.attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => _pokemon.getMoveCategory(_pokemon, move) === MoveCategory.STATUS),
|
||||
new Ability(AbilityId.MINDS_EYE, 9)
|
||||
.attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ])
|
||||
.attr(ProtectStatAbAttr, Stat.ACC)
|
||||
|
@ -1,97 +1,101 @@
|
||||
import { AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "#abilities/ability";
|
||||
import {AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams} from "#abilities/ability";
|
||||
import {applyAbAttrs} from "#abilities/apply-ab-attrs";
|
||||
import {loggedInUser} from "#app/account";
|
||||
import type {GameMode} from "#app/game-mode";
|
||||
import {globalScene} from "#app/global-scene";
|
||||
import {getPokemonNameWithAffix} from "#app/messages";
|
||||
import type {ArenaTrapTag} from "#data/arena-tag";
|
||||
import {WeakenMoveTypeTag} from "#data/arena-tag";
|
||||
import {MoveChargeAnim} from "#data/battle-anims";
|
||||
import {
|
||||
applyAbAttrs
|
||||
} from "#abilities/apply-ab-attrs";
|
||||
import { loggedInUser } from "#app/account";
|
||||
import type { GameMode } from "#app/game-mode";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type { ArenaTrapTag } from "#data/arena-tag";
|
||||
import { WeakenMoveTypeTag } from "#data/arena-tag";
|
||||
import { MoveChargeAnim } from "#data/battle-anims";
|
||||
import {
|
||||
CommandedTag,
|
||||
EncoreTag,
|
||||
GulpMissileTag,
|
||||
HelpingHandTag,
|
||||
SemiInvulnerableTag,
|
||||
ShellTrapTag,
|
||||
StockpilingTag,
|
||||
SubstituteTag,
|
||||
TrappedTag,
|
||||
TypeBoostTag,
|
||||
CommandedTag,
|
||||
EncoreTag,
|
||||
GulpMissileTag,
|
||||
HelpingHandTag,
|
||||
SemiInvulnerableTag,
|
||||
ShellTrapTag,
|
||||
StockpilingTag,
|
||||
SubstituteTag,
|
||||
TrappedTag,
|
||||
TypeBoostTag,
|
||||
} from "#data/battler-tags";
|
||||
import { getBerryEffectFunc } from "#data/berry";
|
||||
import { applyChallenges } from "#data/challenge";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers";
|
||||
import { DelayedAttackTag } from "#data/positional-tags/positional-tag";
|
||||
import {getBerryEffectFunc} from "#data/berry";
|
||||
import {applyChallenges} from "#data/challenge";
|
||||
import {allAbilities, allMoves} from "#data/data-lists";
|
||||
import {SpeciesFormChangeRevertWeatherFormTrigger} from "#data/form-change-triggers";
|
||||
import {DelayedAttackTag} from "#data/positional-tags/positional-tag";
|
||||
import {getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect,} from "#data/status-effect";
|
||||
import {TerrainType} from "#data/terrain";
|
||||
import {getTypeDamageMultiplier} from "#data/type";
|
||||
import {AbilityId} from "#enums/ability-id";
|
||||
import {ArenaTagSide} from "#enums/arena-tag-side";
|
||||
import {ArenaTagType} from "#enums/arena-tag-type";
|
||||
import {BattleType} from "#enums/battle-type";
|
||||
import type {BattlerIndex} from "#enums/battler-index";
|
||||
import {BattlerTagType} from "#enums/battler-tag-type";
|
||||
import {BiomeId} from "#enums/biome-id";
|
||||
import {ChallengeType} from "#enums/challenge-type";
|
||||
import {Command} from "#enums/command";
|
||||
import {FieldPosition} from "#enums/field-position";
|
||||
import {HitResult} from "#enums/hit-result";
|
||||
import {ModifierPoolType} from "#enums/modifier-pool-type";
|
||||
import {ChargeAnim} from "#enums/move-anims-common";
|
||||
import {MoveId} from "#enums/move-id";
|
||||
import {MoveResult} from "#enums/move-result";
|
||||
import {isVirtual, MoveUseMode} from "#enums/move-use-mode";
|
||||
import {MoveCategory} from "#enums/move-category";
|
||||
import {MoveEffectTrigger} from "#enums/move-effect-trigger";
|
||||
import {MoveFlags} from "#enums/move-flags";
|
||||
import {MoveTarget} from "#enums/move-target";
|
||||
import {MultiHitType} from "#enums/multi-hit-type";
|
||||
import {PokemonType} from "#enums/pokemon-type";
|
||||
import {PositionalTagType} from "#enums/positional-tag-type";
|
||||
import {SpeciesId} from "#enums/species-id";
|
||||
import {BATTLE_STATS, type BattleStat, type EffectiveStat, getStatKey, Stat,} from "#enums/stat";
|
||||
import {StatusEffect} from "#enums/status-effect";
|
||||
import {SwitchType} from "#enums/switch-type";
|
||||
import {WeatherType} from "#enums/weather-type";
|
||||
import {MoveUsedEvent} from "#events/battle-scene";
|
||||
import type {EnemyPokemon, Pokemon} from "#field/pokemon";
|
||||
import {
|
||||
getNonVolatileStatusEffects,
|
||||
getStatusEffectHealText,
|
||||
isNonVolatileStatusEffect,
|
||||
} from "#data/status-effect";
|
||||
import { TerrainType } from "#data/terrain";
|
||||
import { getTypeDamageMultiplier } from "#data/type";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ChargeAnim } from "#enums/move-anims-common";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveEffectTrigger } from "#enums/move-effect-trigger";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { MultiHitType } from "#enums/multi-hit-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { PositionalTagType } from "#enums/positional-tag-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import {
|
||||
BATTLE_STATS,
|
||||
type BattleStat,
|
||||
type EffectiveStat,
|
||||
getStatKey,
|
||||
Stat,
|
||||
} from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { MoveUsedEvent } from "#events/battle-scene";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BerryModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonMoveAccuracyBoosterModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PreserveBerryModifier,
|
||||
AttackTypeBoosterModifier,
|
||||
BerryModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonMoveAccuracyBoosterModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves";
|
||||
import { frenzyMissFunc, getMoveTargets } from "#moves/move-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { MoveEndPhase } from "#phases/move-end-phase";
|
||||
import { MovePhase } from "#phases/move-phase";
|
||||
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import {applyMoveAttrs} from "#moves/apply-attrs";
|
||||
import {
|
||||
invalidAssistMoves,
|
||||
invalidCopycatMoves,
|
||||
invalidMetronomeMoves,
|
||||
invalidMirrorMoveMoves,
|
||||
invalidSketchMoves,
|
||||
invalidSleepTalkMoves
|
||||
} from "#moves/invalid-moves";
|
||||
import {frenzyMissFunc, getMoveTargets} from "#moves/move-utils";
|
||||
import {PokemonMove} from "#moves/pokemon-move";
|
||||
import {MoveEndPhase} from "#phases/move-end-phase";
|
||||
import {MovePhase} from "#phases/move-phase";
|
||||
import {PokemonHealPhase} from "#phases/pokemon-heal-phase";
|
||||
import {SwitchSummonPhase} from "#phases/switch-summon-phase";
|
||||
import type {AttackMoveResult} from "#types/attack-move-result";
|
||||
import type {Localizable} from "#types/locales";
|
||||
import type {ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString} from "#types/move-types";
|
||||
import type {TurnMove} from "#types/turn-move";
|
||||
import {
|
||||
BooleanHolder,
|
||||
type Constructor,
|
||||
isNullOrUndefined,
|
||||
NumberHolder,
|
||||
randSeedFloat,
|
||||
randSeedInt,
|
||||
randSeedItem,
|
||||
toDmgValue
|
||||
} from "#utils/common";
|
||||
import {getEnumValues} from "#utils/enums";
|
||||
import {toTitleCase} from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
@ -1626,7 +1630,7 @@ export class MatchHpAttr extends FixedDamageAttr {
|
||||
}*/
|
||||
}
|
||||
|
||||
type MoveFilter = (move: Move) => boolean;
|
||||
type MoveFilter = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||
|
||||
export class CounterDamageAttr extends FixedDamageAttr {
|
||||
private moveFilter: MoveFilter;
|
||||
@ -1640,14 +1644,14 @@ export class CounterDamageAttr extends FixedDamageAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: number, ar: AttackMoveResult) => total + ar.damage, 0);
|
||||
const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(user, target, allMoves[ar.move])).reduce((total: number, ar: AttackMoveResult) => total + ar.damage, 0);
|
||||
(args[0] as NumberHolder).value = toDmgValue(damage * this.multiplier);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).length;
|
||||
return (user, target, move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(user, target, allMoves[ar.move])).length;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2534,10 +2538,10 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance;
|
||||
const quiet = move.category !== MoveCategory.STATUS;
|
||||
const quiet = user.getMoveCategory(target, move) !== MoveCategory.STATUS;
|
||||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, quiet, false, user, true)) {
|
||||
if (user !== target && user.getMoveCategory(target, move) === MoveCategory.STATUS && !target.canSetStatus(this.effect, quiet, false, user, true)) {
|
||||
return false;
|
||||
}
|
||||
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||
@ -5882,7 +5886,7 @@ export class ConfuseAttr extends AddBattlerTagAttr {
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!this.selfTarget && target.isSafeguarded(user)) {
|
||||
if (move.category === MoveCategory.STATUS) {
|
||||
if (user.getMoveCategory(target, move) === MoveCategory.STATUS) {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
return false;
|
||||
@ -5916,8 +5920,8 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
for (const turnMove of user.getLastXMoves(-1).slice()) {
|
||||
if (
|
||||
// Quick & Wide guard increment the Protect counter without using it for fail chance
|
||||
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
|
||||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
|
||||
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
|
||||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
|
||||
turnMove.result !== MoveResult.SUCCESS
|
||||
) {
|
||||
break;
|
||||
@ -6498,7 +6502,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => (move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move));
|
||||
return (user, target, move) => (user.getMoveCategory(target, move) !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move));
|
||||
}
|
||||
|
||||
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
||||
@ -6555,7 +6559,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
return !this.isBatonPass()
|
||||
&& globalScene.currentBattle.waveIndex % 10 !== 0
|
||||
// Don't allow wild mons to flee with U-turn et al.
|
||||
&& !(this.selfSwitch && MoveCategory.STATUS !== move.category);
|
||||
&& !(this.selfSwitch && MoveCategory.STATUS !== user.getMoveCategory(target, move));
|
||||
}
|
||||
|
||||
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
|
||||
@ -8685,7 +8689,7 @@ export function initMoves() {
|
||||
new AttackMove(MoveId.LOW_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1)
|
||||
.attr(WeightPowerAttr),
|
||||
new AttackMove(MoveId.COUNTER, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, -5, 1)
|
||||
.attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.PHYSICAL, 2)
|
||||
.attr(CounterDamageAttr, (user: Pokemon, target: Pokemon, move: Move) => user.getMoveCategory(target, move) === MoveCategory.PHYSICAL, 2)
|
||||
.target(MoveTarget.ATTACKER),
|
||||
new AttackMove(MoveId.SEISMIC_TOSS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1)
|
||||
.attr(LevelDamageAttr),
|
||||
@ -9250,7 +9254,7 @@ export function initMoves() {
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
|
||||
.bitingMove(),
|
||||
new AttackMove(MoveId.MIRROR_COAT, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, -5, 2)
|
||||
.attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.SPECIAL, 2)
|
||||
.attr(CounterDamageAttr, (user: Pokemon, target: Pokemon, move: Move) => user.getMoveCategory(target, move) === MoveCategory.SPECIAL, 2)
|
||||
.target(MoveTarget.ATTACKER),
|
||||
new StatusMove(MoveId.PSYCH_UP, PokemonType.NORMAL, -1, 10, -1, 0, 2)
|
||||
.ignoresSubstitute()
|
||||
@ -9661,7 +9665,7 @@ export function initMoves() {
|
||||
.attr(AcupressureStatStageChangeAttr)
|
||||
.target(MoveTarget.USER_OR_NEAR_ALLY),
|
||||
new AttackMove(MoveId.METAL_BURST, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4)
|
||||
.attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5)
|
||||
.attr(CounterDamageAttr, (user: Pokemon, target: Pokemon, move: Move) => (user.getMoveCategory(target, move) === MoveCategory.PHYSICAL || user.getMoveCategory(target, move) === MoveCategory.SPECIAL), 1.5)
|
||||
.redirectCounter()
|
||||
.makesContact(false)
|
||||
.target(MoveTarget.ATTACKER),
|
||||
@ -11441,7 +11445,7 @@ export function initMoves() {
|
||||
return !turnMove.length || turnMove[0].move !== move.id || turnMove[0].result !== MoveResult.SUCCESS;
|
||||
}), // TODO Add Instruct/Encore interaction
|
||||
new AttackMove(MoveId.COMEUPPANCE, PokemonType.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
||||
.attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5)
|
||||
.attr(CounterDamageAttr, (user: Pokemon, target: Pokemon, move: Move) => (user.getMoveCategory(target, move) === MoveCategory.PHYSICAL || user.getMoveCategory(target, move) === MoveCategory.SPECIAL), 1.5)
|
||||
.redirectCounter()
|
||||
.target(MoveTarget.ATTACKER),
|
||||
new AttackMove(MoveId.AQUA_CUTTER, PokemonType.WATER, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 9)
|
||||
|
@ -1403,7 +1403,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @returns The given move's final category
|
||||
*/
|
||||
getMoveCategory(target: Pokemon, move: Move): MoveCategory {
|
||||
getMoveCategory(target: Pokemon | null, move: Move): MoveCategory {
|
||||
const moveCategory = new NumberHolder(move.category);
|
||||
applyMoveAttrs("VariableMoveCategoryAttr", this, target, move, moveCategory);
|
||||
return moveCategory.value;
|
||||
|
124
test/abilities/fur-coat.test.ts
Normal file
124
test/abilities/fur-coat.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { GameManager } from "#test/test-utils/game-manager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Ability - Fur Coat", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleStyle("single")
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.startingLevel(50)
|
||||
.enemySpecies(SpeciesId.CHANSEY)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemyPassiveAbility(AbilityId.NONE)
|
||||
.enemyMoveset([MoveId.SPLASH])
|
||||
.enemyLevel(50)
|
||||
.criticalHits(false);
|
||||
});
|
||||
|
||||
function damageAfterShouldBeAboutHalfOfDamageBefore(damageAfter: number, damageBefore: number) {
|
||||
const halfDamage = damageBefore * 0.5;
|
||||
const difference = Math.abs(damageAfter - halfDamage);
|
||||
// Can't use `toBeCloseTo` because they're exactly 0.5 apart, and we still get rounding errors
|
||||
expect(difference).toBeLessThan(0.6);
|
||||
}
|
||||
|
||||
it("should reduce damage from a physical move after gaining Fur Coat", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
// Use Tackle before Fur Coat
|
||||
game.move.use(MoveId.TACKLE);
|
||||
await game.toEndOfTurn();
|
||||
const damageBefore = enemy.getMaxHp() - enemy.hp;
|
||||
// Give Fur Coat
|
||||
game.field.mockAbility(enemy, AbilityId.FUR_COAT);
|
||||
enemy.hp = enemy.getMaxHp();
|
||||
// Use Tackle after Fur Coat
|
||||
game.move.use(MoveId.TACKLE);
|
||||
await game.toEndOfTurn();
|
||||
const damageAfter = enemy.getMaxHp() - enemy.hp;
|
||||
damageAfterShouldBeAboutHalfOfDamageBefore(damageAfter, damageBefore);
|
||||
});
|
||||
|
||||
it("should not reduce damage from a special move", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
// Use Scald before Fur Coat
|
||||
game.move.use(MoveId.SCALD);
|
||||
await game.toEndOfTurn();
|
||||
const damageBefore = enemy.getMaxHp() - enemy.hp;
|
||||
// Give Fur Coat
|
||||
game.field.mockAbility(enemy, AbilityId.FUR_COAT);
|
||||
enemy.hp = enemy.getMaxHp();
|
||||
// Use Scald after Fur Coat
|
||||
game.move.use(MoveId.SCALD);
|
||||
await game.toEndOfTurn();
|
||||
const damageAfter = enemy.getMaxHp() - enemy.hp;
|
||||
expect(damageAfter).toBe(damageBefore);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ moveName: "Psyshock", moveId: MoveId.PSYSHOCK },
|
||||
{
|
||||
moveName: "Psystrike",
|
||||
moveId: MoveId.PSYSTRIKE,
|
||||
},
|
||||
{
|
||||
moveName: "Secret Sword",
|
||||
moveId: MoveId.SECRET_SWORD,
|
||||
},
|
||||
])("should reduce damage from $moveName after gaining Fur Coat", async ({ moveId }) => {
|
||||
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
|
||||
game.move.use(moveId);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
const attackDamageBefore = enemy.getMaxHp() - enemy.hp;
|
||||
|
||||
game.field.mockAbility(enemy, AbilityId.FUR_COAT);
|
||||
enemy.hp = enemy.getMaxHp();
|
||||
|
||||
game.move.use(moveId);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
const attackDamageAfter = enemy.getMaxHp() - enemy.hp;
|
||||
damageAfterShouldBeAboutHalfOfDamageBefore(attackDamageAfter, attackDamageBefore);
|
||||
});
|
||||
|
||||
it("should reduce damage from Shell Side Arm", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.HERACROSS]);
|
||||
game.override.enemyAbility(AbilityId.IMMUNITY);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
|
||||
// Use Shell Side Arm before Fur Coat
|
||||
game.move.use(MoveId.SHELL_SIDE_ARM);
|
||||
await game.toEndOfTurn();
|
||||
const damageBefore = enemy.getMaxHp() - enemy.hp;
|
||||
// Give Fur Coat
|
||||
game.field.mockAbility(enemy, AbilityId.FUR_COAT);
|
||||
enemy.hp = enemy.getMaxHp();
|
||||
// Use Shell Side Arm after Fur Coat
|
||||
game.move.use(MoveId.SHELL_SIDE_ARM);
|
||||
await game.toEndOfTurn();
|
||||
const damageAfter = enemy.getMaxHp() - enemy.hp;
|
||||
damageAfterShouldBeAboutHalfOfDamageBefore(damageAfter, damageBefore);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user