mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-16 21:32:18 +02:00
Squashed commit of the following:
Merge branch 'main' into damp_test Cleanup tests Add tests for interaction with Aftermath Prevent ability popup when suppressing Aftermath damage Adds test for damp visual functionality Fix missing import Prevent explosion effect and animation with Damp Split the PostFaintContactDamage portion of Damp into its own AbAttr Standardize Damp effect via MoveAttr
This commit is contained in:
parent
6d515d58b5
commit
914b41aaf7
@ -1115,13 +1115,6 @@ export class VariableMovePowerAbAttr extends PreAttackAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
|
||||
cancelled.value = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a BattleStat if the checked Pokemon lacks this ability.
|
||||
* If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking.
|
||||
@ -3639,10 +3632,11 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
|
||||
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
|
||||
pokemon.scene.getField(true).forEach(p => applyAbAttrs(PreventPostFaintContactDamageAbAttr, p, cancelled));
|
||||
if (cancelled.value || attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
|
||||
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
|
||||
return true;
|
||||
@ -3656,6 +3650,19 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the effects of another Pokemon's {@link PostFaintContactDamageAbAttr} ability.
|
||||
* {@linkcode apply} always returns true.
|
||||
*/
|
||||
export class PreventPostFaintContactDamageAbAttr extends AbAttr {
|
||||
showAbility: boolean = false;
|
||||
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
cancelled.value = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used for abilities (Innards Out) that damage the opponent based on how much HP the last attack used to knock out the owner of the ability.
|
||||
*/
|
||||
@ -3711,6 +3718,36 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
|
||||
|
||||
export class BlockRedirectAbAttr extends AbAttr { }
|
||||
|
||||
/**
|
||||
* Prevents other Pokemon from using moves that match the given {@linkcode moveCondition}.
|
||||
*
|
||||
* @param args [0] {@linkcode Move} The move being checked
|
||||
* @param args [1] {@linkcode Pokemon} The user of the attack
|
||||
*/
|
||||
export class FieldPreventMovesAbAttr extends AbAttr {
|
||||
public moveCondition: (Moves) => boolean;
|
||||
|
||||
constructor(moveCondition: (Moves) => boolean) {
|
||||
super();
|
||||
this.moveCondition = moveCondition;
|
||||
}
|
||||
|
||||
/** @param args See {@linkcode FieldPreventMovesAbAttr}. */
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (this.moveCondition((args[0] as Move).id)) {
|
||||
cancelled.value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param args See {@linkcode FieldPreventMovesAbAttr}. */
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
return (getPokemonMessage(args[1] as Pokemon, ` cannot use ${(args[0] as Move).name}`));
|
||||
}
|
||||
}
|
||||
|
||||
export class ReduceStatusEffectDurationAbAttr extends AbAttr {
|
||||
private statusEffect: StatusEffect;
|
||||
|
||||
@ -4137,7 +4174,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
const message = attr.getTriggerMessage(pokemon, ability.name, args);
|
||||
const message = attr.getTriggerMessage(pokemon, (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name, args);
|
||||
if (message) {
|
||||
pokemon.scene.queueMessage(message);
|
||||
}
|
||||
@ -4320,7 +4357,8 @@ export function initAbilities() {
|
||||
.attr(BlockOneHitKOAbAttr)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.DAMP, 3)
|
||||
.attr(FieldPreventExplosiveMovesAbAttr)
|
||||
.attr(FieldPreventMovesAbAttr, (move) => [Moves.EXPLOSION, Moves.SELF_DESTRUCT, Moves.MIND_BLOWN, Moves.MISTY_EXPLOSION].includes(move))
|
||||
.attr(PreventPostFaintContactDamageAbAttr)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.LIMBER, 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS)
|
||||
|
@ -10,7 +10,7 @@ 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, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability";
|
||||
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, ForceSwitchOutImmunityAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability";
|
||||
import { allAbilities } from "./ability";
|
||||
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
|
||||
import { BattlerIndex, BattleType } from "../battle";
|
||||
@ -96,6 +96,7 @@ export enum MoveFlags {
|
||||
* Indicates a move is able to be redirected to allies in a double battle if the attacker faints
|
||||
*/
|
||||
REDIRECT_COUNTER = 1 << 18,
|
||||
EXPLOSIVE_MOVE = 1 << 19
|
||||
}
|
||||
|
||||
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||
@ -520,6 +521,17 @@ export default class Move implements Localizable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@linkcode MoveFlags.EXPLOSIVE_MOVE} flag for the calling Move
|
||||
* @param explosiveMove The value (boolean) to set the flag to
|
||||
* example: @see {@linkcode Moves.EXPLOSION}
|
||||
* @returns The {@linkcode Move} that called this function
|
||||
*/
|
||||
explosiveMove(explosiveMove?: boolean): this {
|
||||
this.setFlag(MoveFlags.EXPLOSIVE_MOVE, explosiveMove);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move
|
||||
* @param triageMove The value (boolean) to set the flag to
|
||||
@ -5916,16 +5928,6 @@ const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.i
|
||||
|
||||
const failOnMaxCondition: MoveConditionFunc = (user, target, move) => !target.isMax();
|
||||
|
||||
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
|
||||
// Queue a message if an ability prevented usage of the move
|
||||
if (cancelled.value) {
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:cannotUseMove", {pokemonName: getPokemonNameWithAffix(user), moveName: move.name}));
|
||||
}
|
||||
return !cancelled.value;
|
||||
};
|
||||
|
||||
const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP || user.hasAbility(Abilities.COMATOSE);
|
||||
|
||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
||||
@ -6470,7 +6472,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.SELF_DESTRUCT, Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1)
|
||||
.attr(SacrificialAttr)
|
||||
.makesContact(false)
|
||||
.condition(failIfDampCondition)
|
||||
.explosiveMove()
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.EGG_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 100, 75, 10, -1, 0, 1)
|
||||
.makesContact(false)
|
||||
@ -6560,9 +6562,9 @@ export function initMoves() {
|
||||
new AttackMove(Moves.CRABHAMMER, Type.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1)
|
||||
.attr(HighCritAttr),
|
||||
new AttackMove(Moves.EXPLOSION, Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, 0, 1)
|
||||
.condition(failIfDampCondition)
|
||||
.attr(SacrificialAttr)
|
||||
.makesContact(false)
|
||||
.explosiveMove()
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.FURY_SWIPES, Type.NORMAL, MoveCategory.PHYSICAL, 18, 80, 15, -1, 0, 1)
|
||||
.attr(MultiHitAttr),
|
||||
@ -8184,8 +8186,8 @@ export function initMoves() {
|
||||
.ignoresVirtual(),
|
||||
/* End Unused */
|
||||
new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7)
|
||||
.condition(failIfDampCondition)
|
||||
.attr(HalfSacrificialAttr)
|
||||
.explosiveMove()
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.PLASMA_FISTS, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, -1, 0, 7)
|
||||
.punchingMove()
|
||||
@ -8466,7 +8468,7 @@ export function initMoves() {
|
||||
.attr(SacrificialAttr)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1)
|
||||
.condition(failIfDampCondition)
|
||||
.explosiveMove()
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.GRASSY_GLIDE, Type.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8)
|
||||
.attr(IncrementMovePriorityAttr,(user,target,move) =>user.scene.arena.getTerrainType()===TerrainType.GRASSY&&user.isGrounded()),
|
||||
|
@ -22,8 +22,8 @@ import { BattleStat } from "../data/battle-stat";
|
||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags";
|
||||
import { WeatherType } from "../data/weather";
|
||||
import { TempBattleStat } from "../data/temp-battle-stat";
|
||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, 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, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability";
|
||||
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, 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, FieldPreventMovesAbAttr } from "../data/ability";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Mode } from "../ui/ui";
|
||||
@ -1942,6 +1942,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
typeMultiplier.value = 0;
|
||||
}
|
||||
|
||||
// Check if other Pokemon on the field have an ability that prevents this move
|
||||
for (const other of this.scene.getField(true).filter(p => source.id !== p.id)) {
|
||||
applyAbAttrs(FieldPreventMovesAbAttr, other, cancelled, move, source);
|
||||
}
|
||||
|
||||
// Apply arena tags for conditional protection
|
||||
if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) {
|
||||
this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
|
||||
|
@ -25,7 +25,7 @@ import { Starter } from "./ui/starter-select-ui-handler";
|
||||
import { Gender } from "./data/gender";
|
||||
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
|
||||
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
|
||||
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 { 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, FieldPreventMovesAbAttr } from "./data/ability";
|
||||
import { Unlockables, getUnlockableName } from "./system/unlockables";
|
||||
import { getBiomeKey } from "./field/arena";
|
||||
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
|
||||
@ -3048,6 +3048,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
// Check if other Pokemon on the field have an ability that prevents this move
|
||||
const prevented = new Utils.BooleanHolder(false);
|
||||
for (const other of this.scene.getField(true).filter(p => user.id !== p.id)) {
|
||||
applyAbAttrs(FieldPreventMovesAbAttr, other, prevented, move, user as Pokemon);
|
||||
}
|
||||
if (prevented.value) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
/** All move effect attributes are chained together in this array to be applied asynchronously. */
|
||||
const applyAttrs: Promise<void>[] = [];
|
||||
|
||||
|
80
src/test/abilities/aftermath.test.ts
Normal file
80
src/test/abilities/aftermath.test.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
||||
import * as overrides from "#app/overrides";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Species } from "#enums/species";
|
||||
import { TurnEndPhase } from "#app/phases.js";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Abilities - Aftermath", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const moveToUse = Moves.SPLASH;
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BATTLE_BOND);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
||||
});
|
||||
|
||||
it("deals 25% of attacker's HP as damage to attacker when defeated by contact move", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
const enemyAbility = Abilities.AFTERMATH;
|
||||
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BIDOOF);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
|
||||
|
||||
await game.startBattle();
|
||||
|
||||
game.scene.getEnemyParty()[0].hp = 1;
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.phaseInterceptor.log).toContain("FaintPhase");
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
expect(game.scene.getParty()[0].hp).toBeCloseTo(Math.floor(game.scene.getParty()[0].getMaxHp() * 0.75));
|
||||
}, TIMEOUT);
|
||||
|
||||
it("does not activate on non-contact moves", async () => {
|
||||
const moveToUse = Moves.WATER_GUN;
|
||||
const enemyAbility = Abilities.AFTERMATH;
|
||||
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BIDOOF);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
|
||||
|
||||
await game.startBattle();
|
||||
|
||||
game.scene.getEnemyParty()[0].hp = 1;
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.phaseInterceptor.log).toContain("FaintPhase");
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
expect(game.scene.getParty()[0].getHpRatio()).toBeCloseTo(1);
|
||||
}, TIMEOUT);
|
||||
});
|
100
src/test/abilities/damp.test.ts
Normal file
100
src/test/abilities/damp.test.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import * as overrides from "#app/overrides";
|
||||
import { TurnEndPhase } from "#app/phases";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Abilities - Damp", () => {
|
||||
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, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("prevents self-destruction effect on explosive attacks", async() => {
|
||||
const moveToUse = Moves.EXPLOSION;
|
||||
const enemyAbility = Abilities.DAMP;
|
||||
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BIDOOF);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
|
||||
|
||||
await game.startBattle();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
expect(game.phaseInterceptor.log).not.toContain("FaintPhase");
|
||||
}, TIMEOUT);
|
||||
|
||||
// Invalid if aftermath.test.ts has a failure.
|
||||
it("silently prevents Aftermath from triggering", async() => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
const playerAbility = Abilities.DAMP;
|
||||
const enemyAbility = Abilities.AFTERMATH;
|
||||
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(playerAbility);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BIDOOF);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
|
||||
|
||||
await game.startBattle();
|
||||
|
||||
game.scene.getEnemyParty()[0].hp = 1;
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.phaseInterceptor.log).toContain("FaintPhase");
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
expect(game.scene.getParty()[0].getHpRatio()).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
// Ensures fix of #1476.
|
||||
it("does not show ability popup during AI calculations", async() => {
|
||||
const moveToUse = Moves.EXPLOSION;
|
||||
const enemyAbility = Abilities.DAMP;
|
||||
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BIDOOF);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
|
||||
|
||||
await game.startBattle();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
}, TIMEOUT);
|
||||
|
||||
// TODO Test some of the other AbAttrs that use `args`
|
||||
// BattlerTagImmunityAbAttr, StatusEffectImmunityAbAttr
|
||||
});
|
Loading…
Reference in New Issue
Block a user