[Move] Implement Heal Block (#4120)

* Heal Block on new branch

* Add/update code from previous PR

* Re-add i18n from previous PR

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Asdar <asdargmng@gmail.com>

* Disabling Moves

* Still need to update tests and write docs

* removing partial tags from abilities to feel better

* Pollen Puff works now

* Implemented Psychic Noise

* typedocs

* Documentation

* Update src/data/battler-tags.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update src/data/battler-tags.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update src/data/battler-tags.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Turns out the old condition wasn't buggy. Probably mixed up another change I made.

* changed array clear

---------

Co-authored-by: frutescens <info@laptop>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Asdar <asdargmng@gmail.com>
This commit is contained in:
Mumble 2024-09-21 13:15:32 -07:00 committed by GitHub
parent 1e70cb92d1
commit 4fb76fd117
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 302 additions and 20 deletions

View File

@ -4831,11 +4831,9 @@ export function initAbilities() {
.bypassFaint(),
new Ability(Abilities.VOLT_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.WATER_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.WATER)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.OBLIVIOUS, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
@ -4948,8 +4946,7 @@ export function initAbilities() {
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
.ignorable(),
new Ability(Abilities.RAIN_DISH, 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
.partial(), // Healing not blocked by Heal Block
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
new Ability(Abilities.SAND_STREAM, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
@ -5080,7 +5077,6 @@ export function initAbilities() {
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25)
.attr(TypeImmunityHealAbAttr, Type.WATER)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.DOWNLOAD, 4)
.attr(DownloadAbAttr),
@ -5161,8 +5157,7 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.ICE_BODY, 4)
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
.partial(), // Healing not blocked by Heal Block
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW),
new Ability(Abilities.SOLID_ROCK, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
.ignorable(),
@ -5332,8 +5327,7 @@ export function initAbilities() {
.ignorable()
.unimplemented(),
new Ability(Abilities.CHEEK_POUCH, 6)
.attr(HealFromBerryUseAbAttr, 1/3)
.partial(), // Healing not blocked by Heal Block
.attr(HealFromBerryUseAbAttr, 1/3),
new Ability(Abilities.PROTEAN, 6)
.attr(PokemonTypeChangeAbAttr),
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)), //Gen 9 Implementation

View File

@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move";
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr } from "./move";
import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
import { TerrainType } from "./terrain";
@ -141,6 +141,18 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
*/
abstract isMoveRestricted(move: Moves): boolean;
/**
* Checks if this tag is restricting a move based on a user's decisions during the target selection phase
*
* @param {Moves} move {@linkcode Moves} move ID to check restriction for
* @param {Pokemon} user {@linkcode Pokemon} the user of the above move
* @param {Pokemon} target {@linkcode Pokemon} the target of the above move
* @returns {boolean} `false` unless overridden by the child tag
*/
isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon): boolean {
return false;
}
/**
* Gets the text to display when the player attempts to select a move that is restricted by this tag.
*
@ -2178,6 +2190,74 @@ export class ExposedTag extends BattlerTag {
}
}
/**
* Tag that prevents HP recovery from held items and move effects. It also blocks the usage of recovery moves.
* Applied by moves: {@linkcode Moves.HEAL_BLOCK | Heal Block (5 turns)}, {@linkcode Moves.PSYCHIC_NOISE | Psychic Noise (2 turns)}
*
* @extends MoveRestrictionBattlerTag
*/
export class HealBlockTag extends MoveRestrictionBattlerTag {
constructor(turnCount: number, sourceMove: Moves) {
super(BattlerTagType.HEAL_BLOCK, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], turnCount, sourceMove);
}
onActivation(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
/**
* Checks if a move is disabled under Heal Block
* @param {Moves} move {@linkcode Moves} the move ID
* @returns `true` if the move has a TRIAGE_MOVE flag and is a status move
*/
override isMoveRestricted(move: Moves): boolean {
if (allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS) {
return true;
}
return false;
}
/**
* Checks if a move is disabled under Heal Block because of its choice of target
* Implemented b/c of Pollen Puff
* @param {Moves} move {@linkcode Moves} the move ID
* @param {Pokemon} user {@linkcode Pokemon} the move user
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
* @returns `true` if the move cannot be used because the target is an ally
*/
override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) {
const moveCategory = new Utils.IntegerHolder(allMoves[move].category);
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
return true;
}
return false;
}
/**
* Uses DisabledTag's selectionDeniedText() message
*/
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
}
/**
* @override
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
* @returns {string} text to display when the move is interrupted
*/
override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
}
override onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHealBlockOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null);
}
}
/**
* Tag that doubles the type effectiveness of Fire-type moves.
* @extends BattlerTag
@ -2490,6 +2570,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag();
case BattlerTagType.HEAL_BLOCK:
return new HealBlockTag(turnCount, sourceMove);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -4539,6 +4539,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.NIGHTMARE:
case BattlerTagType.DROWSY:
case BattlerTagType.DISABLED:
case BattlerTagType.HEAL_BLOCK:
return -5;
case BattlerTagType.SEEDED:
case BattlerTagType.SALT_CURED:
@ -7826,8 +7827,8 @@ export function initMoves() {
.makesContact()
.attr(LessPPMorePowerAttr),
new StatusMove(Moves.HEAL_BLOCK, Type.PSYCHIC, 100, 15, -1, 0, 4)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.unimplemented(),
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, true, 5)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.WRING_OUT, Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4)
.attr(OpponentHighHpPowerAttr, 120)
.makesContact(),
@ -9609,7 +9610,7 @@ export function initMoves() {
.recklessMove(),
new AttackMove(Moves.PSYCHIC_NOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
.soundBased()
.partial(),
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
.attr(FlinchAttr)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct?

View File

@ -80,4 +80,5 @@ export enum BattlerTagType {
BURNED_UP = "BURNED_UP",
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
HEAL_BLOCK = "HEAL_BLOCK",
}

View File

@ -2971,16 +2971,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getRestrictingTag(moveId) !== null;
}
/**
* Gets whether the given move is currently disabled for the user based on the player's target selection
*
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
* @param {Pokemon} user {@linkcode Pokemon} the move user
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
*
* @returns {boolean} `true` if the move is disabled for this Pokemon due to the player's target selection
*
* @see {@linkcode MoveRestrictionBattlerTag}
*/
isMoveTargetRestricted(moveId: Moves, user: Pokemon, target: Pokemon): boolean {
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
if ((tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
return (tag as MoveRestrictionBattlerTag !== null);
}
}
return false;
}
/**
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
*
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
* @param {Pokemon} user {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status
* @param {Pokemon} target {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
*/
getRestrictingTag(moveId: Moves): MoveRestrictionBattlerTag | null {
getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null {
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
return tag as MoveRestrictionBattlerTag;
} else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
return tag as MoveRestrictionBattlerTag;
}
}
return null;

View File

@ -96,5 +96,7 @@
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
"congratulations": "Glückwunsch!",
"beatModeFirstTime": "{{speciesName}} hat den {{gameMode}} Modus zum ersten Mal beendet! Du erhältst {{newModifier}}!",
"eggSkipPrompt": "Zur Ei-Zusammenfassung springen?"
"eggSkipPrompt": "Zur Ei-Zusammenfassung springen?",
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} kann nicht geheilt werden, da die Heilung blockiert wird!",
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} kann wieder geheilt werden!"
}

View File

@ -105,5 +105,7 @@
"congratulations": "Congratulations!",
"beatModeFirstTime": "{{speciesName}} beat {{gameMode}} Mode for the first time!\nYou received {{newModifier}}!",
"ppReduced": "It reduced the PP of {{targetName}}'s\n{{moveName}} by {{reduction}}!",
"mysteryEncounterAppeared": "What's this?"
"mysteryEncounterAppeared": "What's this?",
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} can't restore its HP!",
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} can restore its HP again!"
}

View File

@ -85,5 +85,7 @@
"statSeverelyFell_one": "¡El {{stats}} de {{pokemonNameWithAffix}} ha bajado muchísimo!",
"statSeverelyFell_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} han bajado muchísimo!",
"statWontGoAnyLower_one": "¡El {{stats}} de {{pokemonNameWithAffix}} no puede bajar más!",
"statWontGoAnyLower_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} no pueden bajar más!"
"statWontGoAnyLower_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} no pueden bajar más!",
"battlerTagsHealBlock": "¡{{pokemonNameWithAffix}} no puede restaurar sus PS!",
"battlerTagsHealBlockOnRemove": "¡{{pokemonNameWithAffix}} ya puede recuperar PS!"
}

View File

@ -99,5 +99,7 @@
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
"congratulations": "Félicitations !",
"beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !",
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?"
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?",
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} ne peut pas guérir !",
"battlerTagsHealBlockOnRemove": "Le blocage de soins qui affectait\n{{pokemonNameWithAffix}} sest dissipé !"
}

View File

@ -96,5 +96,7 @@
"retryBattle": "Você gostaria de tentar novamente desde o início da batalha?",
"unlockedSomething": "{{unlockedThing}}\nfoi desbloqueado.",
"congratulations": "Parabéns!",
"beatModeFirstTime": "{{speciesName}} venceu o Modo {{gameMode}} pela primeira vez!\nVocê recebeu {{newModifier}}!"
"beatModeFirstTime": "{{speciesName}} venceu o Modo {{gameMode}} pela primeira vez!\nVocê recebeu {{newModifier}}!",
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} não pode restaurar seus PS!",
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} pode restaurar seus PS novamente!"
}

View File

@ -10,6 +10,8 @@ import { HealAchv } from "#app/system/achv";
import i18next from "i18next";
import * as Utils from "#app/utils";
import { CommonAnimPhase } from "./common-anim-phase";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { HealBlockTag } from "#app/data/battler-tags";
export class PokemonHealPhase extends CommonAnimPhase {
private hpHealed: integer;
@ -50,9 +52,14 @@ export class PokemonHealPhase extends CommonAnimPhase {
const hasMessage = !!this.message;
const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0);
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK) as HealBlockTag;
let lastStatusEffect = StatusEffect.NONE;
if (healOrDamage) {
if (healBlock && this.hpHealed > 0) {
this.scene.queueMessage(healBlock.onActivation(pokemon));
this.message = null;
super.end();
} else if (healOrDamage) {
const hpRestoreMultiplier = new Utils.IntegerHolder(1);
if (!this.revive) {
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);

View File

@ -4,6 +4,8 @@ import { Command } from "#app/ui/command-ui-handler";
import { Mode } from "#app/ui/ui";
import { CommandPhase } from "./command-phase";
import { PokemonPhase } from "./pokemon-phase";
import i18next from "#app/plugins/i18n";
import { allMoves } from "#app/data/move";
export class SelectTargetPhase extends PokemonPhase {
constructor(scene: BattleScene, fieldIndex: integer) {
@ -17,6 +19,14 @@ export class SelectTargetPhase extends PokemonPhase {
const move = turnCommand?.move?.move;
this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => {
this.scene.ui.setMode(Mode.MESSAGE);
const fieldSide = this.scene.getField();
const user = fieldSide[this.fieldIndex];
const moveObject = allMoves[move!];
if (moveObject && user.isMoveTargetRestricted(moveObject.id, user, fieldSide[targets[0]])) {
const errorMessage = user.getRestrictingTag(move!, user, fieldSide[targets[0]])!.selectionDeniedText(user, moveObject.id);
user.scene.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true);
targets = [];
}
if (targets.length < 1) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));

View File

@ -0,0 +1,153 @@
import { BattlerIndex } from "#app/battle";
import { ArenaTagSide } from "#app/data/arena-tag";
import { WeatherType } from "#app/data/weather";
import GameManager from "#app/test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
// Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Heal_Block_(move)
describe("Moves - Heal Block", () => {
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.ABSORB, Moves.WISH, Moves.SPLASH, Moves.AQUA_RING])
.enemyMoveset(Moves.HEAL_BLOCK)
.ability(Abilities.NO_GUARD)
.enemyAbility(Abilities.BALL_FETCH)
.enemySpecies(Species.BLISSEY)
.disableCrits();
});
it("shouldn't stop damage from HP-drain attacks, just HP restoration", async() => {
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
player.damageAndUpdate(enemy.getMaxHp() - 1);
game.move.select(Moves.ABSORB);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1);
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
}, TIMEOUT
);
it("shouldn't stop Liquid Ooze from dealing damage", async() => {
game.override.enemyAbility(Abilities.LIQUID_OOZE);
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.ABSORB);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.isFullHp()).toBe(false);
expect(enemy.isFullHp()).toBe(false);
}, TIMEOUT);
it("should stop delayed heals, such as from Wish", async() => {
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.getMaxHp() - 1);
game.move.select(Moves.WISH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)).toBeDefined();
while (game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)) {
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
}
expect(player.hp).toBe(1);
}, TIMEOUT);
it("should prevent Grassy Terrain from restoring HP", async() => {
game.override.enemyAbility(Abilities.GRASSY_SURGE);
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.getMaxHp() - 1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1);
}, TIMEOUT);
it("should prevent healing from heal-over-time moves", async() => {
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.getMaxHp() - 1);
game.move.select(Moves.AQUA_RING);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.getTag(BattlerTagType.AQUA_RING)).toBeDefined();
expect(player.hp).toBe(1);
}, TIMEOUT);
it("should prevent abilities from restoring HP", async() => {
game.override
.weather(WeatherType.RAIN)
.ability(Abilities.RAIN_DISH);
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.getMaxHp() - 1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1);
}, TIMEOUT);
it("should stop healing from items", async() => {
game.override.startingHeldItems([{name: "LEFTOVERS"}]);
await game.classicMode.startBattle([Species.CHARIZARD]);
const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.getMaxHp() - 1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1);
}, TIMEOUT);
});