[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
This commit is contained in:
Adrian T 2024-06-06 23:49:50 +08:00 committed by GitHub
parent ba66f2c916
commit 8d6a0bb0a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 132 additions and 8 deletions

View File

@ -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()) {

View File

@ -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<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr },
pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise<void> {
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)

View File

@ -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);

View File

@ -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"
}

View File

@ -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),
]
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;