[Ability] Implement Unburden (#4534)
* unburden implemented * Used tag instead of stat changes for Unburden * added documentation and neutralizing gas test * accounted for unburden in getEffectiveStat * fixed doubling speed in two places * merge conflict resolve * changed documentation wording, added test for stuff cheeks * refactor unburden * merge * remove console logs * Update src/data/ability.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * add neut gas check Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * Added test for NeutGas user entering while unburden activated * add spaces to tests, removed passive and [] from applyPostItemLostAbAttrs * added line breaks after test cases --------- Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com>
This commit is contained in:
parent
1619d512cf
commit
3f97c9e39f
|
@ -15,7 +15,7 @@ import { addTextObject, getTextColor, TextStyle } from "#app/ui/text";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import AbilityBar from "#app/ui/ability-bar";
|
import AbilityBar from "#app/ui/ability-bar";
|
||||||
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr } from "#app/data/ability";
|
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr, PostItemLostAbAttr } from "#app/data/ability";
|
||||||
import Battle, { BattleType, FixedBattleConfig } from "#app/battle";
|
import Battle, { BattleType, FixedBattleConfig } from "#app/battle";
|
||||||
import { GameMode, GameModes, getGameMode } from "#app/game-mode";
|
import { GameMode, GameModes, getGameMode } from "#app/game-mode";
|
||||||
import FieldSpritePipeline from "#app/pipelines/field-sprite";
|
import FieldSpritePipeline from "#app/pipelines/field-sprite";
|
||||||
|
@ -2601,9 +2601,19 @@ export default class BattleScene extends SceneBase {
|
||||||
const addModifier = () => {
|
const addModifier = () => {
|
||||||
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
|
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
|
||||||
if (target.isPlayer()) {
|
if (target.isPlayer()) {
|
||||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => resolve(true));
|
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => {
|
||||||
|
if (source) {
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => resolve(true));
|
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => {
|
||||||
|
if (source) {
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
|
|
@ -3857,6 +3857,41 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers after the Pokemon loses or consumes an item
|
||||||
|
* @extends AbAttr
|
||||||
|
*/
|
||||||
|
export class PostItemLostAbAttr extends AbAttr {
|
||||||
|
applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a Battler Tag to the Pokemon after it loses or consumes item
|
||||||
|
* @extends PostItemLostAbAttr
|
||||||
|
*/
|
||||||
|
export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr {
|
||||||
|
private tagType: BattlerTagType;
|
||||||
|
constructor(tagType: BattlerTagType) {
|
||||||
|
super(true);
|
||||||
|
this.tagType = tagType;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds the last used Pokeball back into the player's inventory
|
||||||
|
* @param pokemon {@linkcode Pokemon} with this ability
|
||||||
|
* @param args N/A
|
||||||
|
* @returns true if BattlerTag was applied
|
||||||
|
*/
|
||||||
|
applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
|
if (!pokemon.getTag(this.tagType) && !simulated) {
|
||||||
|
pokemon.addTag(this.tagType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class StatStageChangeMultiplierAbAttr extends AbAttr {
|
export class StatStageChangeMultiplierAbAttr extends AbAttr {
|
||||||
private multiplier: integer;
|
private multiplier: integer;
|
||||||
|
|
||||||
|
@ -5228,6 +5263,11 @@ export function applyPostFaintAbAttrs(attrType: Constructor<PostFaintAbAttr>,
|
||||||
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated);
|
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyPostItemLostAbAttrs(attrType: Constructor<PostItemLostAbAttr>,
|
||||||
|
pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||||
|
return applyAbAttrsInternal<PostItemLostAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), args);
|
||||||
|
}
|
||||||
|
|
||||||
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
|
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
|
||||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive));
|
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive));
|
||||||
pokemon.scene.clearPhaseQueueSplice();
|
pokemon.scene.clearPhaseQueueSplice();
|
||||||
|
@ -5521,7 +5561,7 @@ export function initAbilities() {
|
||||||
new Ability(Abilities.ANGER_POINT, 4)
|
new Ability(Abilities.ANGER_POINT, 4)
|
||||||
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
||||||
new Ability(Abilities.UNBURDEN, 4)
|
new Ability(Abilities.UNBURDEN, 4)
|
||||||
.unimplemented(),
|
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN),
|
||||||
new Ability(Abilities.HEATPROOF, 4)
|
new Ability(Abilities.HEATPROOF, 4)
|
||||||
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
|
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
|
||||||
.attr(ReduceBurnDamageAbAttr, 0.5)
|
.attr(ReduceBurnDamageAbAttr, 0.5)
|
||||||
|
|
|
@ -1573,6 +1573,22 @@ export class AbilityBattlerTag extends BattlerTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used by Unburden to double speed
|
||||||
|
* @extends AbilityBattlerTag
|
||||||
|
*/
|
||||||
|
export class UnburdenTag extends AbilityBattlerTag {
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.UNBURDEN, Abilities.UNBURDEN, BattlerTagLapseType.CUSTOM, 1);
|
||||||
|
}
|
||||||
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
super.onAdd(pokemon);
|
||||||
|
}
|
||||||
|
onRemove(pokemon: Pokemon): void {
|
||||||
|
super.onRemove(pokemon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class TruantTag extends AbilityBattlerTag {
|
export class TruantTag extends AbilityBattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.TRUANT, Abilities.TRUANT, BattlerTagLapseType.MOVE, 1);
|
super(BattlerTagType.TRUANT, Abilities.TRUANT, BattlerTagLapseType.MOVE, 1);
|
||||||
|
@ -2934,6 +2950,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||||
return new ThroatChoppedTag();
|
return new ThroatChoppedTag();
|
||||||
case BattlerTagType.GORILLA_TACTICS:
|
case BattlerTagType.GORILLA_TACTICS:
|
||||||
return new GorillaTacticsTag();
|
return new GorillaTacticsTag();
|
||||||
|
case BattlerTagType.UNBURDEN:
|
||||||
|
return new UnburdenTag();
|
||||||
case BattlerTagType.SUBSTITUTE:
|
case BattlerTagType.SUBSTITUTE:
|
||||||
return new SubstituteTag(sourceMove, sourceId);
|
return new SubstituteTag(sourceMove, sourceId);
|
||||||
case BattlerTagType.AUTOTOMIZED:
|
case BattlerTagType.AUTOTOMIZED:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||||
import Pokemon, { HitResult } from "../field/pokemon";
|
import Pokemon, { HitResult } from "../field/pokemon";
|
||||||
import { getStatusEffectHealText } from "./status-effect";
|
import { getStatusEffectHealText } from "./status-effect";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
|
import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs, applyPostItemLostAbAttrs } from "./ability";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
@ -75,6 +75,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
|
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
|
||||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||||
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
|
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
};
|
};
|
||||||
case BerryType.LUM:
|
case BerryType.LUM:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
|
@ -86,6 +87,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
}
|
}
|
||||||
pokemon.resetStatus(true, true);
|
pokemon.resetStatus(true, true);
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
};
|
};
|
||||||
case BerryType.LIECHI:
|
case BerryType.LIECHI:
|
||||||
case BerryType.GANLON:
|
case BerryType.GANLON:
|
||||||
|
@ -101,6 +103,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
const statStages = new Utils.NumberHolder(1);
|
const statStages = new Utils.NumberHolder(1);
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
|
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
};
|
};
|
||||||
case BerryType.LANSAT:
|
case BerryType.LANSAT:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
|
@ -108,6 +111,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
pokemon.battleData.berriesEaten.push(berryType);
|
pokemon.battleData.berriesEaten.push(berryType);
|
||||||
}
|
}
|
||||||
pokemon.addTag(BattlerTagType.CRIT_BOOST);
|
pokemon.addTag(BattlerTagType.CRIT_BOOST);
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
};
|
};
|
||||||
case BerryType.STARF:
|
case BerryType.STARF:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
|
@ -118,6 +122,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
const stages = new Utils.NumberHolder(2);
|
const stages = new Utils.NumberHolder(2);
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
|
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
};
|
};
|
||||||
case BerryType.LEPPA:
|
case BerryType.LEPPA:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
|
@ -128,6 +133,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||||
if (ppRestoreMove !== undefined) {
|
if (ppRestoreMove !== undefined) {
|
||||||
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
|
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
|
||||||
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
|
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Constructor, NumberHolder } from "#app/utils";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { WeatherType } from "./weather";
|
import { WeatherType } from "./weather";
|
||||||
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
||||||
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
||||||
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
||||||
import { BattlerIndex, BattleType } from "../battle";
|
import { BattlerIndex, BattleType } from "../battle";
|
||||||
import { TerrainType } from "./terrain";
|
import { TerrainType } from "./terrain";
|
||||||
|
@ -2402,6 +2402,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||||
// Decrease item amount and update icon
|
// Decrease item amount and update icon
|
||||||
!--removedItem.stackCount;
|
!--removedItem.stackCount;
|
||||||
target.scene.updateModifiers(target.isPlayer());
|
target.scene.updateModifiers(target.isPlayer());
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false);
|
||||||
|
|
||||||
|
|
||||||
if (this.berriesOnly) {
|
if (this.berriesOnly) {
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||||
|
@ -2481,6 +2483,7 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||||
eatBerry(consumer: Pokemon) {
|
eatBerry(consumer: Pokemon) {
|
||||||
getBerryEffectFunc(this.chosenBerry!.berryType)(consumer); // consumer eats the berry
|
getBerryEffectFunc(this.chosenBerry!.berryType)(consumer); // consumer eats the berry
|
||||||
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false));
|
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false));
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, consumer, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2516,6 +2519,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
||||||
}
|
}
|
||||||
// if the target has berries, pick a random berry and steal it
|
// if the target has berries, pick a random berry and steal it
|
||||||
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
||||||
|
applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false);
|
||||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
||||||
user.scene.queueMessage(message);
|
user.scene.queueMessage(message);
|
||||||
this.reduceBerryModifier(target);
|
this.reduceBerryModifier(target);
|
||||||
|
|
|
@ -74,6 +74,7 @@ export enum BattlerTagType {
|
||||||
DRAGON_CHEER = "DRAGON_CHEER",
|
DRAGON_CHEER = "DRAGON_CHEER",
|
||||||
NO_RETREAT = "NO_RETREAT",
|
NO_RETREAT = "NO_RETREAT",
|
||||||
GORILLA_TACTICS = "GORILLA_TACTICS",
|
GORILLA_TACTICS = "GORILLA_TACTICS",
|
||||||
|
UNBURDEN = "UNBURDEN",
|
||||||
THROAT_CHOPPED = "THROAT_CHOPPED",
|
THROAT_CHOPPED = "THROAT_CHOPPED",
|
||||||
TAR_SHOT = "TAR_SHOT",
|
TAR_SHOT = "TAR_SHOT",
|
||||||
BURNED_UP = "BURNED_UP",
|
BURNED_UP = "BURNED_UP",
|
||||||
|
|
|
@ -982,6 +982,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
if (this.status && this.status.effect === StatusEffect.PARALYSIS) {
|
if (this.status && this.status.effect === StatusEffect.PARALYSIS) {
|
||||||
ret >>= 1;
|
ret >>= 1;
|
||||||
}
|
}
|
||||||
|
if (this.getTag(BattlerTagType.UNBURDEN) && !this.scene.getField(true).some(pokemon => pokemon !== this && pokemon.hasAbilityWithAttr(SuppressFieldAbilitiesAbAttr))) {
|
||||||
|
ret *= 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
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, vi } from "vitest";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import { BerryType } from "#app/enums/berry-type";
|
||||||
|
import { allMoves, StealHeldItemChanceAttr } from "#app/data/move";
|
||||||
|
|
||||||
|
|
||||||
|
describe("Abilities - Unburden", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.starterSpecies(Species.TREECKO)
|
||||||
|
.startingLevel(1)
|
||||||
|
.moveset([ Moves.POPULATION_BOMB, Moves.KNOCK_OFF, Moves.PLUCK, Moves.THIEF ])
|
||||||
|
.startingHeldItems([
|
||||||
|
{ name: "BERRY", count: 1, type: BerryType.SITRUS },
|
||||||
|
{ name: "BERRY", count: 2, type: BerryType.APICOT },
|
||||||
|
{ name: "BERRY", count: 2, type: BerryType.LUM },
|
||||||
|
])
|
||||||
|
.enemySpecies(Species.NINJASK)
|
||||||
|
.enemyLevel(100)
|
||||||
|
.enemyMoveset([ Moves.FALSE_SWIPE ])
|
||||||
|
.enemyAbility(Abilities.UNBURDEN)
|
||||||
|
.enemyPassiveAbility(Abilities.NO_GUARD)
|
||||||
|
.enemyHeldItems([
|
||||||
|
{ name: "BERRY", type: BerryType.SITRUS, count: 1 },
|
||||||
|
{ name: "BERRY", type: BerryType.LUM, count: 1 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when a berry is eaten", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
playerPokemon.abilityIndex = 2;
|
||||||
|
const playerHeldItems = playerPokemon.getHeldItems().length;
|
||||||
|
const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.FALSE_SWIPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems);
|
||||||
|
expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when a berry is stolen", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.PLUCK);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when an item is knocked off", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.KNOCK_OFF);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when an item is stolen via attacking ability", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.MAGICIAN)
|
||||||
|
.startingHeldItems([
|
||||||
|
{ name: "MULTI_LENS", count: 3 },
|
||||||
|
]);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.POPULATION_BOMB);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when an item is stolen via defending ability", async () => {
|
||||||
|
game.override
|
||||||
|
.startingLevel(45)
|
||||||
|
.enemyAbility(Abilities.PICKPOCKET)
|
||||||
|
.startingHeldItems([
|
||||||
|
{ name: "MULTI_LENS", count: 3 },
|
||||||
|
{ name: "SOUL_DEW", count: 1 },
|
||||||
|
{ name: "LUCKY_EGG", count: 1 },
|
||||||
|
]);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
playerPokemon.abilityIndex = 2;
|
||||||
|
const playerHeldItems = playerPokemon.getHeldItems().length;
|
||||||
|
const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.POPULATION_BOMB);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems);
|
||||||
|
expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when an item is stolen via move", async () => {
|
||||||
|
vi.spyOn(allMoves[Moves.THIEF], "attrs", "get").mockReturnValue([ new StealHeldItemChanceAttr(1.0) ]); // give Thief 100% steal rate
|
||||||
|
game.override.startingHeldItems([
|
||||||
|
{ name: "MULTI_LENS", count: 3 },
|
||||||
|
]);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.THIEF);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when an item is stolen via grip claw", async () => {
|
||||||
|
game.override
|
||||||
|
.startingLevel(5)
|
||||||
|
.startingHeldItems([
|
||||||
|
{ name: "GRIP_CLAW", count: 5 },
|
||||||
|
{ name: "MULTI_LENS", count: 3 },
|
||||||
|
])
|
||||||
|
.enemyHeldItems([
|
||||||
|
{ name: "SOUL_DEW", count: 1 },
|
||||||
|
{ name: "LUCKY_EGG", count: 1 },
|
||||||
|
{ name: "LEFTOVERS", count: 1 },
|
||||||
|
{ name: "GRIP_CLAW", count: 1 },
|
||||||
|
{ name: "MULTI_LENS", count: 1 },
|
||||||
|
{ name: "BERRY", type: BerryType.SITRUS, count: 1 },
|
||||||
|
{ name: "BERRY", type: BerryType.LUM, count: 1 },
|
||||||
|
]);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
while (enemyPokemon.getHeldItems().length === enemyHeldItemCt) {
|
||||||
|
game.move.select(Moves.POPULATION_BOMB);
|
||||||
|
await game.toNextTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not activate when a neutralizing ability is present", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.NEUTRALIZING_GAS);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
const playerHeldItems = playerPokemon.getHeldItems().length;
|
||||||
|
const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.FALSE_SWIPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems);
|
||||||
|
expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate when a move that consumes a berry is used", async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.STUFF_CHEEKS ]);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyHeldItemCt = enemyPokemon.getHeldItems().length;
|
||||||
|
const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.STUFF_CHEEKS);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt);
|
||||||
|
expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deactivate when a neutralizing gas user enters the field", async () => {
|
||||||
|
game.override
|
||||||
|
.battleType("double")
|
||||||
|
.moveset([ Moves.SPLASH ]);
|
||||||
|
await game.classicMode.startBattle([ Species.TREECKO, Species.MEOWTH, Species.WEEZING ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getParty();
|
||||||
|
const treecko = playerPokemon[0];
|
||||||
|
const weezing = playerPokemon[2];
|
||||||
|
treecko.abilityIndex = 2;
|
||||||
|
weezing.abilityIndex = 1;
|
||||||
|
const playerHeldItems = treecko.getHeldItems().length;
|
||||||
|
const initialPlayerSpeed = treecko.getStat(Stat.SPD);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.FALSE_SWIPE, 0);
|
||||||
|
await game.forceEnemyMove(Moves.FALSE_SWIPE, 0);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(treecko.getHeldItems().length).toBeLessThan(playerHeldItems);
|
||||||
|
expect(treecko.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2);
|
||||||
|
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.doSwitchPokemon(2);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(treecko.getHeldItems().length).toBeLessThan(playerHeldItems);
|
||||||
|
expect(treecko.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue