[Ability] Heatproof now reduces burn damage by half (#3524)

* Heatproof now reduces burn damage by half

* Add tests

* Add comment to test
This commit is contained in:
NightKev 2024-08-13 14:00:19 -07:00 committed by GitHub
parent 452fbbb345
commit 39e7591d3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 7 deletions

View File

@ -3428,6 +3428,30 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr {
} }
} }
/**
* Causes Pokemon to take reduced damage from the {@linkcode StatusEffect.BURN | Burn} status
* @param multiplier Multiplied with the damage taken
*/
export class ReduceBurnDamageAbAttr extends AbAttr {
constructor(protected multiplier: number) {
super(false);
}
/**
* Applies the damage reduction
* @param pokemon N/A
* @param passive N/A
* @param cancelled N/A
* @param args `[0]` {@linkcode Utils.NumberHolder} The damage value being modified
* @returns `true`
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor((args[0] as Utils.NumberHolder).value * this.multiplier), 1);
return true;
}
}
export class DoubleBerryEffectAbAttr extends AbAttr { export class DoubleBerryEffectAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value *= 2; (args[0] as Utils.NumberHolder).value *= 2;
@ -4613,6 +4637,7 @@ export function initAbilities() {
.unimplemented(), .unimplemented(),
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)
.ignorable(), .ignorable(),
new Ability(Abilities.SIMPLE, 4) new Ability(Abilities.SIMPLE, 4)
.attr(StatChangeMultiplierAbAttr, 2) .attr(StatChangeMultiplierAbAttr, 2)

View File

@ -25,7 +25,7 @@ import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender"; import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { ArenaTagSide, ArenaTrapTag, ConditionalProtectTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagSide, ArenaTrapTag, ConditionalProtectTag, 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, ChangeMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, PreventBypassSpeedChanceAbAttr, 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, ChangeMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, PreventBypassSpeedChanceAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr, ReduceBurnDamageAbAttr } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables"; import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -3815,21 +3815,22 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
if (!cancelled.value) { if (!cancelled.value) {
this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
let damage: integer = 0; const damage = new Utils.NumberHolder(0);
switch (pokemon.status.effect) { switch (pokemon.status.effect) {
case StatusEffect.POISON: case StatusEffect.POISON:
damage = Math.max(pokemon.getMaxHp() >> 3, 1); damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
break; break;
case StatusEffect.TOXIC: case StatusEffect.TOXIC:
damage = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1);
break; break;
case StatusEffect.BURN: case StatusEffect.BURN:
damage = Math.max(pokemon.getMaxHp() >> 4, 1); damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage);
break; break;
} }
if (damage) { if (damage.value) {
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage, false, true)); this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
pokemon.updateInfo(); pokemon.updateInfo();
} }
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end());

View File

@ -0,0 +1,77 @@
import { Species } from "#app/enums/species.js";
import { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import { StatusEffect } from "#app/enums/status-effect.js";
describe("Abilities - Heatproof", () => {
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")
.disableCrits()
.enemySpecies(Species.CHARMANDER)
.enemyAbility(Abilities.HEATPROOF)
.enemyMoveset(SPLASH_ONLY)
.enemyLevel(100)
.starterSpecies(Species.CHANDELURE)
.ability(Abilities.BALL_FETCH)
.moveset([Moves.FLAMETHROWER, Moves.SPLASH])
.startingLevel(100);
});
it("reduces Fire type damage by half", async () => {
await game.startBattle();
const enemy = game.scene.getEnemyPokemon()!;
const initialHP = 1000;
enemy.hp = initialHP;
game.doAttack(getMovePosition(game.scene, 0, Moves.FLAMETHROWER));
await game.phaseInterceptor.to(TurnEndPhase);
const heatproofDamage = initialHP - enemy.hp;
enemy.hp = initialHP;
game.override.enemyAbility(Abilities.BALL_FETCH);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLAMETHROWER));
await game.phaseInterceptor.to(TurnEndPhase);
const regularDamage = initialHP - enemy.hp;
expect(heatproofDamage).toBeLessThanOrEqual((regularDamage / 2) + 1);
expect(heatproofDamage).toBeGreaterThanOrEqual((regularDamage / 2) - 1);
});
it("reduces Burn damage by half", async () => {
game.override
.enemyStatusEffect(StatusEffect.BURN)
.enemySpecies(Species.ABRA);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn();
// Normal burn damage is /16
expect(enemy.hp).toBe(enemy.getMaxHp() - Math.floor(enemy.getMaxHp() / 32));
});
});