[Fix] Refactor Disguise to work like Ice Face (#2684)

* Fix typos in `src/data/egg.ts` comments

* Refactor Disguise using Ice Face as a base

i18n {
  Co-authored-by: Arxxer <javiptn7@gmail.com>
  Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com>
  Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com>
  Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
  Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>
}

Update tests

* Update jsdoc

* Make recoil damage a parameter

* Fix Ice Face i18n parameter name

* Add i18n placeholder strings for translation

* Update disguise test with override helper functions

* Move a form change from `battle-scene.ts` to `ability.ts`

* Remove a bit of obsolete code from the Disguise test

* Add some translations

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: returntoice <dieandbecome@gmail.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Sonny Ding <93831983+sonnyding1@users.noreply.github.com>

* Add translation placeholder strings for Japanese and Catalan

* Update for strict-null

* Remove unused parameter from the `BattlerTag` class

* Remove species checks, fusion doesn't seem to be an issue (any more?)

* Move Ice Face weather-based code to a subclass

* Condense conditionals

Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com>

---------

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: returntoice <dieandbecome@gmail.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Sonny Ding <93831983+sonnyding1@users.noreply.github.com>
Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com>
This commit is contained in:
NightKev 2024-08-12 03:52:39 -07:00 committed by GitHub
parent d3d376dca3
commit 223295e827
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 238 additions and 217 deletions

View File

@ -55,7 +55,6 @@ import {UiInputs} from "./ui-inputs";
import { NewArenaEvent } from "./events/battle-scene";
import { ArenaFlyout } from "./ui/arena-flyout";
import { EaseType } from "#enums/ease-type";
import { Abilities } from "#enums/abilities";
import { BattleSpec } from "#enums/battle-spec";
import { BattleStyle } from "#enums/battle-style";
import { Biome } from "#enums/biome";
@ -1153,12 +1152,6 @@ export default class BattleScene extends SceneBase {
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}

View File

@ -245,25 +245,6 @@ export class PreDefendAbAttr extends AbAttr {
}
}
export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
private formFunc: (p: Pokemon) => integer;
constructor(formFunc: ((p: Pokemon) => integer)) {
super(true);
this.formFunc = formFunc;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
return true;
}
return false;
}
}
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.isFullHp() &&
@ -330,21 +311,6 @@ export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultip
}
}
export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(condition: PokemonDefendCondition) {
super(condition, 1);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = Math.floor(pokemon.getMaxHp() / 8);
return true;
}
return false;
}
}
/**
* Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr
@ -539,45 +505,6 @@ export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
}
}
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt);
if (!recoilDamage) {
return false;
}
pokemon.damageAndUpdate(recoilDamage, HitResult.OTHER);
pokemon.turnData.damageTaken += recoilDamage;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postDefendDisguise", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
return false;
}
}
export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
private formFunc: (p: Pokemon) => integer;
constructor(formFunc: ((p: Pokemon) => integer)) {
super(true);
this.formFunc = formFunc;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
return true;
}
return false;
}
}
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.priority);
@ -4045,34 +3972,44 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt
}
/**
* Takes no damage from the first hit of a physical move.
* This is used in Ice Face ability.
* Takes no damage from the first hit of a damaging move.
* This is used in the Disguise and Ice Face abilities.
* @extends ReceivedMoveDamageMultiplierAbAttr
*/
export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
private multiplier: number;
private tagType: BattlerTagType;
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined;
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
constructor(condition: PokemonDefendCondition, multiplier: number) {
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
super(condition, multiplier);
this.multiplier = multiplier;
this.tagType = tagType;
this.recoilDamageFunc = recoilDamageFunc;
this.triggerMessageFunc = triggerMessageFunc;
}
/**
* Applies the Ice Face pre-defense ability to the Pokémon.
* Removes BattlerTagType.ICE_FACE when hit by physical attack and is in Ice Face form.
* Applies the pre-defense ability to the Pokémon.
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense 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.
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {boolean} passive n/a
* @param {Pokemon} attacker The attacking Pokémon.
* @param {PokemonMove} move The move being used.
* @param {Utils.BooleanHolder} cancelled n/a
* @param {any[]} args Additional arguments.
* @returns {boolean} Whether the immunity was applied.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(BattlerTagType.ICE_FACE);
pokemon.removeTag(this.tagType);
if (this.recoilDamageFunc) {
pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER);
}
return true;
}
@ -4080,14 +4017,14 @@ export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAt
}
/**
* 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.
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {string} abilityName The name of the ability.
* @param {...any} args n/a
* @returns {string} The trigger message.
*/
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName });
return this.triggerMessageFunc(pokemon, abilityName);
}
}
@ -5004,20 +4941,18 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMoveDamageToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PreDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostDefendDisguiseAbAttr)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.ignorable()
.partial(),
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE,
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
(pokemon) => Math.floor(pokemon.getMaxHp() / 8))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.ignorable(),
new Ability(Abilities.BATTLE_BOND, 7)
.attr(PostVictoryFormChangeAbAttr, () => 2)
.attr(PostBattleInitFormChangeAbAttr, () => 1)
@ -5166,7 +5101,9 @@ export function initAbilities() {
.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(IceFaceBlockPhysicalAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0)
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.ignorable(),
new Ability(Abilities.POWER_SPOT, 8)
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3),

View File

@ -1558,36 +1558,25 @@ export class GroundedTag 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);
/** Common attributes of form change abilities that block damage */
export class FormBlockDamageTag extends BattlerTag {
constructor(tagType: BattlerTagType) {
super(tagType, BattlerTagLapseType.CUSTOM, 1);
}
/**
* 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.
* Determines if the 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;
return pokemon.formIndex === 0;
}
/**
* 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.
* Applies the tag to the Pokémon.
* Triggers a form change if the Pokémon is not in its defense form.
* @param {Pokemon} pokemon The Pokémon to which the tag is added.
*/
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
@ -1598,9 +1587,9 @@ export class IceFaceTag extends BattlerTag {
}
/**
* 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.
* Removes the tag from the Pokémon.
* Triggers a form change when the tag is removed.
* @param {Pokemon} pokemon The Pokémon from which the tag is removed.
*/
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
@ -1609,6 +1598,24 @@ export class IceFaceTag extends BattlerTag {
}
}
/** Provides the additional weather-based effects of the Ice Face ability */
export class IceFaceBlockDamageTag extends FormBlockDamageTag {
constructor(tagType: BattlerTagType) {
super(tagType);
}
/**
* Determines if the 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;
return super.canAdd(pokemon) || isWeatherSnowOrHail;
}
}
/**
* Battler tag enabling the Stockpile mechanic. This tag handles:
@ -1890,7 +1897,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE:
return new IceFaceTag(sourceMove);
return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK:

View File

@ -372,6 +372,9 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) });
}
if (pokemon.getAbility().id === Abilities.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}

View File

@ -60,6 +60,7 @@ export enum BattlerTagType {
DESTINY_BOND = "DESTINY_BOND",
CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION",
ICE_FACE = "ICE_FACE",
DISGUISE = "DISGUISE",
STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}'s disguise was busted!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
"revertChange": "{{pokemonName}} reverted\nto its original form!",
"formChange": "{{preName}} changed form!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,7 +4,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage": "{{pokemonName}} wurde durch {{abilityName}} vor Rückstoß geschützt!",
"badDreams": "{{pokemonName}} ist in einem Alptraum gefangen!",
"costar": "{{pokemonName}} kopiert die Statusveränderungen von {{allyName}}!",
"iceFaceAvoidedDamage": "{{pokemonName}} wehrt Schaden mit {{abilityName}} ab!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} wehrt Schaden mit {{abilityName}} ab!",
"perishBody": "Durch {{abilityName}} von {{pokemonName}} werden beide Pokémon nach drei Runden K.O. gehen!",
"poisonHeal": "{{abilityName}} von {{pokemonName}} füllte einige KP auf!",
"trace": "{{pokemonName}} kopiert {{abilityName}} von {{targetName}}!",
@ -13,7 +13,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockItemTheft": "{{abilityName}} von {{pokemonNameWithAffix}} verhindert Item-Diebstahl!",
"typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!",
"postDefendDisguise": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!",
"disguiseAvoidedDamage": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!",
"moveImmunity": "Es hat keine Wirkung auf {{pokemonNameWithAffix}}...",
"reverseDrain": "{{pokemonNameWithAffix}} saugt Kloakensoße auf!",
"postDefendTypeChange": "{{abilityName}} von {{pokemonNameWithAffix}} macht es zu einem {{typeName}}-Typ!",

View File

@ -14,6 +14,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} hat sich zu {{pokemonName}} unendynamaximiert!",
"revertChange": "{{pokemonName}} hat seine ursprüngliche Form zurückerlangt!",
"formChange": "{{preName}} hat seine Form geändert!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,16 +4,16 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage": "{{pokemonName}}'s {{abilityName}}\nprotected it from recoil!",
"badDreams": "{{pokemonName}} is tormented!",
"costar": "{{pokemonName}} copied {{allyName}}'s stat changes!",
"iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} avoided\ndamage with {{abilityName}}!",
"perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!",
"poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!",
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}'s disguise was busted!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!",
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
"revertChange": "{{pokemonName}} reverted\nto its original form!",
"formChange": "{{preName}} changed form!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -7,13 +7,13 @@ export const abilityTriggers: SimpleTranslationEntries = {
"iceFaceAvoidedDamage": "¡{{pokemonNameWithAffix}} evitó\ndaño con {{abilityName}}!",
"perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!",
"poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!",
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"trace": "¡{{pokemonName}} ha copiado la habilidad {{abilityName}} \nde {{targetName}}!",
"windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"quickDraw": "¡{{pokemonName}} ataca primero gracias a la habilidad Mano Rápida!",
"disguiseAvoidedDamage" : "¡El disfraz de {{pokemonNameWithAffix}} se ha roto!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!",
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
"revertChange": "{{pokemonName}} reverted\nto its original form!",
"formChange": "{{preName}} changed form!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,7 +4,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage" : "{{abilityName}}\nde {{pokemonName}} le protège du contrecoup !",
"badDreams": "{{pokemonName}} a le sommeil agité !",
"costar": "{{pokemonName}} copie les changements de stats\nde {{allyName}} !",
"iceFaceAvoidedDamage": "{{pokemonName}} évite les dégâts\navec {{abilityName}} !",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} évite les dégâts\navec {{abilityName}} !",
"perishBody": "{{abilityName}} de {{pokemonName}}\nmettra les deux Pokémon K.O. dans trois tours !",
"poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaure un peu ses PV !",
"trace": "{{pokemonName}} copie le talent {{abilityName}}\nde {{targetName}} !",
@ -13,7 +13,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} évite\nles dégâts avec {{abilityName}} !",
"postDefendDisguise": "Le déguisement de {{pokemonNameWithAffix}}\ntombe !",
"disguiseAvoidedDamage": "Le déguisement de {{pokemonNameWithAffix}}\ntombe !",
"moveImmunity": "Ça n'affecte pas {{pokemonNameWithAffix}}…",
"reverseDrain": "{{pokemonNameWithAffix}} aspire\nle suintement !",
"postDefendTypeChange": "{{abilityName}} de {{pokemonNameWithAffix}}\nle transforme en type {{typeName}} !",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} devient\n{{pokemonName}} !",
"revertChange": "{{pokemonName}} retourne\nà sa forme initiale !",
"formChange": "{{preName}} change de forme !",
"disguiseChange": "Le déguisement absorbe lattaque !",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,7 +4,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage" : "{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!",
"badDreams": "{{pokemonName}} è tormentato dagli incubi!",
"costar": "{{pokemonName}} ha copiato le modifiche alle statistiche\ndel suo alleato {{allyName}}!",
"iceFaceAvoidedDamage": "{{pokemonName}} ha evitato\ni danni grazie a {{abilityName}}!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} ha evitato\ni danni grazie a {{abilityName}}!",
"perishBody": "{{abilityName}} di {{pokemonName}}\nmanderà KO entrambi i Pokémon dopo 3 turni!",
"poisonHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!",
"trace": "L'abilità {{abilityName}} di {{targetName}}\nviene copiata da {{pokemonName}} con Traccia!",
@ -13,7 +13,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockItemTheft": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo rende immune ai furti!",
"typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}} è stato smascherato!",
"disguiseAvoidedDamage": "{{pokemonNameWithAffix}} è stato smascherato!",
"moveImmunity": "Non ha effetto su {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} ha assorbito la melma!",
"postDefendTypeChange": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo ha reso di tipo {{typeName}}!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} si Dynamaxxa infinitamente\nin {{pokemonName}}!",
"revertChange": "{{pokemonName}} è tornato\nalla sua forma originaria!",
"formChange": "{{preName}} ha cambiato forma!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}}は 相手の {{targetName}}の\n{{abilityName}}を トレースした!",
"windPowerCharged": "{{pokemonName}}は\n{{moveName}}を 受けて じゅうでんした!",
"quickDraw": "{{pokemonName}}は クイックドロウで\n行動が はやくなった",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}'s disguise was busted!",
"blockItemTheft": "{{pokemonNameWithAffix}}の {{abilityName}}で\n道具を うばわれない",
"typeImmunityHeal": "{{pokemonNameWithAffix}}は {{abilityName}}で\n体力を 回復した",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}は {{abilityName}}で\nダメージを 受けない。",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
"revertChange": "{{pokemonName}} reverted\nto its original form!",
"formChange": "{{preName}} changed form!",
"disguiseChange": "Its disguise served it as a decoy!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,16 +4,16 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!",
"badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!",
"costar": "{{pokemonName}}[[는]] {{allyName}}의\n능력 변화를 복사했다!",
"iceFaceAvoidedDamage": "{{pokemonName}}[[는]] {{abilityName}} 때문에\n데미지를 받지 않는다!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}}[[는]] {{abilityName}} 때문에\n데미지를 받지 않는다!",
"perishBody": "{{pokemonName}}의 {{abilityName}} 때문에\n양쪽 포켓몬 모두는 3턴 후에 쓰러져 버린다!",
"poisonHeal": "{{pokemonName}}[[는]] {{abilityName}}[[로]]인해\n조금 회복했다.",
"trace": "{{pokemonName}}[[는]] 상대 {{targetName}}의 \n{{abilityName}}[[를]] 트레이스했다!",
"windPowerCharged": "{{pokemonName}}[[는]]\n{{moveName}}에 맞아 충전되었다!",
"quickDraw": "{{pokemonName}}[[는]]\n퀵드로에 의해 행동이 빨라졌다!",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}의 정체가 드러났다!",
"blockItemTheft": "{{pokemonNameWithAffix}}의 {{abilityName}}에 의해\n도구를 빼앗기지 않는다!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}[[는]]\n{{abilityName}}[[로]] 체력이 회복되었다!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}[[는]] {{abilityName}} 때문에\n데미지를 입지 않는다!",
"postDefendDisguise": "{{pokemonNameWithAffix}}의\n정체가 드러났다!",
"moveImmunity": "{{pokemonNameWithAffix}}에게는\n효과가 없는 것 같다...",
"reverseDrain": "{{pokemonNameWithAffix}}[[는]]\n해감액을 흡수했다!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}[[는]] {{abilityName}}[[로]] 인해\n{{typeName}}타입이 됐다!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}}[[는]]\n{{pokemonName}}가 되었다!",
"revertChange": "{{pokemonName}}[[는]]\n원래 모습으로 되돌아왔다!",
"formChange": "{{preName}}[[는]]\n다른 모습으로 변화했다!",
"disguiseChange": "탈이 대타가 되었다!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,16 +4,16 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage": "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano reverso!",
"badDreams": "{{pokemonName}} está tendo pesadelos!",
"costar": "{{pokemonName}} copiou as mudanças\nde atributo de {{allyName}}!",
"iceFaceAvoidedDamage": "{{pokemonName}} evitou\ndanos com sua {{abilityName}}!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} evitou\ndanos com sua {{abilityName}}!",
"perishBody": "{{abilityName}} de {{pokemonName}}\nirá desmaiar ambos os Pokémon em 3 turnos!",
"poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaurou seus PS um pouco!",
"trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!",
"windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!",
"quickDraw": "{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!",
"disguiseAvoidedDamage" : "O disfarce de {{pokemonNameWithAffix}} foi descoberto!",
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nprevine o roubo de itens!",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaurou um pouco de PS!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evitou dano\ncom {{abilityName}}!",
"postDefendDisguise": "O disfarce de {{pokemonNameWithAffix}} foi descoberto!",
"moveImmunity": "Isso não afeta {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} absorveu a gosma líquida!",
"postDefendTypeChange": "{{abilityName}} de {{pokemonNameWithAffix}}\ntransformou-o no tipo {{typeName}}!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}} Eternamaxou\npara {{pokemonName}}!",
"revertChange": "{{pokemonName}} voltou\npara sua forma original!",
"formChange": "{{preName}} mudou de forma!",
"disguiseChange": "O seu disfarce serviu-lhe de isca!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,16 +4,16 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage" : "{{pokemonName}}的{{abilityName}}\n抵消了反作用力",
"badDreams": "{{pokemonName}}被折磨着!",
"costar": "{{pokemonName}}复制了{{allyName}}的能力变化!",
"iceFaceAvoidedDamage": "{{pokemonName}}因为{{abilityName}}\n避免了伤害",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}}因为{{abilityName}}\n避免了伤害",
"perishBody": "因为{{pokemonName}}的{{abilityName}}\n双方将在3回合后灭亡",
"poisonHeal": "{{pokemonName}}因{{abilityName}}\n回复了少许HP",
"trace": "{{pokemonName}}复制了{{targetName}}的\n{{abilityName}}",
"windPowerCharged": "受{{moveName}}的影响,{{pokemonName}}提升了能力!",
"quickDraw":"因为速击效果发动,\n{{pokemonName}}比平常出招更快了!",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}的画皮脱落了!",
"blockItemTheft": "{{pokemonNameWithAffix}}的{{abilityName}}\n阻止了对方夺取道具",
"typeImmunityHeal": "{{pokemonNameWithAffix}}因{{abilityName}}\n回复了少许HP",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}因{{abilityName}}\n避免了伤害",
"postDefendDisguise": "{{pokemonNameWithAffix}}的\n画皮脱落了",
"moveImmunity": "对{{pokemonNameWithAffix}}没有效果!",
"reverseDrain": "{{pokemonNameWithAffix}}\n吸到了污泥浆",
"postDefendTypeChange": "{{pokemonNameWithAffix}}因{{abilityName}}\n变成了{{typeName}}属性!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}}无极巨化成了\n{{pokemonName}}",
"revertChange": "{{pokemonName}}变回了\n原本的样子",
"formChange": "{{preName}}变成其他样子了。",
"disguiseChange": "它的画皮被当作诱饵使用了!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -4,16 +4,16 @@ export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力",
"badDreams": "{{pokemonName}} 被折磨着!",
"costar": "{{pokemonName}} 複製了 {{allyName}} 的\n能力變化",
"iceFaceAvoidedDamage": "{{pokemonName}} 因爲 {{abilityName}}\n避免了傷害",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} 因爲 {{abilityName}}\n避免了傷害",
"perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!",
"poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!",
"trace": "{{pokemonName}} 複製了 {{targetName}} 的\n{{abilityName}}!",
"windPowerCharged": "受 {{moveName}} 的影響, {{pokemonName}} 提升了能力!",
"quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"disguiseAvoidedDamage" : "{{pokemonNameWithAffix}}的畫皮脫落了!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!",
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",

View File

@ -13,6 +13,7 @@ export const battlePokemonForm: SimpleTranslationEntries = {
"eternamaxChange": "{{preName}}無極巨化成了\n{{pokemonName}}",
"revertChange": "{{pokemonName}}變回了\n原本的樣子",
"formChange": "{{preName}}變為其他樣子了。",
"disguiseChange": "它的畫皮被當作誘餌使用了!",
} as const;
export const pokemonForm: SimpleTranslationEntries = {

View File

@ -1,18 +1,21 @@
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
import { TurnEndPhase } from "#app/phases.js";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import { Moves } from "#enums/moves";
import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species";
import { StatusEffect } from "#app/data/status-effect.js";
import { MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { SPLASH_ONLY } from "../utils/testUtils";
const TIMEOUT = 20 * 1000;
describe("Abilities - DISGUISE", () => {
describe("Abilities - Disguise", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const bustedForm = 1;
const disguisedForm = 0;
beforeAll(() => {
phaserGame = new Phaser.Game({
@ -26,72 +29,134 @@ describe("Abilities - DISGUISE", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
game.override.battleType("single");
game.override.ability(Abilities.DISGUISE);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switched to base form on arena reset",
async () => {
const baseForm = 0,
bustedForm = 1;
game.override.startingWave(4);
game.override.starterForms({
[Species.MIMIKYU]: bustedForm,
});
game.override.enemySpecies(Species.MIMIKYU);
game.override.enemyMoveset(SPLASH_ONLY);
await game.startBattle([Species.MAGIKARP, Species.MIMIKYU]);
game.override.starterSpecies(Species.REGIELEKI);
game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
}, TIMEOUT);
const mimikyu = game.scene.getParty().find((p) => p.species.speciesId === Species.MIMIKYU);
expect(mimikyu).not.toBe(undefined);
expect(mimikyu!.formIndex).toBe(bustedForm);
it("takes no damage from attacking move and transforms to Busted form, taking 1/8 max HP damage from the disguise breaking", async () => {
await game.startBattle();
mimikyu!.hp = 0;
mimikyu!.status = new Status(StatusEffect.FAINT);
expect(mimikyu!.isFainted()).toBe(true);
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(mimikyu.formIndex).toBe(disguisedForm);
expect(mimikyu!.formIndex).toBe(baseForm);
},
TIMEOUT
);
game.doAttack(getMovePosition(game.scene, 0, Moves.SHADOW_SNEAK));
test(
"damage taken should be equal to 1/8 of its maximum HP, rounded down",
async () => {
const baseForm = 0,
bustedForm = 1;
await game.phaseInterceptor.to(MoveEndPhase);
game.override.enemyMoveset([Moves.DARK_PULSE, Moves.DARK_PULSE, Moves.DARK_PULSE, Moves.DARK_PULSE]);
game.override.startingLevel(20);
game.override.enemyLevel(20);
game.override.enemySpecies(Species.MAGIKARP);
game.override.starterForms({
[Species.MIMIKYU]: baseForm,
});
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
await game.startBattle([Species.MIMIKYU]);
it("doesn't break disguise when attacked with ineffective move", async () => {
await game.startBattle();
const mimikyu = game.scene.getPlayerPokemon()!;
const damage = (Math.floor(mimikyu!.getMaxHp()/8));
const mimikyu = game.scene.getEnemyPokemon()!;
expect(mimikyu).not.toBe(undefined);
expect(mimikyu!.formIndex).toBe(baseForm);
expect(mimikyu.formIndex).toBe(disguisedForm);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.VACUUM_WAVE));
expect(mimikyu!.formIndex).toBe(bustedForm);
expect(game.scene.getEnemyPokemon()!.turnData.currDamageDealt).toBe(damage);
},
TIMEOUT
);
await game.phaseInterceptor.to(MoveEndPhase);
expect(mimikyu.formIndex).toBe(disguisedForm);
}, TIMEOUT);
it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => {
game.override.moveset([Moves.SURGING_STRIKES]);
game.override.enemyLevel(5);
await game.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
expect(mimikyu.formIndex).toBe(disguisedForm);
game.doAttack(getMovePosition(game.scene, 0, Moves.SURGING_STRIKES));
// First hit
await game.phaseInterceptor.to(MoveEffectPhase);
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(disguisedForm);
// Second hit
await game.phaseInterceptor.to(MoveEffectPhase);
expect(mimikyu.hp).lessThan(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
it("takes effects from status moves and damage from status effects", async () => {
await game.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
expect(mimikyu.hp).toBe(mimikyu.getMaxHp());
game.doAttack(getMovePosition(game.scene, 0, Moves.TOXIC_THREAD));
await game.phaseInterceptor.to(TurnEndPhase);
expect(mimikyu.formIndex).toBe(disguisedForm);
expect(mimikyu.status?.effect).toBe(StatusEffect.POISON);
expect(mimikyu.summonData.battleStats[BattleStat.SPD]).toBe(-1);
expect(mimikyu.hp).toBeLessThan(mimikyu.getMaxHp());
}, TIMEOUT);
it("persists form change when switched out", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
game.override.starterSpecies(0);
await game.startBattle([Species.MIMIKYU, Species.FURRET]);
const mimikyu = game.scene.getPlayerPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(mimikyu.formIndex).toBe(bustedForm);
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase);
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
it("reverts to Disguised on arena reset", async () => {
game.override.startingWave(4);
game.override.starterSpecies(Species.MIMIKYU);
game.override.starterForms({
[Species.MIMIKYU]: bustedForm
});
game.override.enemySpecies(Species.MAGIKARP);
game.override.enemyAbility(Abilities.BALL_FETCH);
await game.startBattle();
const mimikyu = game.scene.getPlayerPokemon()!;
expect(mimikyu.formIndex).toBe(bustedForm);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(TurnInitPhase);
expect(mimikyu.formIndex).toBe(disguisedForm);
}, TIMEOUT);
});