Refactored healing phase + removed preventFullHp param

This commit is contained in:
Bertie690 2025-08-01 18:00:21 -04:00
parent cee6e8678a
commit 93c7ab7935
4 changed files with 79 additions and 91 deletions

View File

@ -2183,7 +2183,6 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr {
false,
false,
true,
false,
this.restorePP),
true);

View File

@ -24,11 +24,11 @@ import { NoCritTag, WeakenMoveScreenTag } from "#data/arena-tag";
import {
AutotomizedTag,
BattlerTag,
type BattlerTagTypeMap,
CritBoostTag,
EncoreTag,
ExposedTag,
GroundedTag,
type GrudgeTag,
getBattlerTag,
HighestStatBoostTag,
MoveRestrictionBattlerTag,
@ -4240,14 +4240,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
/**@overload */
getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | undefined;
/** @overload */
getTag(tagType: BattlerTagType.SUBSTITUTE): SubstituteTag | undefined;
/** @overload */
getTag(tagType: BattlerTagType): BattlerTag | undefined;
getTag<T extends BattlerTagType>(tagType: T): BattlerTagTypeMap[T] | undefined;
/** @overload */
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | undefined;

View File

@ -1933,7 +1933,6 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
* @returns always `true`
*/
override apply(pokemon: Pokemon): boolean {
// Restore the Pokemon to half HP
globalScene.phaseManager.unshiftNew(
"PokemonHealPhase",
pokemon.getBattlerIndex(),
@ -1948,6 +1947,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
);
// Remove the Pokemon's FAINT status
// TODO: Remove call to `resetStatus` once StatusEffect.FAINT is canned
pokemon.resetStatus(true, false, true, false);
// Reapply Commander on the Pokemon's side of the field, if applicable
@ -3549,24 +3549,26 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier {
* @returns `true` if the {@linkcode Pokemon} was healed
*/
override apply(enemyPokemon: Pokemon): boolean {
if (!enemyPokemon.isFullHp()) {
globalScene.phaseManager.unshiftNew(
"PokemonHealPhase",
enemyPokemon.getBattlerIndex(),
Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1),
i18next.t("modifier:enemyTurnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon),
}),
true,
false,
false,
false,
true,
);
return true;
if (enemyPokemon.isFullHp()) {
return false;
}
return false;
// Prevent healing to full from healing tokens
const healAmt = Math.min(
enemyPokemon.getMaxHp() - 1,
toDmgValue((enemyPokemon.getMaxHp() * this.stackCount * this.healPercent) / 100),
);
globalScene.phaseManager.unshiftNew(
"PokemonHealPhase",
enemyPokemon.getBattlerIndex(),
healAmt,
i18next.t("modifier:enemyTurnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon),
}),
true,
);
return true;
}
getMaxStackCount(): number {

View File

@ -1,12 +1,10 @@
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import type { HealBlockTag } from "#data/battler-tags";
import { getStatusEffectHealText } from "#data/status-effect";
import type { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type";
import { HitResult } from "#enums/hit-result";
import { CommonAnim } from "#enums/move-anims-common";
import { StatusEffect } from "#enums/status-effect";
import { HealingBoosterModifier } from "#modifiers/modifier";
import { CommonAnimPhase } from "#phases/common-anim-phase";
import { HealAchv } from "#system/achv";
@ -15,13 +13,16 @@ import i18next from "i18next";
export class PokemonHealPhase extends CommonAnimPhase {
public readonly phaseName = "PokemonHealPhase";
/** The base amount of HP to heal. */
private hpHealed: number;
/** The message to display upon healing the target, or `null` to show no message. */
private message: string | null;
/** Whether to show a message and quit out early upon healing a Pokemon already at full hp. */
private showFullHpMessage: boolean;
private skipAnim: boolean;
private revive: boolean;
private healStatus: boolean;
private preventFullHeal: boolean;
private fullRestorePP: boolean;
constructor(
@ -32,7 +33,6 @@ export class PokemonHealPhase extends CommonAnimPhase {
skipAnim = false,
revive = false,
healStatus = false,
preventFullHeal = false,
fullRestorePP = false,
) {
super(battlerIndex, undefined, CommonAnim.HEALTH_UP);
@ -43,12 +43,11 @@ export class PokemonHealPhase extends CommonAnimPhase {
this.skipAnim = skipAnim;
this.revive = revive;
this.healStatus = healStatus;
this.preventFullHeal = preventFullHeal;
this.fullRestorePP = fullRestorePP;
}
start() {
if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) {
if (!this.skipAnim && !this.getPokemon().isFullHp()) {
super.start();
} else {
this.end();
@ -58,78 +57,72 @@ export class PokemonHealPhase extends CommonAnimPhase {
end() {
const pokemon = this.getPokemon();
if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) {
return super.end();
// Prevent healing off-field pokemon
if (!pokemon.isActive(true)) {
super.end();
return;
}
const hasMessage = !!this.message;
const healOrDamage = !pokemon.isFullHp() || this.hpHealed < 0;
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK) as HealBlockTag;
let lastStatusEffect = StatusEffect.NONE;
// Check for heal block, ending the phase early if healing was prevented
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK);
if (healBlock && this.hpHealed > 0) {
globalScene.phaseManager.queueMessage(healBlock.onActivation(pokemon));
return super.end();
super.end();
return;
}
if (healOrDamage) {
const hpRestoreMultiplier = new NumberHolder(1);
if (!this.revive) {
globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
}
const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value));
if (healAmount.value < 0) {
pokemon.damageAndUpdate(healAmount.value * -1, { result: HitResult.INDIRECT });
healAmount.value = 0;
}
// Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock)
if (this.preventFullHeal && pokemon.hp + healAmount.value >= pokemon.getMaxHp()) {
healAmount.value = pokemon.getMaxHp() - pokemon.hp - 1;
}
healAmount.value = pokemon.heal(healAmount.value);
if (healAmount.value) {
globalScene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL);
}
if (pokemon.isPlayer()) {
globalScene.validateAchvs(HealAchv, healAmount);
if (healAmount.value > globalScene.gameData.gameStats.highestHeal) {
globalScene.gameData.gameStats.highestHeal = healAmount.value;
}
}
if (this.healStatus && !this.revive && pokemon.status) {
lastStatusEffect = pokemon.status.effect;
pokemon.resetStatus();
}
if (this.fullRestorePP) {
for (const move of this.getPokemon().getMoveset()) {
if (move) {
move.ppUsed = 0;
}
}
// If we would heal the user past full HP, don't.
if (this.hpHealed >= 0 && pokemon.isFullHp()) {
if (this.showFullHpMessage) {
globalScene.phaseManager.queueMessage(
i18next.t("battle:hpIsFull", {
pokemonName: getPokemonNameWithAffix(pokemon),
}),
);
}
super.end();
return;
}
// Apply the effect of healing charms for non-revival items
const hpRestoreMultiplier = new NumberHolder(1);
globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
let healAmount = Math.floor(this.hpHealed * hpRestoreMultiplier.value);
// If Liquid Ooze is active, damage the user for the healing amount, then return.
// TODO: Refactor liquid ooze to not use a heal phase to do damage
if (healAmount < 0) {
pokemon.damageAndUpdate(-healAmount, { result: HitResult.INDIRECT });
pokemon.updateInfo().then(() => super.end());
} else if (this.healStatus && !this.revive && pokemon.status) {
lastStatusEffect = pokemon.status.effect;
return;
}
// Heal the pokemon (capping at max HP), then show damage numbers and
// do achievement validation
healAmount = pokemon.heal(healAmount);
globalScene.damageNumberHandler.add(pokemon, healAmount, HitResult.HEAL);
if (pokemon.isPlayer()) {
globalScene.validateAchvs(HealAchv, healAmount);
globalScene.gameData.gameStats.highestHeal = Math.max(globalScene.gameData.gameStats.highestHeal, healAmount);
}
// Cure status as applicable
// TODO: This should not be the job of the healing phase
if (this.healStatus && pokemon.status) {
globalScene.phaseManager.queueMessage(
getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)),
);
pokemon.resetStatus();
pokemon.updateInfo().then(() => super.end());
} else if (this.showFullHpMessage) {
this.message = i18next.t("battle:hpIsFull", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
this.showMessage();
pokemon.updateInfo().then(() => super.end());
}
private showMessage(): void {
if (this.message) {
globalScene.phaseManager.queueMessage(this.message);
}
if (this.healStatus && lastStatusEffect && !hasMessage) {
globalScene.phaseManager.queueMessage(
getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon)),
);
}
if (!healOrDamage && !lastStatusEffect) {
super.end();
}
}
}