diff --git a/src/data/ability.ts b/src/data/ability.ts index e2ae7830cee..0394a55dd95 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3792,28 +3792,16 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { } export class UnburdenBerryAbAttr extends PostTurnAbAttr { - private stats: BattleStat[]; - private stages: number; - - constructor(stats: BattleStat[], stages: number) { - super(true); - - this.stats = Array.isArray(stats) - ? stats - : [ stats ]; - this.stages = stages; - } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const multipleItems = pokemon.battleData.berriesEaten.length * this.stages; - if (multipleItems > 6) { - this.stages = 6; - } else { - this.stages = multipleItems; + if (simulated) { + return simulated; } - if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); + + if (pokemon.getTag(BattlerTagType.UNBURDEN)) { + return false; } + + pokemon.addTag(BattlerTagType.UNBURDEN); return true; } @@ -3824,23 +3812,16 @@ export class UnburdenBerryAbAttr extends PostTurnAbAttr { } export class UnburdenDefStolenAbAttr extends PostDefendAbAttr { - private stats: BattleStat[]; - private stages: number; - - constructor(stats: BattleStat[], stages: number) { - super(true); - - this.stats = Array.isArray(stats) - ? stats - : [ stats ]; - this.stages = stages; - } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); - pokemon.turnData.itemsLost -= 1; + if (simulated) { + return simulated; } + + if (pokemon.getTag(BattlerTagType.UNBURDEN)) { + return false; + } + + pokemon.addTag(BattlerTagType.UNBURDEN); return true; } @@ -3851,23 +3832,16 @@ export class UnburdenDefStolenAbAttr extends PostDefendAbAttr { } export class UnburdenAtkStolenAbAttr extends PostAttackAbAttr { - private stats: BattleStat[]; - private stages: number; - - constructor(stats: BattleStat[], stages: number) { - super(); - - this.stats = Array.isArray(stats) - ? stats - : [ stats ]; - this.stages = stages; - } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); - pokemon.turnData.itemsLost -= 1; + if (simulated) { + return simulated; } + + if (pokemon.getTag(BattlerTagType.UNBURDEN)) { + return false; + } + + pokemon.addTag(BattlerTagType.UNBURDEN); return true; } @@ -5188,9 +5162,9 @@ export function initAbilities() { new Ability(Abilities.ANGER_POINT, 4) .attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6), new Ability(Abilities.UNBURDEN, 4) - .attr(UnburdenBerryAbAttr, [ Stat.SPD ], 2) - .attr(UnburdenAtkStolenAbAttr, [ Stat.SPD ], 2) - .attr(UnburdenDefStolenAbAttr, [ Stat.SPD ], 2), + .attr(UnburdenBerryAbAttr) + .attr(UnburdenAtkStolenAbAttr) + .attr(UnburdenDefStolenAbAttr), new Ability(Abilities.HEATPROOF, 4) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReduceBurnDamageAbAttr, 0.5) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index d8094f96368..8450a4ecfdc 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1537,6 +1537,19 @@ 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 { + pokemon.setStat(Stat.SPD, pokemon.getStat(Stat.SPD, false) * 2, false); + } +} + export class TruantTag extends AbilityBattlerTag { constructor() { super(BattlerTagType.TRUANT, Abilities.TRUANT, BattlerTagLapseType.MOVE, 1); @@ -2815,6 +2828,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new ThroatChoppedTag(); case BattlerTagType.GORILLA_TACTICS: return new GorillaTacticsTag(); + case BattlerTagType.UNBURDEN: + return new UnburdenTag(); case BattlerTagType.SUBSTITUTE: return new SubstituteTag(sourceMove, sourceId); case BattlerTagType.AUTOTOMIZED: diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 209d36316f9..e914d98ca37 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -75,6 +75,7 @@ export enum BattlerTagType { DRAGON_CHEER = "DRAGON_CHEER", NO_RETREAT = "NO_RETREAT", GORILLA_TACTICS = "GORILLA_TACTICS", + UNBURDEN = "UNBURDEN", THROAT_CHOPPED = "THROAT_CHOPPED", TAR_SHOT = "TAR_SHOT", BURNED_UP = "BURNED_UP", diff --git a/src/test/abilities/unburden.test.ts b/src/test/abilities/unburden.test.ts index 628ef83f8a2..dc87ec92306 100644 --- a/src/test/abilities/unburden.test.ts +++ b/src/test/abilities/unburden.test.ts @@ -50,29 +50,32 @@ describe("Abilities - Unburden", () => { 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.getStatStage(Stat.SPD)).toBe(4); + expect(playerPokemon.getStat(Stat.SPD, false)).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.getStatStage(Stat.SPD)).toBe(2); + expect(enemyPokemon.getStat(Stat.SPD, false)).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.getStatStage(Stat.SPD)).toBe(2); + expect(enemyPokemon.getStat(Stat.SPD, false)).toBeCloseTo(initialEnemySpeed * 2); }); it("should activate when an item is stolen via attacking ability", async () => { game.override @@ -84,10 +87,11 @@ describe("Abilities - Unburden", () => { vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); 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.getStatStage(Stat.SPD)).toBe(4); + expect(enemyPokemon.getStat(Stat.SPD, false)).toBeCloseTo(initialEnemySpeed * 2); }); it("should activate when an item is stolen via defending ability", async () => { game.override @@ -101,10 +105,11 @@ describe("Abilities - Unburden", () => { vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); const playerPokemon = game.scene.getPlayerPokemon()!; 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.getStatStage(Stat.SPD)).toBe(6); + expect(playerPokemon.getStat(Stat.SPD, false)).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 @@ -114,10 +119,11 @@ describe("Abilities - Unburden", () => { 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.getStatStage(Stat.SPD)).toBe(4); + expect(enemyPokemon.getStat(Stat.SPD, false)).toBeCloseTo(initialEnemySpeed * 2); }); it("should activate when an item is stolen via grip claw", async () => { game.override.startingHeldItems([ @@ -128,9 +134,10 @@ describe("Abilities - Unburden", () => { vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); 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.getStatStage(Stat.SPD)).toBe(4); + expect(enemyPokemon.getStat(Stat.SPD, false)).toBeCloseTo(initialEnemySpeed * 2); }); });