From 06ba63dd7dda5454f842593e0f190714404d41c0 Mon Sep 17 00:00:00 2001 From: td76099 <85713900+td76099@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:53:32 -0400 Subject: [PATCH] [Bug] Liquid Ooze hurts move user instead of one with ability (#1301) * Fixing Liquid Ooze to turn move user and not one with the ability as well as converted strength sap to use same logic instead of making it a separate class * Replaced "undefined" with "null" * Updated based on feedback + fix file permissions * Fixing file permission on battler-tags * Adding localization for drain message * Apparently this file is 755 unlike the others * Removed ability for custom message in exchange for getting to pass Pokemon name * Once again changing moves from 644 to 755 like it is in repo right now :) * putting ability back to 755 (why are file permissions all over the place) --- src/data/ability.ts | 22 +++++++++-- src/data/battler-tags.ts | 3 +- src/data/move.ts | 74 ++++++++++++++++++++++++------------- src/locales/de/battle.ts | 4 +- src/locales/en/battle.ts | 4 +- src/locales/es/battle.ts | 4 +- src/locales/fr/battle.ts | 4 +- src/locales/it/battle.ts | 4 +- src/locales/pt_BR/battle.ts | 4 +- src/locales/zh_CN/battle.ts | 4 +- src/locales/zh_TW/battle.ts | 4 +- src/phases.ts | 5 ++- 12 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 9cd8c61d47f..31f41492ba7 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; @@ -575,10 +575,26 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr { return ret; } } - +/** + * Class for abilities that make drain moves deal damage to user instead of healing them. + * @extends PostDefendAbAttr + * @see {@linkcode applyPostDefend} + */ export class ReverseDrainAbAttr extends PostDefendAbAttr { + /** + * Determines if a damage and draining move was used to check if this ability should stop the healing. + * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. + * Also displays a message to show this ability was activated. + * @param pokemon {@linkcode Pokemon} with this ability + * @param passive N/A + * @param attacker {@linkcode Pokemon} that is attacking this Pokemon + * @param move {@linkcode PokemonMove} that is being used + * @param hitResult N/A + * @args N/A + * @returns true if healing should be reversed on a healing move, false otherwise. + */ applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().hasAttr(HitHealAttr) || move.getMove().hasAttr(StrengthSapHealAttr) ) { + if (move.getMove().hasAttr(HitHealAttr)) { pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); return true; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 13c3954fd01..c4fd5dfb45b 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -391,7 +391,7 @@ export class SeedTag extends BattlerTag { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1)); - const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr); + const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, !reverseDrain ? getPokemonMessage(pokemon, "'s health is\nsapped by Leech Seed!") : getPokemonMessage(source, "'s Leech Seed\nsucked up the liquid ooze!"), @@ -1479,4 +1479,3 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { tag.loadTag(source); return tag; } - diff --git a/src/data/move.ts b/src/data/move.ts index 5b7bd5f6c82..594b9e73efe 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1201,48 +1201,72 @@ export class BoostHealAttr extends HealAttr { } } +/** + * Heals user as a side effect of a move that hits a target. + * Healing is based on {@linkcode healRatio} * the amount of damage dealt or a stat of the target. + * @extends MoveEffectAttr + * @see {@linkcode apply} + * @see {@linkcode getUserBenefitScore} + */ export class HitHealAttr extends MoveEffectAttr { private healRatio: number; + private message: string; + private healStat: Stat; - constructor(healRatio?: number) { + constructor(healRatio?: number, healStat?: Stat) { super(true, MoveEffectTrigger.HIT); this.healRatio = healRatio || 0.5; + this.healStat = healStat || null; } - + /** + * Heals the user the determined amount and possibly displays a message about regaining health. + * If the target has the {@linkcode ReverseDrainAbAttr}, all healing is instead converted + * to damage to the user. + * @param user {@linkcode Pokemon} using this move + * @param target {@linkcode Pokemon} target of this move + * @param move {@linkcode Move} being used + * @param args N/A + * @returns true if the function succeeds + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const healAmount = Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1); - const reverseDrain = user.hasAbilityWithAttr(ReverseDrainAbAttr); - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), - !reverseDrain ? healAmount : healAmount * -1, - !reverseDrain ? getPokemonMessage(target, " had its\nenergy drained!") : undefined, - false, true)); + let healAmount = 0; + let message = ""; + const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); + if (this.healStat) { + // Strength Sap formula + healAmount = target.getBattleStat(this.healStat); + message = i18next.t("battle:drainMessage", {pokemonName: target.name}); + } else { + // Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc. + healAmount = Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1); + message = i18next.t("battle:regainHealth", {pokemonName: user.name}); + } if (reverseDrain) { user.turnData.damageTaken += healAmount; + healAmount = healAmount * -1; + message = null; } + user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true)); return true; } + /** + * Used by the Enemy AI to rank an attack based on a given user + * @param user {@linkcode Pokemon} using this move + * @param target {@linkcode Pokemon} target of this move + * @param move {@linkcode Move} being used + * @returns an integer. Higher means enemy is more likely to use that move. + */ getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { - return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4)); + if (this.healStat) { + const healAmount = target.getBattleStat(this.healStat); + return Math.floor(Math.max(0, (Math.min(1, (healAmount+user.hp)/user.getMaxHp() - 0.33))) / user.getHpRatio()); + } + return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * (move.power / 4)); } } -export class StrengthSapHealAttr extends MoveEffectAttr { - constructor() { - super(true, MoveEffectTrigger.HIT); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const healAmount = target.stats[Stat.ATK] * (Math.max(2, 2 + target.summonData.battleStats[BattleStat.ATK]) / Math.max(2, 2 - target.summonData.battleStats[BattleStat.ATK])); - const reverseDrain = user.hasAbilityWithAttr(ReverseDrainAbAttr); - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), - !reverseDrain ? healAmount : healAmount * -1, - !reverseDrain ? getPokemonMessage(user, " regained\nhealth!") : undefined, - false, true)); - return true; - } -} /** * Attribute used for moves that change priority in a turn given a condition, * e.g. Grassy Glide @@ -6918,7 +6942,7 @@ export function initMoves() { .triageMove(), new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7), new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, 100, 0, 7) - .attr(StrengthSapHealAttr) + .attr(HitHealAttr, null, Stat.ATK) .attr(StatChangeAttr, BattleStat.ATK, -1) .condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6) .triageMove(), diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index e60833cb27a..8c153aa77eb 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Bist du sicher, dass du kein Item nehmen willst?", "notDisabled": "{{pokemonName}}'s {{moveName}} ist\nnicht mehr deaktiviert!", "eggHatching": "Oh?", - "ivScannerUseQuestion": "IV-Scanner auf {{pokemonName}} benutzen?" + "ivScannerUseQuestion": "IV-Scanner auf {{pokemonName}} benutzen?", + "drainMessage": "{{pokemonName}} wurde Energie abgesaugt", + "regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!" } as const; diff --git a/src/locales/en/battle.ts b/src/locales/en/battle.ts index a2eb9e4a9c9..a3fa41d9b76 100644 --- a/src/locales/en/battle.ts +++ b/src/locales/en/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}'s {{moveName}} is disabled\nno more!", "skipItemQuestion": "Are you sure you want to skip taking an item?", "eggHatching": "Oh?", - "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?" + "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index 7b7b71c464d..af090153a04 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "¡El movimiento {{moveName}} de {{pokemonName}}\nya no está anulado!", "skipItemQuestion": "¿Estás seguro de que no quieres coger un objeto?", "eggHatching": "¿Y esto?", - "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?" + "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/fr/battle.ts b/src/locales/fr/battle.ts index b1ae6f48f35..535ed4b6ade 100644 --- a/src/locales/fr/battle.ts +++ b/src/locales/fr/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "La capacité {{moveName}}\nde {{pokemonName}} n’est plus sous entrave !", "skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre d’objet ?", "eggHatching": "Oh ?", - "ivScannerUseQuestion": "Utiliser le Scanner d’IV sur {{pokemonName}} ?" + "ivScannerUseQuestion": "Utiliser le Scanner d’IV sur {{pokemonName}} ?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index 444e1f01bf0..5b8089e6677 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}'s {{moveName}} non è più\ndisabilitata!", "skipItemQuestion": "Sei sicuro di non voler prendere nessun oggetto?", "eggHatching": "Oh!", - "ivScannerUseQuestion": "Vuoi usare lo scanner di IV su {{pokemonName}}?" + "ivScannerUseQuestion": "Vuoi usare lo scanner di IV su {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index b96266ac189..b3563665d9e 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "O movimento {{moveName}}\nnão está mais desabilitado!", "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", - "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?" + "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index 6eb400f5176..cd6357608ac 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{moveName}} 不再被禁用!", "skipItemQuestion": "你确定要跳过拾取道具吗?", "eggHatching": "咦?", - "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?" + "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_TW/battle.ts b/src/locales/zh_TW/battle.ts index c1fd9155dae..c0846e1cb18 100644 --- a/src/locales/zh_TW/battle.ts +++ b/src/locales/zh_TW/battle.ts @@ -52,5 +52,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{moveName}} 不再被禁用!", "skipItemQuestion": "你要跳過拾取道具嗎?", "eggHatching": "咦?", - "ivScannerUseQuestion": "對 {{pokemonName}} 使用個體值掃描?" + "ivScannerUseQuestion": "對 {{pokemonName}} 使用個體值掃描?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/phases.ts b/src/phases.ts index 8b5e8731faf..b896c6f9484 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4483,9 +4483,10 @@ export class PokemonHealPhase extends CommonAnimPhase { const fullHp = pokemon.getHpRatio() >= 1; const hasMessage = !!this.message; + const healOrDamage = (!fullHp || this.hpHealed < 0); let lastStatusEffect = StatusEffect.NONE; - if (!fullHp || this.hpHealed < 0) { + if (healOrDamage) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); if (!this.revive) { this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); @@ -4530,7 +4531,7 @@ export class PokemonHealPhase extends CommonAnimPhase { this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectHealText(lastStatusEffect))); } - if (fullHp && !lastStatusEffect) { + if (!healOrDamage && !lastStatusEffect) { super.end(); } }