Cleaned up pokemonHealPhase + wrapped inside an object

This commit is contained in:
Bertie690 2025-08-05 12:53:10 -04:00
parent e760ed9949
commit 90c9c71cd9
9 changed files with 276 additions and 206 deletions

View File

@ -748,16 +748,12 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
const { pokemon, cancelled, simulated, passive } = params; const { pokemon, cancelled, simulated, passive } = params;
if (!pokemon.isFullHp() && !simulated) { if (!pokemon.isFullHp() && !simulated) {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew("PokemonHealPhase", pokemon.getBattlerIndex(), pokemon.getMaxHp() / 4, {
"PokemonHealPhase", message: i18next.t("abilityTriggers:typeImmunityHeal", {
pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 4),
i18next.t("abilityTriggers:typeImmunityHeal", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
}), }),
true, });
);
cancelled.value = true; // Suppresses "No Effect" message cancelled.value = true; // Suppresses "No Effect" message
} }
} }
@ -2830,12 +2826,13 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
"PokemonHealPhase", "PokemonHealPhase",
target.getBattlerIndex(), target.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / this.healRatio), toDmgValue(pokemon.getMaxHp() / this.healRatio),
i18next.t("abilityTriggers:postSummonAllyHeal", { {
pokemonNameWithAffix: getPokemonNameWithAffix(target), message: i18next.t("abilityTriggers:postSummonAllyHeal", {
pokemonName: pokemon.name, pokemonNameWithAffix: getPokemonNameWithAffix(target),
}), pokemonName: pokemon.name,
true, }),
!this.showAnim, skipAnim: !this.showAnim,
},
); );
} }
} }
@ -4476,11 +4473,12 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)),
i18next.t("abilityTriggers:postWeatherLapseHeal", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("abilityTriggers:postWeatherLapseHeal", {
abilityName, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), abilityName,
true, }),
},
); );
} }
} }
@ -4595,8 +4593,12 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 8), toDmgValue(pokemon.getMaxHp() / 8),
i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), {
true, message: i18next.t("abilityTriggers:poisonHeal", {
pokemonName: getPokemonNameWithAffix(pokemon),
abilityName,
}),
},
); );
} }
} }
@ -4843,11 +4845,12 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16), toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("abilityTriggers:postTurnHeal", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("abilityTriggers:postTurnHeal", {
abilityName, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), abilityName,
true, }),
},
); );
} }
} }
@ -5224,11 +5227,12 @@ export class HealFromBerryUseAbAttr extends AbAttr {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() * this.healPercent), toDmgValue(pokemon.getMaxHp() * this.healPercent),
i18next.t("abilityTriggers:healFromBerryUse", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("abilityTriggers:healFromBerryUse", {
abilityName, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), abilityName,
true, }),
},
); );
} }
} }

View File

@ -1076,18 +1076,16 @@ export class SeedTag extends SerializableBattlerTag {
); );
// Damage the target and restore our HP (or take damage in the case of liquid ooze) // Damage the target and restore our HP (or take damage in the case of liquid ooze)
// TODO: Liquid ooze should queue a damage anim phase directly
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false); const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew("PokemonHealPhase", source.getBattlerIndex(), reverseDrain ? -damage : damage, {
"PokemonHealPhase", message: i18next.t(reverseDrain ? "battlerTags:seededLapseShed" : "battlerTags:seededLapse", {
source.getBattlerIndex(),
reverseDrain ? -damage : damage,
i18next.t(reverseDrain ? "battlerTags:seededLapseShed" : "battlerTags:seededLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
false, showFullHpMessage: false,
true, skipAnim: true,
); });
return true; return true;
} }
@ -1382,10 +1380,11 @@ export class IngrainTag extends TrappedTag {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16), toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:ingrainLapse", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("battlerTags:ingrainLapse", {
}), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
true, }),
},
); );
} }
@ -1455,11 +1454,12 @@ export class AquaRingTag extends SerializableBattlerTag {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16), toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:aquaRingLapse", { {
moveName: this.getMoveName(), message: i18next.t("battlerTags:aquaRingLapse", {
pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(),
}), pokemonName: getPokemonNameWithAffix(pokemon),
true, }),
},
); );
} }

View File

@ -73,16 +73,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
{ {
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed }); applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed });
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew("PokemonHealPhase", consumer.getBattlerIndex(), hpHealed.value, {
"PokemonHealPhase", message: i18next.t("battle:hpHealBerry", {
consumer.getBattlerIndex(),
hpHealed.value,
i18next.t("battle:hpHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(consumer), pokemonNameWithAffix: getPokemonNameWithAffix(consumer),
berryName: getBerryName(berryType), berryName: getBerryName(berryType),
}), }),
true, });
);
} }
break; break;
case BerryType.LUM: case BerryType.LUM:

View File

@ -1983,7 +1983,13 @@ export class HealAttr extends MoveEffectAttr {
*/ */
protected addHealPhase(target: Pokemon, healRatio: number) { protected addHealPhase(target: Pokemon, healRatio: number) {
globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(),
toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim); toDmgValue(target.getMaxHp() * healRatio),
{
message: i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }),
showFullHpMessage: true,
skipAnim: !this.showAnim,
}
);
} }
override getTargetBenefitScore(user: Pokemon, target: Pokemon, _move: Move): number { override getTargetBenefitScore(user: Pokemon, target: Pokemon, _move: Move): number {
@ -2175,16 +2181,19 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr {
const pm = globalScene.phaseManager; const pm = globalScene.phaseManager;
pm.pushPhase( pm.pushPhase(
pm.create("PokemonHealPhase", pm.create(
"PokemonHealPhase",
user.getBattlerIndex(), user.getBattlerIndex(),
maxPartyMemberHp, maxPartyMemberHp,
i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }), {
true, message: i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }),
false, showFullHpMessage: false,
false, skipAnim: true,
true, healStatus: true,
this.restorePP), fullRestorePP: this.restorePP,
true); }
),
true);
return true; return true;
} }
@ -2280,7 +2289,9 @@ export class HitHealAttr extends MoveEffectAttr {
message = ""; message = "";
} }
} }
globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), healAmount, message, false, true); globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), healAmount,
{message, showFullHpMessage: false, skipAnim: true}
);
return true; return true;
} }
@ -4313,7 +4324,8 @@ export class PunishmentPowerAttr extends VariablePowerAttr {
} }
export class PresentPowerAttr extends VariablePowerAttr { export class PresentPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: [NumberHolder]): boolean {
const power = args[0]
/** /**
* If this move is multi-hit, and this attribute is applied to any hit * If this move is multi-hit, and this attribute is applied to any hit
* other than the first, this move cannot result in a heal. * other than the first, this move cannot result in a heal.
@ -4322,17 +4334,21 @@ export class PresentPowerAttr extends VariablePowerAttr {
const powerSeed = randSeedInt(firstHit ? 100 : 80); const powerSeed = randSeedInt(firstHit ? 100 : 80);
if (powerSeed <= 40) { if (powerSeed <= 40) {
(args[0] as NumberHolder).value = 40; power.value = 40;
} else if (40 < powerSeed && powerSeed <= 70) { } else if (powerSeed <= 70) {
(args[0] as NumberHolder).value = 80; power.value = 80;
} else if (70 < powerSeed && powerSeed <= 80) { } else if (powerSeed <= 80) {
(args[0] as NumberHolder).value = 120; power.value = 120;
} else if (80 < powerSeed && powerSeed <= 100) { } else if (powerSeed <= 100) {
// If this move is multi-hit, disable all other hits // Disable all other hits and heal the target for 25% max HP
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1; user.turnData.hitsLeft = 1;
globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), globalScene.phaseManager.unshiftNew(
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true); "PokemonHealPhase",
target.getBattlerIndex(),
toDmgValue(target.getMaxHp() / 4),
{message: i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) })}
)
} }
return true; return true;

View File

@ -155,13 +155,12 @@ export class WishTag extends PositionalTag implements WishArgs {
public override trigger(): void { public override trigger(): void {
// TODO: Rename this locales key - wish shows a message on REMOVAL, not addition // TODO: Rename this locales key - wish shows a message on REMOVAL, not addition
globalScene.phaseManager.queueMessage( // TODO: What messages does Wish show when healing a Pokemon at full HP?
i18next.t("arenaTag:wishTagOnAdd", { globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.targetIndex, this.healHp, {
message: i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: this.pokemonName, pokemonNameWithAffix: this.pokemonName,
}), }),
); });
globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.targetIndex, this.healHp, null, true, false);
} }
public override shouldTrigger(): boolean { public override shouldTrigger(): boolean {

View File

@ -1674,11 +1674,12 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount,
i18next.t("modifier:turnHealApply", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("modifier:turnHealApply", {
typeName: this.type.name, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), typeName: this.type.name,
true, }),
},
); );
return true; return true;
} }
@ -1766,16 +1767,16 @@ export class HitHealModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) {
// TODO: this shouldn't be undefined AFAIK
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, toDmgValue((pokemon.turnData.totalDamageDealt * this.stackCount) / 8),
i18next.t("modifier:hitHealApply", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("modifier:hitHealApply", {
typeName: this.type.name, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), typeName: this.type.name,
true, }),
},
); );
} }
@ -1934,20 +1935,22 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
// Restore the Pokemon to half HP // Restore the Pokemon to half HP
// TODO: This should not use a phase to revive pokemon
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 2), toDmgValue(pokemon.getMaxHp() / 2),
i18next.t("modifier:pokemonInstantReviveApply", { {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), message: i18next.t("modifier:pokemonInstantReviveApply", {
typeName: this.type.name, pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), typeName: this.type.name,
false, }),
false, revive: true,
true, },
); );
// Remove the Pokemon's FAINT status // Remove the Pokemon's FAINT status
// TODO: Remove call to `resetStatus` once StatusEffect.FAINT is canned
pokemon.resetStatus(true, false, true, false); pokemon.resetStatus(true, false, true, false);
// Reapply Commander on the Pokemon's side of the field, if applicable // Reapply Commander on the Pokemon's side of the field, if applicable
@ -3549,24 +3552,24 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier {
* @returns `true` if the {@linkcode Pokemon} was healed * @returns `true` if the {@linkcode Pokemon} was healed
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (!enemyPokemon.isFullHp()) { if (enemyPokemon.isFullHp()) {
globalScene.phaseManager.unshiftNew( return false;
"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;
} }
return false; // Prevent healing to full from healing tokens
globalScene.phaseManager.unshiftNew(
"PokemonHealPhase",
enemyPokemon.getBattlerIndex(),
(enemyPokemon.getMaxHp() * this.stackCount * this.healPercent) / 100,
{
message: i18next.t("modifier:enemyTurnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon),
}),
preventFullHeal: true,
},
);
return true;
} }
getMaxStackCount(): number { getMaxStackCount(): number {

View File

@ -1,39 +1,81 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type { HealBlockTag } from "#data/battler-tags";
import { getStatusEffectHealText } from "#data/status-effect"; import { getStatusEffectHealText } from "#data/status-effect";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { HitResult } from "#enums/hit-result"; import { HitResult } from "#enums/hit-result";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { StatusEffect } from "#enums/status-effect";
import { HealingBoosterModifier } from "#modifiers/modifier"; import { HealingBoosterModifier } from "#modifiers/modifier";
import { CommonAnimPhase } from "#phases/common-anim-phase"; import { CommonAnimPhase } from "#phases/common-anim-phase";
import { HealAchv } from "#system/achv"; import { HealAchv } from "#system/achv";
import { NumberHolder } from "#utils/common"; import { NumberHolder, toDmgValue } from "#utils/common";
import i18next from "i18next"; import i18next from "i18next";
export class PokemonHealPhase extends CommonAnimPhase { export class PokemonHealPhase extends CommonAnimPhase {
public readonly phaseName = "PokemonHealPhase"; public readonly phaseName = "PokemonHealPhase";
/** The base amount of HP to heal. */
private hpHealed: number; private hpHealed: number;
private message: string | null; /**
* The message to display upon healing the target, or `undefined` to show no message.
* Will be overridden by the full HP message if {@linkcode showFullHpMessage} is set to `true`
*/
private message: string | undefined;
/**
* Whether to show a failure message upon healing a Pokemon already at full HP.
* @defaultValue `true`
*/
private showFullHpMessage: boolean; private showFullHpMessage: boolean;
/**
* Whether to skip showing the healing animation.
* @defaultValue `false`
*/
private skipAnim: boolean; private skipAnim: boolean;
/**
* Whether to revive the affected Pokemon in addition to healing.
* Revives will not be affected by any Healing Charms.
* @todo Remove post modifier rework as revives will not be using phases to heal stuff
* @defaultValue `false`
*/
private revive: boolean; private revive: boolean;
/**
* Whether to heal the affected Pokemon's status condition.
* @todo This should not be the healing phase's job
* @defaultValue `false`
*/
private healStatus: boolean; private healStatus: boolean;
/**
* Whether to prevent fully healing affected Pokemon, leaving them 1 HP below full.
* @defaultValue `false`
*/
private preventFullHeal: boolean; private preventFullHeal: boolean;
/**
* Whether to fully restore PP upon healing.
* @todo This should not be the healing phase's job
* @defaultValue `false`
*/
private fullRestorePP: boolean; private fullRestorePP: boolean;
constructor( constructor(
battlerIndex: BattlerIndex, battlerIndex: BattlerIndex,
hpHealed: number, hpHealed: number,
message: string | null, {
showFullHpMessage: boolean, message,
skipAnim = false, showFullHpMessage = true,
revive = false, skipAnim = false,
healStatus = false, revive = false,
preventFullHeal = false, healStatus = false,
fullRestorePP = false, preventFullHeal = false,
fullRestorePP = false,
}: {
message?: string;
showFullHpMessage?: boolean;
skipAnim?: boolean;
revive?: boolean;
healStatus?: boolean;
preventFullHeal?: boolean;
fullRestorePP?: boolean;
} = {},
) { ) {
super(battlerIndex, undefined, CommonAnim.HEALTH_UP); super(battlerIndex, undefined, CommonAnim.HEALTH_UP);
@ -47,89 +89,108 @@ export class PokemonHealPhase extends CommonAnimPhase {
this.fullRestorePP = fullRestorePP; this.fullRestorePP = fullRestorePP;
} }
start() { override start() {
if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) { // Only play animation if not skipped and target is at full HP
if (!this.skipAnim && !this.getPokemon().isFullHp()) {
super.start(); super.start();
} else {
this.end();
} }
this.heal();
super.end();
} }
end() { private heal() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) { // Prevent healing off-field pokemon unless via revives
return super.end(); // TODO: Revival effects shouldn't use this phase
if (!this.revive && !pokemon.isActive(true)) {
super.end();
return;
} }
const hasMessage = !!this.message; // Check for heal block, ending the phase early if healing was prevented
const healOrDamage = !pokemon.isFullHp() || this.hpHealed < 0; const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK);
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK) as HealBlockTag;
let lastStatusEffect = StatusEffect.NONE;
if (healBlock && this.hpHealed > 0) { if (healBlock && this.hpHealed > 0) {
globalScene.phaseManager.queueMessage(healBlock.onActivation(pokemon)); globalScene.phaseManager.queueMessage(healBlock.onActivation(pokemon));
this.message = null; super.end();
return super.end(); return;
} }
if (healOrDamage) {
const hpRestoreMultiplier = new NumberHolder(1); this.doHealPokemon();
if (!this.revive) {
globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); // Cure status as applicable
} // TODO: This should not be the job of the healing phase
const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (this.healStatus && pokemon.status) {
if (healAmount.value < 0) { this.message = getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon));
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;
}
}
}
pokemon.updateInfo().then(() => super.end());
} else if (this.healStatus && !this.revive && pokemon.status) {
lastStatusEffect = pokemon.status.effect;
pokemon.resetStatus(); pokemon.resetStatus();
pokemon.updateInfo().then(() => super.end()); }
} else if (this.showFullHpMessage) {
this.message = i18next.t("battle:hpIsFull", { // Restore PP.
pokemonName: getPokemonNameWithAffix(pokemon), // TODO: This should not be the job of the healing phase
if (this.fullRestorePP) {
pokemon.getMoveset().forEach(m => {
m.ppUsed = 0;
}); });
} }
// Show message, update info boxes and then wrap up.
if (this.message) { if (this.message) {
globalScene.phaseManager.queueMessage(this.message); globalScene.phaseManager.queueMessage(this.message);
} }
pokemon.updateInfo().then(() => super.end());
}
if (this.healStatus && lastStatusEffect && !hasMessage) { /**
globalScene.phaseManager.queueMessage( * Heal the Pokemon affected by this Phase.
getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon)), */
); private doHealPokemon(): void {
const pokemon = this.getPokemon()!;
// If we would heal the user past full HP, don't.
if (this.hpHealed > 0 && pokemon.isFullHp()) {
if (this.showFullHpMessage) {
this.message = i18next.t("battle:hpIsFull", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
return;
} }
if (!healOrDamage && !lastStatusEffect) { const healAmount = this.getHealAmount();
super.end();
if (healAmount < 0) {
// If Liquid Ooze is active, damage the user for the healing amount, then return.
// TODO: Consider refactoring liquid ooze to not use a heal phase to do damage
pokemon.damageAndUpdate(-healAmount, { result: HitResult.INDIRECT });
return;
}
// Heal the pokemon, then show damage numbers and validate achievements.
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);
} }
} }
/**
* Calculate the amount of HP to be healed during this Phase.
* @returns The updated healing amount, rounded down and capped at the Pokemon's maximum HP.
* @todo Prevent double rounding from callers
*/
private getHealAmount(): number {
if (this.revive) {
return toDmgValue(this.hpHealed);
}
// Apply the effect of healing charms for non-revival items before rounding down and capping at max HP
// (or 1 below max for healing tokens).
// Liquid Ooze damage (being negative) remains uncapped as normal.
const healMulti = new NumberHolder(1);
globalScene.applyModifiers(HealingBoosterModifier, this.player, healMulti);
return toDmgValue(Math.min(this.hpHealed * healMulti.value, this.getPokemon().getMaxHp() - +this.preventFullHeal));
}
} }

View File

@ -154,16 +154,11 @@ export class QuietFormChangePhase extends BattlePhase {
this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED);
if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon.isEnemy()) { if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon.isEnemy()) {
globalScene.playBgm(); globalScene.playBgm();
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), {
"PokemonHealPhase", showFullHpMessage: false,
this.pokemon.getBattlerIndex(), healStatus: true,
this.pokemon.getMaxHp(), fullRestorePP: true,
null, });
false,
false,
false,
true,
);
this.pokemon.findAndRemoveTags(() => true); this.pokemon.findAndRemoveTags(() => true);
this.pokemon.bossSegments = 5; this.pokemon.bossSegments = 5;
this.pokemon.bossSegmentIndex = 4; this.pokemon.bossSegmentIndex = 4;

View File

@ -35,15 +35,11 @@ export class TurnEndPhase extends FieldPhase {
globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew("PokemonHealPhase", pokemon.getBattlerIndex(), pokemon.getMaxHp() / 16, {
"PokemonHealPhase", message: i18next.t("battle:turnEndHpRestore", {
pokemon.getBattlerIndex(),
Math.max(pokemon.getMaxHp() >> 4, 1),
i18next.t("battle:turnEndHpRestore", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
}), }),
true, });
);
} }
if (!pokemon.isPlayer()) { if (!pokemon.isPlayer()) {