mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 09:02:47 +02:00
[Bug] Fix moves/abilities which modify abilities (#5146)
* Add setAbility method to pokemon.ts * Edit SwitchAbilitiesAttr to use setAbility * Change AbilityGiveAttr to use setAbility * Rename setAbility to be more accurate * Fix AbilityCopyAttr * Fix AbilityChangeAttr * Fix Transform * Fix imposter * Fix PostDefendAbilityGiveAbAttr * Actually fix imposter * Actually fix transform * Fix CopyFaintedAllyAbilityAbAttr * Fix Trace * Fix PostDefendAbilitySwapAbAttr * Add tests for skill swap * Add tests for doodle * Add tests for entrainment * Add tests for role play * Add test for simple beam * Add test for transform * Add test for imposter * Add tests for mummy * Add tests for trace * Add tests for wandering spirit * Consider legendary weather when changing ability * Ensure that passives are not (re)applied when main abilities change * Add general ability swap test cases * Fix test name * Add NoMidTurnActivationAttr * Remove NoMidTurnActivationAttr from illusion * Remove extraneous call to triggerWeatherBasedFormChanges * Fix primal weather clearing * Change "MidTurn" to "OnGain" * Change NoOnGainActivationAttr to a field in PostSummonAbAttr * Add passive support * Remove redundant parentheses Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: damocleas <damocleas25@gmail.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
7fafccf8de
commit
9cc1b17745
@ -1044,9 +1044,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
|||||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
||||||
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
const tempAbilityId = attacker.getAbility().id;
|
const tempAbility = attacker.getAbility();
|
||||||
attacker.summonData.ability = pokemon.getAbility().id;
|
attacker.setTempAbility(pokemon.getAbility());
|
||||||
pokemon.summonData.ability = tempAbilityId;
|
pokemon.setTempAbility(tempAbility);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1071,7 +1071,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
|||||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
|
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
|
||||||
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
attacker.summonData.ability = this.ability;
|
attacker.setTempAbility(allAbilities[this.ability]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -1908,8 +1908,8 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr {
|
|||||||
applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise<boolean> {
|
applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise<boolean> {
|
||||||
if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) {
|
if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
pokemon.summonData.ability = knockedOut.getAbility().id;
|
|
||||||
globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name }));
|
globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name }));
|
||||||
|
pokemon.setTempAbility(knockedOut.getAbility());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1993,6 +1993,21 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr {
|
|||||||
* @see {@linkcode applyPostSummon()}
|
* @see {@linkcode applyPostSummon()}
|
||||||
*/
|
*/
|
||||||
export class PostSummonAbAttr extends AbAttr {
|
export class PostSummonAbAttr extends AbAttr {
|
||||||
|
/** Should the ability activate when gained in battle? This will almost always be true */
|
||||||
|
private activateOnGain: boolean;
|
||||||
|
|
||||||
|
constructor(showAbility: boolean = true, activateOnGain: boolean = true) {
|
||||||
|
super(showAbility);
|
||||||
|
this.activateOnGain = activateOnGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Whether the ability should activate when gained in battle
|
||||||
|
*/
|
||||||
|
shouldActivateOnGain(): boolean {
|
||||||
|
return this.activateOnGain;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies ability post summon (after switching in)
|
* Applies ability post summon (after switching in)
|
||||||
* @param pokemon {@linkcode Pokemon} with this ability
|
* @param pokemon {@linkcode Pokemon} with this ability
|
||||||
@ -2330,7 +2345,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
|
|||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
this.target = target!;
|
this.target = target!;
|
||||||
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
|
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
|
||||||
pokemon.summonData.ability = target!.getAbility().id;
|
pokemon.setTempAbility(target!.getAbility());
|
||||||
setAbilityRevealed(target!);
|
setAbilityRevealed(target!);
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
}
|
}
|
||||||
@ -2427,7 +2442,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr {
|
|||||||
*/
|
*/
|
||||||
export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true);
|
super(true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): Promise<boolean> {
|
async applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): Promise<boolean> {
|
||||||
@ -2462,7 +2477,6 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pokemon.summonData.speciesForm = target.getSpeciesForm();
|
pokemon.summonData.speciesForm = target.getSpeciesForm();
|
||||||
pokemon.summonData.ability = target.getAbility().id;
|
|
||||||
pokemon.summonData.gender = target.getGender();
|
pokemon.summonData.gender = target.getGender();
|
||||||
|
|
||||||
// Copy all stats (except HP)
|
// Copy all stats (except HP)
|
||||||
@ -2492,6 +2506,8 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|||||||
promises.push(pokemon.loadAssets(false).then(() => {
|
promises.push(pokemon.loadAssets(false).then(() => {
|
||||||
pokemon.playAnim();
|
pokemon.playAnim();
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
|
// If the new ability activates immediately, it needs to happen after all the transform animations
|
||||||
|
pokemon.setTempAbility(target.getAbility());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
@ -4852,13 +4868,34 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
|||||||
showAbilityInstant: boolean = false,
|
showAbilityInstant: boolean = false,
|
||||||
simulated: boolean = false,
|
simulated: boolean = false,
|
||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
|
gainedMidTurn: boolean = false
|
||||||
) {
|
) {
|
||||||
for (const passive of [ false, true ]) {
|
for (const passive of [ false, true ]) {
|
||||||
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages);
|
||||||
|
globalScene.clearPhaseQueueSplice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applySingleAbAttrs<TAttr extends AbAttr>(
|
||||||
|
pokemon: Pokemon,
|
||||||
|
passive: boolean,
|
||||||
|
attrType: Constructor<TAttr>,
|
||||||
|
applyFunc: AbAttrApplyFunc<TAttr>,
|
||||||
|
args: any[],
|
||||||
|
gainedMidTurn: boolean = false,
|
||||||
|
simulated: boolean = false,
|
||||||
|
showAbilityInstant: boolean = false,
|
||||||
|
messages: string[] = []
|
||||||
|
) {
|
||||||
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
||||||
|
if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const attr of ability.getAttrs(attrType)) {
|
for (const attr of ability.getAttrs(attrType)) {
|
||||||
const condition = attr.getCondition();
|
const condition = attr.getCondition();
|
||||||
if (condition && !condition(pokemon)) {
|
if (condition && !condition(pokemon)) {
|
||||||
@ -4895,8 +4932,6 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
|||||||
messages.push(message!);
|
messages.push(message!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalScene.clearPhaseQueueSplice();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ForceSwitchOutHelper {
|
class ForceSwitchOutHelper {
|
||||||
@ -5285,6 +5320,21 @@ export function applyPostItemLostAbAttrs(attrType: Constructor<PostItemLostAbAtt
|
|||||||
return applyAbAttrsInternal<PostItemLostAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), args);
|
return applyAbAttrsInternal<PostItemLostAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies abilities when they become active mid-turn (ability switch)
|
||||||
|
*
|
||||||
|
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
||||||
|
*/
|
||||||
|
export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void {
|
||||||
|
applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears primal weather during the turn if {@linkcode pokemon}'s ability corresponds to one
|
||||||
|
*/
|
||||||
|
export function applyOnLoseClearWeatherAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void {
|
||||||
|
applySingleAbAttrs<PreLeaveFieldClearWeatherAbAttr>(pokemon, passive, PreLeaveFieldClearWeatherAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
|
||||||
|
}
|
||||||
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
|
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
|
||||||
globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive));
|
globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive));
|
||||||
globalScene.clearPhaseQueueSplice();
|
globalScene.clearPhaseQueueSplice();
|
||||||
|
@ -7521,11 +7521,11 @@ export class AbilityChangeAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const moveTarget = this.selfTarget ? user : target;
|
const moveTarget = this.selfTarget ? user : target;
|
||||||
|
|
||||||
moveTarget.summonData.ability = this.ability;
|
|
||||||
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
|
|
||||||
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name }));
|
||||||
|
|
||||||
|
moveTarget.setTempAbility(allAbilities[this.ability]);
|
||||||
|
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7548,13 +7548,13 @@ export class AbilityCopyAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.summonData.ability = target.getAbility().id;
|
|
||||||
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
|
||||||
|
|
||||||
|
user.setTempAbility(target.getAbility());
|
||||||
|
|
||||||
if (this.copyToPartner && globalScene.currentBattle?.double && user.getAlly().hp) {
|
if (this.copyToPartner && globalScene.currentBattle?.double && user.getAlly().hp) {
|
||||||
user.getAlly().summonData.ability = target.getAbility().id;
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
|
||||||
|
user.getAlly().setTempAbility(target.getAbility());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -7585,10 +7585,10 @@ export class AbilityGiveAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
target.summonData.ability = user.getAbility().id;
|
|
||||||
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name }));
|
||||||
|
|
||||||
|
target.setTempAbility(user.getAbility());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7603,15 +7603,14 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempAbilityId = user.getAbility().id;
|
const tempAbility = user.getAbility();
|
||||||
user.summonData.ability = target.getAbility().id;
|
|
||||||
target.summonData.ability = tempAbilityId;
|
|
||||||
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) }));
|
globalScene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) }));
|
||||||
|
|
||||||
|
user.setTempAbility(target.getAbility());
|
||||||
|
target.setTempAbility(tempAbility);
|
||||||
// Swaps Forecast/Flower Gift from Castform/Cherrim
|
// Swaps Forecast/Flower Gift from Castform/Cherrim
|
||||||
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
|
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
|
||||||
// Swaps Forecast/Flower Gift to Castform/Cherrim (edge case)
|
|
||||||
globalScene.arena.triggerWeatherBasedFormChanges();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -7690,7 +7689,6 @@ export class TransformAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
user.summonData.speciesForm = target.getSpeciesForm();
|
user.summonData.speciesForm = target.getSpeciesForm();
|
||||||
user.summonData.ability = target.getAbility().id;
|
|
||||||
user.summonData.gender = target.getGender();
|
user.summonData.gender = target.getGender();
|
||||||
|
|
||||||
// Power Trick's effect will not preserved after using Transform
|
// Power Trick's effect will not preserved after using Transform
|
||||||
@ -7723,6 +7721,8 @@ export class TransformAttr extends MoveEffectAttr {
|
|||||||
promises.push(user.loadAssets(false).then(() => {
|
promises.push(user.loadAssets(false).then(() => {
|
||||||
user.playAnim();
|
user.playAnim();
|
||||||
user.updateInfo();
|
user.updateInfo();
|
||||||
|
// If the new ability activates immediately, it needs to happen after all the transform animations
|
||||||
|
user.setTempAbility(target.getAbility());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -64,7 +64,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo
|
|||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import type { Ability, AbAttr } from "#app/data/ability";
|
import type { Ability, AbAttr } from "#app/data/ability";
|
||||||
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs } from "#app/data/ability";
|
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseClearWeatherAbAttrs } from "#app/data/ability";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
@ -1481,6 +1481,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return abilityAttrs;
|
return abilityAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@linkcode Pokemon}'s ability and activates it if it normally activates on summon
|
||||||
|
*
|
||||||
|
* Also clears primal weather if it is from the ability being changed
|
||||||
|
* @param ability New Ability
|
||||||
|
*/
|
||||||
|
public setTempAbility(ability: Ability, passive: boolean = false): void {
|
||||||
|
applyOnLoseClearWeatherAbAttrs(this, passive);
|
||||||
|
if (passive) {
|
||||||
|
this.summonData.passiveAbility = ability.id;
|
||||||
|
} else {
|
||||||
|
this.summonData.ability = ability.id;
|
||||||
|
}
|
||||||
|
applyOnGainAbAttrs(this, passive);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a pokemon has a passive either from:
|
* Checks if a pokemon has a passive either from:
|
||||||
* - bought with starter candy
|
* - bought with starter candy
|
||||||
|
@ -116,4 +116,15 @@ describe("Abilities - Imposter", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should activate its ability if it copies one that activates on summon", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.DITTO ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
52
src/test/abilities/mummy.test.ts
Normal file
52
src/test/abilities/mummy.test.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Mummy", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH ])
|
||||||
|
.ability(Abilities.MUMMY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.TACKLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the enemy's ability to mummy when hit by a contact move", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.MUMMY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not change the enemy's ability hit by a non-contact move", async () => {
|
||||||
|
game.override.enemyMoveset(Moves.EARTHQUAKE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
});
|
53
src/test/abilities/trace.test.ts
Normal file
53
src/test/abilities/trace.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Trace", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH ])
|
||||||
|
.ability(Abilities.TRACE)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should copy the opponent's ability", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate a copied post-summon ability", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
65
src/test/abilities/wandering_spirit.test.ts
Normal file
65
src/test/abilities/wandering_spirit.test.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Wandering Spirit", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH ])
|
||||||
|
.ability(Abilities.WANDERING_SPIRIT)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.TACKLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should exchange abilities when hit with a contact move", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.WANDERING_SPIRIT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not exchange abilities when hit with a non-contact move", async () => {
|
||||||
|
game.override.enemyMoveset(Moves.EARTHQUAKE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.WANDERING_SPIRIT);
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
67
src/test/battle/ability_swap.test.ts
Normal file
67
src/test/battle/ability_swap.test.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { allAbilities } from "#app/data/ability";
|
||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Test Ability Swapping", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.scene.getPlayerPokemon()?.setTempAbility(allAbilities[Abilities.INTIMIDATE]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove primal weather when the setter's ability is removed", async () => {
|
||||||
|
game.override.ability(Abilities.DESOLATE_LAND);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.scene.getPlayerPokemon()?.setTempAbility(allAbilities[Abilities.BALL_FETCH]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not activate passive abilities", async () => {
|
||||||
|
game.override.passiveAbility(Abilities.INTREPID_SWORD);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.scene.getPlayerPokemon()?.setTempAbility(allAbilities[Abilities.BALL_FETCH]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(1); // would be 2 if passive activated again
|
||||||
|
});
|
||||||
|
});
|
70
src/test/moves/doodle.test.ts
Normal file
70
src/test/moves/doodle.test.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Doodle", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH, Moves.DOODLE ])
|
||||||
|
.ability(Abilities.ADAPTABILITY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should copy the opponent's ability in singles", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.DOODLE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should copy the opponent's ability to itself and its ally in doubles", async () => {
|
||||||
|
game.override.battleType("double");
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.DOODLE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerField()[0].getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
expect(game.scene.getPlayerField()[1].getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
game.override.battleType("double")
|
||||||
|
.enemyAbility(Abilities.INTIMIDATE);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.DOODLE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
// Enemies should have been intimidated twice
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-2);
|
||||||
|
});
|
||||||
|
});
|
53
src/test/moves/entrainment.test.ts
Normal file
53
src/test/moves/entrainment.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Entrainment", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH, Moves.ENTRAINMENT ])
|
||||||
|
.ability(Abilities.ADAPTABILITY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gives its ability to the target", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.ENTRAINMENT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.ADAPTABILITY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
game.override.ability(Abilities.INTIMIDATE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.ENTRAINMENT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
53
src/test/moves/role_play.test.ts
Normal file
53
src/test/moves/role_play.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Role Play", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH, Moves.ROLE_PLAY ])
|
||||||
|
.ability(Abilities.ADAPTABILITY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the user's ability to the target's ability", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.ROLE_PLAY);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.ROLE_PLAY);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
42
src/test/moves/simple_beam.test.ts
Normal file
42
src/test/moves/simple_beam.test.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Simple Beam", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH, Moves.SIMPLE_BEAM ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the target's ability to simple", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SIMPLE_BEAM);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.SIMPLE);
|
||||||
|
});
|
||||||
|
});
|
56
src/test/moves/skill_swap.test.ts
Normal file
56
src/test/moves/skill_swap.test.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Skill Swap", () => {
|
||||||
|
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
|
||||||
|
.moveset([ Moves.SPLASH, Moves.SKILL_SWAP ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should swap the two abilities", async () => {
|
||||||
|
game.override.ability(Abilities.ADAPTABILITY);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getAbility().id).toBe(Abilities.BALL_FETCH);
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getAbility().id).toBe(Abilities.ADAPTABILITY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate post-summon abilities", async () => {
|
||||||
|
game.override.ability(Abilities.INTIMIDATE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
// player atk should be -1 after opponent gains intimidate and it activates
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
@ -116,4 +116,16 @@ describe("Moves - Transform", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should activate its ability if it copies one that activates on summon", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.INTIMIDATE)
|
||||||
|
.ability(Abilities.BALL_FETCH);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.DITTO ]);
|
||||||
|
game.move.select(Moves.TRANSFORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user