[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:
parent
1e70cb92d1
commit
4fb76fd117
|
@ -4831,11 +4831,9 @@ export function initAbilities() {
|
||||||
.bypassFaint(),
|
.bypassFaint(),
|
||||||
new Ability(Abilities.VOLT_ABSORB, 3)
|
new Ability(Abilities.VOLT_ABSORB, 3)
|
||||||
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC)
|
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC)
|
||||||
.partial() // Healing not blocked by Heal Block
|
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.WATER_ABSORB, 3)
|
new Ability(Abilities.WATER_ABSORB, 3)
|
||||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||||
.partial() // Healing not blocked by Heal Block
|
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.OBLIVIOUS, 3)
|
new Ability(Abilities.OBLIVIOUS, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
||||||
|
@ -4948,8 +4946,7 @@ export function initAbilities() {
|
||||||
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.RAIN_DISH, 3)
|
new Ability(Abilities.RAIN_DISH, 3)
|
||||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
|
||||||
.partial(), // Healing not blocked by Heal Block
|
|
||||||
new Ability(Abilities.SAND_STREAM, 3)
|
new Ability(Abilities.SAND_STREAM, 3)
|
||||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
|
||||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
|
||||||
|
@ -5080,7 +5077,6 @@ export function initAbilities() {
|
||||||
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
||||||
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25)
|
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25)
|
||||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||||
.partial() // Healing not blocked by Heal Block
|
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.DOWNLOAD, 4)
|
new Ability(Abilities.DOWNLOAD, 4)
|
||||||
.attr(DownloadAbAttr),
|
.attr(DownloadAbAttr),
|
||||||
|
@ -5161,8 +5157,7 @@ export function initAbilities() {
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.ICE_BODY, 4)
|
new Ability(Abilities.ICE_BODY, 4)
|
||||||
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
|
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
|
||||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
|
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW),
|
||||||
.partial(), // Healing not blocked by Heal Block
|
|
||||||
new Ability(Abilities.SOLID_ROCK, 4)
|
new Ability(Abilities.SOLID_ROCK, 4)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
|
@ -5332,8 +5327,7 @@ export function initAbilities() {
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new Ability(Abilities.CHEEK_POUCH, 6)
|
new Ability(Abilities.CHEEK_POUCH, 6)
|
||||||
.attr(HealFromBerryUseAbAttr, 1/3)
|
.attr(HealFromBerryUseAbAttr, 1/3),
|
||||||
.partial(), // Healing not blocked by Heal Block
|
|
||||||
new Ability(Abilities.PROTEAN, 6)
|
new Ability(Abilities.PROTEAN, 6)
|
||||||
.attr(PokemonTypeChangeAbAttr),
|
.attr(PokemonTypeChangeAbAttr),
|
||||||
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)), //Gen 9 Implementation
|
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)), //Gen 9 Implementation
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
||||||
import { StatusEffect } from "./status-effect";
|
import { StatusEffect } from "./status-effect";
|
||||||
import * as Utils from "../utils";
|
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 { Type } from "./type";
|
||||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
||||||
import { TerrainType } from "./terrain";
|
import { TerrainType } from "./terrain";
|
||||||
|
@ -141,6 +141,18 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||||
*/
|
*/
|
||||||
abstract isMoveRestricted(move: Moves): boolean;
|
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.
|
* 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.
|
* Tag that doubles the type effectiveness of Fire-type moves.
|
||||||
* @extends BattlerTag
|
* @extends BattlerTag
|
||||||
|
@ -2490,6 +2570,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||||
return new SubstituteTag(sourceMove, sourceId);
|
return new SubstituteTag(sourceMove, sourceId);
|
||||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||||
return new MysteryEncounterPostSummonTag();
|
return new MysteryEncounterPostSummonTag();
|
||||||
|
case BattlerTagType.HEAL_BLOCK:
|
||||||
|
return new HealBlockTag(turnCount, sourceMove);
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
|
|
@ -4539,6 +4539,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||||
case BattlerTagType.NIGHTMARE:
|
case BattlerTagType.NIGHTMARE:
|
||||||
case BattlerTagType.DROWSY:
|
case BattlerTagType.DROWSY:
|
||||||
case BattlerTagType.DISABLED:
|
case BattlerTagType.DISABLED:
|
||||||
|
case BattlerTagType.HEAL_BLOCK:
|
||||||
return -5;
|
return -5;
|
||||||
case BattlerTagType.SEEDED:
|
case BattlerTagType.SEEDED:
|
||||||
case BattlerTagType.SALT_CURED:
|
case BattlerTagType.SALT_CURED:
|
||||||
|
@ -7826,8 +7827,8 @@ export function initMoves() {
|
||||||
.makesContact()
|
.makesContact()
|
||||||
.attr(LessPPMorePowerAttr),
|
.attr(LessPPMorePowerAttr),
|
||||||
new StatusMove(Moves.HEAL_BLOCK, Type.PSYCHIC, 100, 15, -1, 0, 4)
|
new StatusMove(Moves.HEAL_BLOCK, Type.PSYCHIC, 100, 15, -1, 0, 4)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, true, 5)
|
||||||
.unimplemented(),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new AttackMove(Moves.WRING_OUT, Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4)
|
new AttackMove(Moves.WRING_OUT, Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4)
|
||||||
.attr(OpponentHighHpPowerAttr, 120)
|
.attr(OpponentHighHpPowerAttr, 120)
|
||||||
.makesContact(),
|
.makesContact(),
|
||||||
|
@ -9609,7 +9610,7 @@ export function initMoves() {
|
||||||
.recklessMove(),
|
.recklessMove(),
|
||||||
new AttackMove(Moves.PSYCHIC_NOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.PSYCHIC_NOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
|
||||||
.soundBased()
|
.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)
|
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
|
||||||
.attr(FlinchAttr)
|
.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?
|
.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?
|
||||||
|
|
|
@ -80,4 +80,5 @@ export enum BattlerTagType {
|
||||||
BURNED_UP = "BURNED_UP",
|
BURNED_UP = "BURNED_UP",
|
||||||
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
||||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||||
|
HEAL_BLOCK = "HEAL_BLOCK",
|
||||||
}
|
}
|
||||||
|
|
|
@ -2971,16 +2971,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
return this.getRestrictingTag(moveId) !== null;
|
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.
|
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
|
||||||
*
|
*
|
||||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
* @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.
|
* @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)) {
|
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
|
||||||
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
|
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
|
||||||
return tag as MoveRestrictionBattlerTag;
|
return tag as MoveRestrictionBattlerTag;
|
||||||
|
} else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
|
||||||
|
return tag as MoveRestrictionBattlerTag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -96,5 +96,7 @@
|
||||||
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
|
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
|
||||||
"congratulations": "Glückwunsch!",
|
"congratulations": "Glückwunsch!",
|
||||||
"beatModeFirstTime": "{{speciesName}} hat den {{gameMode}} Modus zum ersten Mal beendet! Du erhältst {{newModifier}}!",
|
"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!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,5 +105,7 @@
|
||||||
"congratulations": "Congratulations!",
|
"congratulations": "Congratulations!",
|
||||||
"beatModeFirstTime": "{{speciesName}} beat {{gameMode}} Mode for the first time!\nYou received {{newModifier}}!",
|
"beatModeFirstTime": "{{speciesName}} beat {{gameMode}} Mode for the first time!\nYou received {{newModifier}}!",
|
||||||
"ppReduced": "It reduced the PP of {{targetName}}'s\n{{moveName}} by {{reduction}}!",
|
"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!"
|
||||||
}
|
}
|
|
@ -85,5 +85,7 @@
|
||||||
"statSeverelyFell_one": "¡El {{stats}} de {{pokemonNameWithAffix}} ha bajado muchísimo!",
|
"statSeverelyFell_one": "¡El {{stats}} de {{pokemonNameWithAffix}} ha bajado muchísimo!",
|
||||||
"statSeverelyFell_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} han 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_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!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,5 +99,7 @@
|
||||||
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
|
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
|
||||||
"congratulations": "Félicitations !",
|
"congratulations": "Félicitations !",
|
||||||
"beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !",
|
"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}} s’est dissipé !"
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,5 +96,7 @@
|
||||||
"retryBattle": "Você gostaria de tentar novamente desde o início da batalha?",
|
"retryBattle": "Você gostaria de tentar novamente desde o início da batalha?",
|
||||||
"unlockedSomething": "{{unlockedThing}}\nfoi desbloqueado.",
|
"unlockedSomething": "{{unlockedThing}}\nfoi desbloqueado.",
|
||||||
"congratulations": "Parabéns!",
|
"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!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { HealAchv } from "#app/system/achv";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import { CommonAnimPhase } from "./common-anim-phase";
|
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 {
|
export class PokemonHealPhase extends CommonAnimPhase {
|
||||||
private hpHealed: integer;
|
private hpHealed: integer;
|
||||||
|
@ -50,9 +52,14 @@ export class PokemonHealPhase extends CommonAnimPhase {
|
||||||
|
|
||||||
const hasMessage = !!this.message;
|
const hasMessage = !!this.message;
|
||||||
const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0);
|
const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0);
|
||||||
|
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK) as HealBlockTag;
|
||||||
let lastStatusEffect = StatusEffect.NONE;
|
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);
|
const hpRestoreMultiplier = new Utils.IntegerHolder(1);
|
||||||
if (!this.revive) {
|
if (!this.revive) {
|
||||||
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
|
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { CommandPhase } from "./command-phase";
|
import { CommandPhase } from "./command-phase";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
import i18next from "#app/plugins/i18n";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
|
||||||
export class SelectTargetPhase extends PokemonPhase {
|
export class SelectTargetPhase extends PokemonPhase {
|
||||||
constructor(scene: BattleScene, fieldIndex: integer) {
|
constructor(scene: BattleScene, fieldIndex: integer) {
|
||||||
|
@ -17,6 +19,14 @@ export class SelectTargetPhase extends PokemonPhase {
|
||||||
const move = turnCommand?.move?.move;
|
const move = turnCommand?.move?.move;
|
||||||
this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => {
|
this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
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) {
|
if (targets.length < 1) {
|
||||||
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
|
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
|
||||||
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
|
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
Loading…
Reference in New Issue