From 8d6a0bb0a1f1d03a0ebc266ba330b0e49f60b5b5 Mon Sep 17 00:00:00 2001 From: Adrian T <68144167+torranx@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:49:50 +0800 Subject: [PATCH] [Ability] Implement Ice Face (#1755) * implement ice face ability * remove showing ability bar * fixes * add documentations * move code out of phases.ts * add hardcoded eiscue check, localization * add KO locale * remove unnecessary constructor * use && instead of || to specify ice form on eiscue --- .../cry/{875-noice.m4a => 875-no-ice.m4a} | Bin src/battle-scene.ts | 7 +++ src/data/ability.ts | 52 ++++++++++++++++- src/data/battler-tags.ts | 55 ++++++++++++++++++ src/data/enums/battler-tag-type.ts | 3 +- src/data/pokemon-forms.ts | 4 ++ src/locales/de/ability-trigger.ts | 1 + src/locales/en/ability-trigger.ts | 3 +- src/locales/es/ability-trigger.ts | 1 + src/locales/fr/ability-trigger.ts | 3 +- src/locales/it/ability-trigger.ts | 1 + src/locales/ko/ability-trigger.ts | 1 + src/locales/pt_BR/ability-trigger.ts | 3 +- src/locales/zh_CN/ability-trigger.ts | 3 +- src/locales/zh_TW/ability-trigger.ts | 3 +- 15 files changed, 132 insertions(+), 8 deletions(-) rename public/audio/cry/{875-noice.m4a => 875-no-ice.m4a} (100%) diff --git a/public/audio/cry/875-noice.m4a b/public/audio/cry/875-no-ice.m4a similarity index 100% rename from public/audio/cry/875-noice.m4a rename to public/audio/cry/875-no-ice.m4a diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a873fab80b2..638314b3444 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -59,6 +59,7 @@ import {InputsController} from "./inputs-controller"; import {UiInputs} from "./ui-inputs"; import { MoneyFormat } from "./enums/money-format"; import { NewArenaEvent } from "./battle-scene-events"; +import { Abilities } from "./data/enums/abilities"; import ArenaFlyout from "./ui/arena-flyout"; import { EaseType } from "./ui/enums/ease-type"; @@ -1048,6 +1049,12 @@ export default class BattleScene extends SceneBase { if (resetArenaState) { this.arena.removeAllTags(); playerField.forEach((_, p) => this.unshiftPhase(new ReturnPhase(this, p))); + + for (const pokemon of this.getParty()) { + if (pokemon.hasAbility(Abilities.ICE_FACE)) { + pokemon.formIndex = 0; + } + } this.unshiftPhase(new ShowTrainerPhase(this)); } for (const pokemon of this.getParty()) { diff --git a/src/data/ability.ts b/src/data/ability.ts index 4bb4f36350e..db44835a86a 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3344,6 +3344,48 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt } } +/** + * Applies immunity to physical moves. + * This is used in Ice Face ability. + */ +export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr { + /** + * Applies the Ice Face pre-defense ability to the Pokémon. + * Removes BattlerTagType.ICE_FACE hit by physical attack and is in Ice Face form. + * + * @param {Pokemon} pokemon - The Pokémon with the Ice Face ability. + * @param {boolean} passive - Whether the ability is passive. + * @param {Pokemon} attacker - The attacking Pokémon. + * @param {PokemonMove} move - The move being used. + * @param {Utils.BooleanHolder} cancelled - A holder for whether the move was cancelled. + * @param {any[]} args - Additional arguments. + * @returns {boolean} - Whether the immunity was applied. + */ + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + + if (isImmune) { + const simulated = args.length > 1 && args[1]; + if (!simulated) { + pokemon.removeTag(BattlerTagType.ICE_FACE); + } + } + + return isImmune; + } + + /** + * Gets the message triggered when the Pokémon avoids damage using the Ice Face ability. + * @param {Pokemon} pokemon - The Pokémon with the Ice Face ability. + * @param {string} abilityName - The name of the ability. + * @param {...any} args - Additional arguments. + * @returns {string} - The trigger message. + */ + getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + return i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonName: pokemon.name, abilityName: abilityName }); + } +} + function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { return new Promise(resolve => { @@ -4330,8 +4372,14 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(NoFusionAbilityAbAttr) - .ignorable() - .unimplemented(), + // Add BattlerTagType.ICE_FACE if the pokemon is in ice face form + .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0, false) + // When summoned with active HAIL or SNOW, add BattlerTagType.ICE_FACE + .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) + // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE + .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) + .attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.getMove().category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE)) + .ignorable(), new Ability(Abilities.POWER_SPOT, 8) .unimplemented(), new Ability(Abilities.MIMICRY, 8) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 50d4b62decb..bfdeb2189dc 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -15,6 +15,8 @@ import { TerrainType } from "./terrain"; import { WeatherType } from "./weather"; import { BattleStat } from "./battle-stat"; import { allAbilities } from "./ability"; +import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; +import { Species } from "./enums/species"; export enum BattlerTagLapseType { FAINT, @@ -1354,6 +1356,57 @@ export class CursedTag extends BattlerTag { } } +/** + * Provides the Ice Face ability's effects. + */ +export class IceFaceTag extends BattlerTag { + constructor(sourceMove: Moves) { + super(BattlerTagType.ICE_FACE, BattlerTagLapseType.CUSTOM, 1, sourceMove); + } + + /** + * Determines if the Ice Face tag can be added to the Pokémon. + * @param {Pokemon} pokemon - The Pokémon to which the tag might be added. + * @returns {boolean} - True if the tag can be added, false otherwise. + */ + canAdd(pokemon: Pokemon): boolean { + const weatherType = pokemon.scene.arena.weather?.weatherType; + const isWeatherSnowOrHail = weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW; + const isFormIceFace = pokemon.formIndex === 0; + + + // Hard code Eiscue for now, this is to prevent the game from crashing if fused pokemon has Ice Face + if ((pokemon.species.speciesId === Species.EISCUE && isFormIceFace) || isWeatherSnowOrHail) { + return true; + } + return false; + } + + /** + * Applies the Ice Face tag to the Pokémon. + * Triggers a form change to Ice Face if the Pokémon is not in its Ice Face form. + * @param {Pokemon} pokemon - The Pokémon to which the tag is added. + */ + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + if (pokemon.formIndex !== 0) { + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + } + } + + /** + * Removes the Ice Face tag from the Pokémon. + * Triggers a form change to Noice when the tag is removed. + * @param {Pokemon} pokemon - The Pokémon from which the tag is removed. + */ + onRemove(pokemon: Pokemon): void { + super.onRemove(pokemon); + + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + } +} + export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves, sourceId: integer): BattlerTag { switch (tagType) { case BattlerTagType.RECHARGING: @@ -1467,6 +1520,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new MinimizeTag(); case BattlerTagType.DESTINY_BOND: return new DestinyBondTag(sourceMove, sourceId); + case BattlerTagType.ICE_FACE: + return new IceFaceTag(sourceMove); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index 6d36cdafab5..98f01c9c375 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -57,5 +57,6 @@ export enum BattlerTagType { GROUNDED = "GROUNDED", MAGNET_RISEN = "MAGNET_RISEN", MINIMIZED = "MINIMIZED", - DESTINY_BOND = "DESTINY_BOND" + DESTINY_BOND = "DESTINY_BOND", + ICE_FACE = "ICE_FACE" } diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index d87e4d90299..da6bac42ac7 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -730,6 +730,10 @@ export const pokemonFormChanges: PokemonFormChanges = { [Species.GALAR_DARMANITAN]: [ new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.GALAR_DARMANITAN, "zen", "", new SpeciesFormChangeManualTrigger(), true) + ], + [Species.EISCUE]: [ + new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true), ] }; diff --git a/src/locales/de/ability-trigger.ts b/src/locales/de/ability-trigger.ts index 07853233a01..d3fbdfc1b77 100644 --- a/src/locales/de/ability-trigger.ts +++ b/src/locales/de/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} wurde durch {{abilityName}}\nvor Rückstoß geschützt!", "badDreams": "{{pokemonName}} ist in einem Alptraum gefangen!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/en/ability-trigger.ts b/src/locales/en/ability-trigger.ts index b6e4c7c67fd..a99053785ab 100644 --- a/src/locales/en/ability-trigger.ts +++ b/src/locales/en/ability-trigger.ts @@ -5,5 +5,6 @@ export const abilityTriggers: SimpleTranslationEntries = { "badDreams": "{{pokemonName}} is tormented!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", "perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!", - "poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!" + "poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/es/ability-trigger.ts b/src/locales/es/ability-trigger.ts index 594a67628e8..3ede2cd9cf3 100644 --- a/src/locales/es/ability-trigger.ts +++ b/src/locales/es/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "¡{{abilityName}} de {{pokemonName}}\nlo protegió del daño de retroceso!", "badDreams": "¡{{pokemonName}} está atormentado!", "windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/fr/ability-trigger.ts b/src/locales/fr/ability-trigger.ts index eb450117238..f3311118591 100644 --- a/src/locales/fr/ability-trigger.ts +++ b/src/locales/fr/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}}\nde {{pokemonName}} le protège du contrecoup !", "badDreams": "{{pokemonName}} a le sommeil agité !", - "windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !" + "windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 61053896c49..8aa81f6090d 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!", "badDreams": "{{pokemonName}} è tormentato!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index 7fc98edce76..8fed1208fff 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -3,4 +3,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", "badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 8c57fb5b98c..19b043094a5 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", "badDreams": "{{pokemonName}} está tendo pesadelos!", - "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!" + "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/zh_CN/ability-trigger.ts b/src/locales/zh_CN/ability-trigger.ts index 643952ad352..07fedd8ac3e 100644 --- a/src/locales/zh_CN/ability-trigger.ts +++ b/src/locales/zh_CN/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!", "badDreams": "{{pokemonName}} 被折磨着!", - "windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!" + "windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/zh_TW/ability-trigger.ts b/src/locales/zh_TW/ability-trigger.ts index 1d9a3446785..1281a8720c1 100644 --- a/src/locales/zh_TW/ability-trigger.ts +++ b/src/locales/zh_TW/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!", "badDreams": "{{pokemonName}} 被折磨着!", - "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!" + "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const;