diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e6649d0999a..4616187e098 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2582,90 +2582,83 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise { + addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): boolean { if (!modifier) { - return Promise.resolve(false); + return false; } - return new Promise(resolve => { - let success = false; - const soundName = modifier.type.soundName; - this.validateAchvs(ModifierAchv, modifier); - const modifiersToRemove: PersistentModifier[] = []; - const modifierPromises: Promise[] = []; - if (modifier instanceof PersistentModifier) { - if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - success = modifier.apply(pokemon, true); - } + let success = false; + const soundName = modifier.type.soundName; + this.validateAchvs(ModifierAchv, modifier); + const modifiersToRemove: PersistentModifier[] = []; + if (modifier instanceof PersistentModifier) { + if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { + if (modifier instanceof PokemonFormChangeItemModifier) { + const pokemon = this.getPokemonById(modifier.pokemonId); + if (pokemon) { + success = modifier.apply(pokemon, true); } - if (playSound && !this.sound.get(soundName)) { - this.playSound(soundName); - } - } else if (!virtual) { - const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); - this.queueMessage(i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }), undefined, false, 3000); - return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant).then(success => resolve(success)); } - - for (const rm of modifiersToRemove) { - this.removeModifier(rm); - } - - if (!ignoreUpdate && !virtual) { - return this.updateModifiers(true, instant).then(() => resolve(success)); - } - } else if (modifier instanceof ConsumableModifier) { if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } - - if (modifier instanceof ConsumablePokemonModifier) { - for (const p in this.party) { - const pokemon = this.party[p]; - - const args: unknown[] = []; - if (modifier instanceof PokemonHpRestoreModifier) { - if (!(modifier as PokemonHpRestoreModifier).fainted) { - const hpRestoreMultiplier = new Utils.NumberHolder(1); - this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); - args.push(hpRestoreMultiplier.value); - } else { - args.push(1); - } - } else if (modifier instanceof FusePokemonModifier) { - args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); - } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { - args.push(cost); - } - - if (modifier.shouldApply(pokemon, ...args)) { - const result = modifier.apply(pokemon, ...args); - if (result instanceof Promise) { - modifierPromises.push(result.then(s => success ||= s)); - } else { - success ||= result; - } - } - } - - return Promise.allSettled([ this.party.map(p => p.updateInfo(instant)), ...modifierPromises ]).then(() => resolve(success)); - } else { - const args = [ this ]; - if (modifier.shouldApply(...args)) { - const result = modifier.apply(...args); - if (result instanceof Promise) { - return result.then(success => resolve(success)); - } else { - success ||= result; - } - } - } + } else if (!virtual) { + const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); + this.queueMessage( + i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }), + undefined, + false, + 3000 + ); + return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant); } - resolve(success); - }); + for (const rm of modifiersToRemove) { + this.removeModifier(rm); + } + + if (!ignoreUpdate && !virtual) { + this.updateModifiers(true, instant); + } + } else if (modifier instanceof ConsumableModifier) { + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + + if (modifier instanceof ConsumablePokemonModifier) { + for (const p in this.party) { + const pokemon = this.party[p]; + + const args: unknown[] = []; + if (modifier instanceof PokemonHpRestoreModifier) { + if (!(modifier as PokemonHpRestoreModifier).fainted) { + const hpRestoreMultiplier = new Utils.NumberHolder(1); + this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); + args.push(hpRestoreMultiplier.value); + } else { + args.push(1); + } + } else if (modifier instanceof FusePokemonModifier) { + args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); + } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { + args.push(cost); + } + + if (modifier.shouldApply(pokemon, ...args)) { + const result = modifier.apply(pokemon, ...args); + success ||= result; + } + } + + this.party.map((p) => p.updateInfo(instant)); + } else { + const args = [ this ]; + if (modifier.shouldApply(...args)) { + const result = modifier.apply(...args); + success ||= result; + } + } + } + return true; } addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { @@ -2683,7 +2676,8 @@ export default class BattleScene extends SceneBase { } } if (!ignoreUpdate) { - this.updateModifiers(false, instant).then(() => resolve()); + this.updateModifiers(false, instant); + resolve(); } else { resolve(); } @@ -2704,66 +2698,82 @@ export default class BattleScene extends SceneBase { * @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`. * @returns `true` if the transfer was successful */ - tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise { - return new Promise(resolve => { - const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; - const cancelled = new Utils.BooleanHolder(false); - Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => { - if (cancelled.value) { - return resolve(false); - } - const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; - newItemModifier.pokemonId = target.id; - const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; - let removeOld = true; - if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(); - if (matchingModifier.stackCount >= maxStackCount) { - return resolve(false); - } - const countTaken = Math.min(transferQuantity, itemModifier.stackCount, maxStackCount - matchingModifier.stackCount); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = matchingModifier.stackCount + countTaken; - removeOld = !itemModifier.stackCount; - } else { - const countTaken = Math.min(transferQuantity, itemModifier.stackCount); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = countTaken; - } - removeOld = !itemModifier.stackCount; - if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { - const addModifier = () => { - if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { - if (target.isPlayer()) { - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => { - if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); - } - resolve(true); - }); - } else { - this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => { - if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); - } - resolve(true); - }); - } - } else { - resolve(false); + tryTransferHeldItemModifier( + itemModifier: PokemonHeldItemModifier, + target: Pokemon, + playSound: boolean, + transferQuantity: number = 1, + instant?: boolean, + ignoreUpdate?: boolean, + itemLost: boolean = true, + ): boolean { + const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; + const cancelled = new Utils.BooleanHolder(false); + + if (source && source.isPlayer() !== target.isPlayer()) { + applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); + } + + if (cancelled.value) { + return false; + } + + const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; + newItemModifier.pokemonId = target.id; + const matchingModifier = this.findModifier( + (m) => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier; + + if (matchingModifier) { + const maxStackCount = matchingModifier.getMaxStackCount(); + if (matchingModifier.stackCount >= maxStackCount) { + return false; + } + const countTaken = Math.min( + transferQuantity, + itemModifier.stackCount, + maxStackCount - matchingModifier.stackCount, + ); + itemModifier.stackCount -= countTaken; + newItemModifier.stackCount = matchingModifier.stackCount + countTaken; + } else { + const countTaken = Math.min(transferQuantity, itemModifier.stackCount); + itemModifier.stackCount -= countTaken; + newItemModifier.stackCount = countTaken; + } + + const removeOld = itemModifier.stackCount === 0; + + if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { + const addModifier = () => { + if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { + if (target.isPlayer()) { + this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); + if (source && itemLost) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); } - }; - if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { - this.updateModifiers(source.isPlayer(), instant).then(() => addModifier()); + return true; } else { - addModifier(); + this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => { + if (source && itemLost) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); + } + return true; + }); } - return; } - resolve(false); - }); - }); + return false; + }; + if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { + this.updateModifiers(source.isPlayer(), instant); + addModifier(); + } else { + addModifier(); + } + return true; + } + return false; } removePartyMemberModifiers(partyMemberIndex: number): Promise { @@ -2773,7 +2783,8 @@ export default class BattleScene extends SceneBase { for (const m of modifiersToRemove) { this.modifiers.splice(this.modifiers.indexOf(m), 1); } - this.updateModifiers().then(() => resolve()); + this.updateModifiers(); + resolve(); }); } @@ -2841,7 +2852,8 @@ export default class BattleScene extends SceneBase { } return true; }); - this.updateModifiers(false).then(() => resolve()); + this.updateModifiers(false); + resolve(); }); } @@ -2853,7 +2865,8 @@ export default class BattleScene extends SceneBase { for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } - this.updateModifiers(false).then(() => this.updateUIPositions()); + this.updateModifiers(false); + this.updateUIPositions(); } /** @@ -2865,46 +2878,43 @@ export default class BattleScene extends SceneBase { for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } - this.updateModifiers(false).then(() => this.updateUIPositions()); + this.updateModifiers(false); + this.updateUIPositions(); } setModifiersVisible(visible: boolean) { [ this.modifierBar, this.enemyModifierBar ].map(m => m.setVisible(visible)); } - updateModifiers(player?: boolean, instant?: boolean): Promise { - if (player === undefined) { - player = true; + updateModifiers(player: boolean = true, instant?: boolean): void { + const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); + for (let m = 0; m < modifiers.length; m++) { + const modifier = modifiers[m]; + if ( + modifier instanceof PokemonHeldItemModifier && + !this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId) + ) { + modifiers.splice(m--, 1); + } } - return new Promise(resolve => { - const modifiers = player ? this.modifiers : this.enemyModifiers as PersistentModifier[]; - for (let m = 0; m < modifiers.length; m++) { - const modifier = modifiers[m]; - if (modifier instanceof PokemonHeldItemModifier && !this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId)) { - modifiers.splice(m--, 1); - } - } - for (const modifier of modifiers) { - if (modifier instanceof PersistentModifier) { - (modifier as PersistentModifier).virtualStackCount = 0; - } + for (const modifier of modifiers) { + if (modifier instanceof PersistentModifier) { + (modifier as PersistentModifier).virtualStackCount = 0; } + } - const modifiersClone = modifiers.slice(0); - for (const modifier of modifiersClone) { - if (!modifier.getStackCount()) { - modifiers.splice(modifiers.indexOf(modifier), 1); - } + const modifiersClone = modifiers.slice(0); + for (const modifier of modifiersClone) { + if (!modifier.getStackCount()) { + modifiers.splice(modifiers.indexOf(modifier), 1); } + } - this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant).then(() => { - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); - if (!player) { - this.updateUIPositions(); - } - resolve(); - }); - }); + this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant); + (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); + if (!player) { + this.updateUIPositions(); + } } updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { diff --git a/src/data/ability.ts b/src/data/ability.ts index 95601dc2010..0eaa0acbefb 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,6 +1,6 @@ -import type { EnemyPokemon } from "../field/pokemon"; +import type { EnemyPokemon, PokemonMove } from "../field/pokemon"; import type Pokemon from "../field/pokemon"; -import { HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; +import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon"; import { Type } from "#enums/type"; import type { Constructor } from "#app/utils"; import * as Utils from "../utils"; @@ -44,6 +44,7 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; +import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; export class Ability implements Localizable { public id: Abilities; @@ -143,7 +144,7 @@ export class Ability implements Localizable { } } -type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => boolean | Promise; +type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => boolean; type AbAttrCondition = (pokemon: Pokemon) => boolean; // TODO: Can this be improved? @@ -159,7 +160,7 @@ export abstract class AbAttr { this.showAbility = showAbility; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { return false; } @@ -215,7 +216,7 @@ export class DoubleBattleChanceAbAttr extends AbAttr { } export class PostBattleInitAbAttr extends AbAttr { - applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } @@ -250,7 +251,7 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { this.stages = stages; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { @@ -268,7 +269,15 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; export class PreDefendAbAttr extends AbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move | null, + cancelled: Utils.BooleanHolder | null, + args: any[], + ): boolean { return false; } } @@ -546,7 +555,15 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { * @param args `[0]` a container for the move's current type effectiveness multiplier * @returns `true` if the move's effectiveness is reduced; `false` otherwise */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move | null, + cancelled: Utils.BooleanHolder | null, + args: any[], + ): boolean { const typeMultiplier = args[0]; if (!(typeMultiplier && typeMultiplier instanceof Utils.NumberHolder)) { return false; @@ -572,7 +589,15 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { } export class PostDefendAbAttr extends AbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { return false; } } @@ -593,7 +618,14 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { } export class PostStatStageChangeAbAttr extends AbAttr { - applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): boolean | Promise { + applyPostStatStageChange( + pokemon: Pokemon, + simulated: boolean, + statsChanged: BattleStat[], + stagesChanged: integer, + selfTarget: boolean, + args: any[], + ): boolean { return false; } } @@ -1142,7 +1174,14 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang } export class PreAttackAbAttr extends AbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise { + applyPreAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon | null, + move: Move, + args: any[], + ): boolean { return false; } } @@ -1208,7 +1247,13 @@ export class VariableMovePowerAbAttr extends PreAttackAbAttr { } export class FieldPreventExplosiveMovesAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + override apply( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { cancelled.value = true; return true; } @@ -1562,8 +1607,15 @@ export class StatMultiplierAbAttr extends AbAttr { this.condition = condition ?? null; } - applyStatStage(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise { - const move = (args[0] as Move); + applyStatStage( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + stat: BattleStat, + statValue: Utils.NumberHolder, + args: any[], + ): boolean { + const move = args[0] as Move; if (stat === this.stat && (!this.condition || this.condition(pokemon, null, move))) { statValue.value *= this.multiplier; return true; @@ -1588,7 +1640,15 @@ export class PostAttackAbAttr extends AbAttr { * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} * for an example of an effect that does not require a damaging move. */ - applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. // If attackRequired is false, we always defer to the secondary requirements. if (this.attackCondition(pokemon, defender, move)) { @@ -1601,7 +1661,15 @@ export class PostAttackAbAttr extends AbAttr { /** * This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question. */ - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostAttackAfterMoveTypeCheck( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { return false; } } @@ -1626,7 +1694,15 @@ export class GorillaTacticsAbAttr extends PostAttackAbAttr { * @param args n/a * @returns `true` if the ability is applied */ - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + override applyPostAttackAfterMoveTypeCheck( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { if (simulated) { return simulated; } @@ -1649,23 +1725,36 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.stealCondition = stealCondition ?? null; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { - return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); - if (heldItems.length) { - const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { - if (success) { - globalScene.queueMessage(i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, stolenItemType: stolenItem.type.name })); - } - resolve(success); - }); - return; + override applyPostAttackAfterMoveTypeCheck( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult, + args: any[], + ): boolean { + if ( + !simulated && + hitResult < HitResult.NO_EFFECT && + (!this.stealCondition || this.stealCondition(pokemon, defender, move)) + ) { + const heldItems = this.getTargetHeldItems(defender).filter((i) => i.isTransferable); + if (heldItems.length) { + const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; + if (globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false)) { + globalScene.queueMessage( + i18next.t("abilityTriggers:postAttackStealHeldItem", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + defenderName: defender.name, + stolenItemType: stolenItem.type.name, + }), + ); + return true; } } - resolve(simulated); - }); + } + return false; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { @@ -1742,23 +1831,37 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.condition = condition; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise { - return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { - const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); - if (heldItems.length) { - const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { - if (success) { - globalScene.queueMessage(i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, stolenItemType: stolenItem.type.name })); - } - resolve(success); - }); - return; + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult, + _args: any[], + ): boolean { + if ( + !simulated && + hitResult < HitResult.NO_EFFECT && + (!this.condition || this.condition(pokemon, attacker, move)) && + !move.hitsSubstitute(attacker, pokemon) + ) { + const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); + if (heldItems.length) { + const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; + if (globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false)) { + globalScene.queueMessage( + i18next.t("abilityTriggers:postDefendStealHeldItem", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + attackerName: attacker.name, + stolenItemType: stolenItem.type.name, + }), + ); + return true; } } - resolve(simulated); - }); + } + return false; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { @@ -1781,7 +1884,14 @@ export class PostSetStatusAbAttr extends AbAttr { * @param args Set of unique arguments needed by this attribute. * @returns `true` if application of the ability succeeds. */ - applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]) : boolean | Promise { + applyPostSetStatus( + pokemon: Pokemon, + sourcePokemon: Pokemon | null = null, + passive: boolean, + effect: StatusEffect, + simulated: boolean, + args: any[], + ): boolean { return false; } } @@ -1823,7 +1933,7 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { } export class PostVictoryAbAttr extends AbAttr { - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } @@ -1839,10 +1949,8 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { this.stages = stages; } - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - const stat = typeof this.stat === "function" - ? this.stat(pokemon) - : this.stat; + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } @@ -1859,7 +1967,7 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { this.formFunc = formFunc; } - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { @@ -1873,7 +1981,7 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { } export class PostKnockOutAbAttr extends AbAttr { - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { return false; } } @@ -1889,10 +1997,8 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { this.stages = stages; } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { - const stat = typeof this.stat === "function" - ? this.stat(pokemon) - : this.stat; + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { + const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } @@ -1905,7 +2011,7 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { super(); } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) { if (!simulated) { globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); @@ -2015,7 +2121,7 @@ export class PostSummonAbAttr extends AbAttr { * @param args Set of unique arguments needed by this attribute * @returns true if application of the ability succeeds */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } @@ -2035,7 +2141,7 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { this.arenaTags = arenaTags; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated) { for (const arenaTag of this.arenaTags) { globalScene.arena.removeTag(arenaTag); @@ -2385,7 +2491,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt * @param args - n/a * @returns A boolean or a promise that resolves to a boolean indicating the result of the ability application. */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const party = pokemon instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); @@ -2445,12 +2551,11 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { super(true, false); } - async applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): Promise { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { const targets = pokemon.getOpponents(); if (simulated || !targets.length) { return simulated; } - const promises: Promise[] = []; let target: Pokemon; if (targets.length > 1) { @@ -2476,41 +2581,14 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return false; } - pokemon.summonData.speciesForm = target.getSpeciesForm(); - pokemon.summonData.gender = target.getGender(); + globalScene.unshiftPhase(new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true)); - // Copy all stats (except HP) - for (const s of EFFECTIVE_STATS) { - pokemon.setStat(s, target.getStat(s, false), false); - } - - // Copy all stat stages - for (const s of BATTLE_STATS) { - pokemon.setStatStage(s, target.getStatStage(s)); - } - - pokemon.summonData.moveset = target.getMoveset().map((m) => { - if (m) { - // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. - return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); - } else { - console.warn(`Imposter: somehow iterating over a ${m} value when copying moveset!`); - return new PokemonMove(Moves.NONE); - } - }); - pokemon.summonData.types = target.getTypes(); - promises.push(pokemon.updateInfo()); - - globalScene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); - globalScene.playSound("battle_anims/PRSFX- Transform"); - promises.push(pokemon.loadAssets(false).then(() => { - pokemon.playAnim(); - pokemon.updateInfo(); - // If the new ability activates immediately, it needs to happen after all the transform animations - pokemon.setTempAbility(target.getAbility()); - })); - - await Promise.all(promises); + globalScene.queueMessage( + i18next.t("abilityTriggers:postSummonTransform", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + targetName: target.name, + }), + ); return true; } @@ -2627,13 +2705,13 @@ export class PreSwitchOutAbAttr extends AbAttr { super(true); } - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (pokemon.status) { if (!simulated) { pokemon.resetStatus(); @@ -2647,9 +2725,72 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } } +/** + * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. + */ +export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { + /** + * @param pokemon The {@linkcode Pokemon} with the ability + * @param passive N/A + * @param args N/A + * @returns {boolean} Returns true if the weather clears, otherwise false. + */ + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const weatherType = globalScene.arena.weather?.weatherType; + let turnOffWeather = false; + + // Clear weather only if user's ability matches the weather and no other pokemon has the ability. + switch (weatherType) { + case WeatherType.HARSH_SUN: + if ( + pokemon.hasAbility(Abilities.DESOLATE_LAND) && + globalScene + .getField(true) + .filter((p) => p !== pokemon) + .filter((p) => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0 + ) { + turnOffWeather = true; + } + break; + case WeatherType.HEAVY_RAIN: + if ( + pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && + globalScene + .getField(true) + .filter((p) => p !== pokemon) + .filter((p) => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0 + ) { + turnOffWeather = true; + } + break; + case WeatherType.STRONG_WINDS: + if ( + pokemon.hasAbility(Abilities.DELTA_STREAM) && + globalScene + .getField(true) + .filter((p) => p !== pokemon) + .filter((p) => p.hasAbility(Abilities.DELTA_STREAM)).length === 0 + ) { + turnOffWeather = true; + } + break; + } + + if (simulated) { + return turnOffWeather; + } + + if (turnOffWeather) { + globalScene.arena.trySetWeather(WeatherType.NONE, false); + return true; + } + + return false; + } +} export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!pokemon.isFullHp()) { if (!simulated) { const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); @@ -2685,7 +2826,7 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { * @param args N/A * @returns true if the form change was successful */ - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { @@ -2755,7 +2896,14 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { } export class PreStatStageChangeAbAttr extends AbAttr { - applyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreStatStageChange( + pokemon: Pokemon | null, + passive: boolean, + simulated: boolean, + stat: BattleStat, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { return false; } } @@ -2878,7 +3026,14 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { } export class PreSetStatusAbAttr extends AbAttr { - applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreSetStatus( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + effect: StatusEffect | undefined, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { return false; } } @@ -2944,7 +3099,14 @@ export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr { } export class PreApplyBattlerTagAbAttr extends AbAttr { - applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreApplyBattlerTag( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + tag: BattlerTag, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { return false; } } @@ -3139,7 +3301,14 @@ export class ChangeMovePriorityAbAttr extends AbAttr { export class IgnoreContactAbAttr extends AbAttr { } export class PreWeatherEffectAbAttr extends AbAttr { - applyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreWeatherEffect( + pokemon: Pokemon, + passive: Boolean, + simulated: boolean, + weather: Weather | null, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { return false; } } @@ -3420,7 +3589,13 @@ export class PostWeatherLapseAbAttr extends AbAttr { this.weatherTypes = weatherTypes; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather | null, args: any[]): boolean | Promise { + applyPostWeatherLapse( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + weather: Weather | null, + args: any[], + ): boolean { return false; } @@ -3516,7 +3691,7 @@ function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { } export class PostTurnAbAttr extends AbAttr { - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } @@ -3542,7 +3717,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { * @param {any[]} args N/A * @returns Returns true if healed from status, false if not */ - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (pokemon.status && this.effects.includes(pokemon.status.effect)) { if (!pokemon.isFullHp()) { if (!simulated) { @@ -3776,7 +3951,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { * @param args N/A * @returns `true` if any opponents are sleeping */ - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { let hadEffect: boolean = false; for (const opp of pokemon.getOpponents()) { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { @@ -3870,7 +4045,14 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { * @extends AbAttr */ export class PostMoveUsedAbAttr extends AbAttr { - applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean, args: any[]): boolean | Promise { + applyPostMoveUsed( + pokemon: Pokemon, + move: PokemonMove, + source: Pokemon, + targets: BattlerIndex[], + simulated: boolean, + args: any[], + ): boolean { return false; } } @@ -3891,7 +4073,14 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * * @return true if the Dancer ability was resolved */ - applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean, args: any[]): boolean | Promise { + override applyPostMoveUsed( + dancer: Pokemon, + move: PokemonMove, + source: Pokemon, + targets: BattlerIndex[], + simulated: boolean, + args: any[], + ): boolean { // List of tags that prevent the Dancer from replicating the move const forbiddenTags = [ BattlerTagType.FLYING, BattlerTagType.UNDERWATER, BattlerTagType.UNDERGROUND, BattlerTagType.HIDDEN ]; @@ -3933,7 +4122,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @extends AbAttr */ export class PostItemLostAbAttr extends AbAttr { - applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean | Promise { + applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { return false; } } @@ -3954,7 +4143,7 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { * @param args N/A * @returns true if BattlerTag was applied */ - applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean | Promise { + override applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType); return true; @@ -3980,7 +4169,13 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { } export class StatStageChangeCopyAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + override apply( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + cancelled: Utils.BooleanHolder, + args: any[], + ): boolean { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); } @@ -4096,7 +4291,14 @@ export class CheckTrappedAbAttr extends AbAttr { this.arenaTrapCondition = condition; } - applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean | Promise { + applyCheckTrapped( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + trapped: Utils.BooleanHolder, + otherPokemon: Pokemon, + args: any[], + ): boolean { return false; } } @@ -4841,7 +5043,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { * Checks if the Pokemon should change types if summoned into an active terrain * @returns `true` if there is an active terrain requiring a type change | `false` if not */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (globalScene.arena.getTerrainType() !== TerrainType.NONE) { return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []); } @@ -4860,27 +5062,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { } } -async function applyAbAttrsInternal( - attrType: Constructor, - pokemon: Pokemon | null, - applyFunc: AbAttrApplyFunc, - args: any[], - showAbilityInstant: boolean = false, - simulated: boolean = false, - messages: string[] = [], - gainedMidTurn: boolean = false -) { - for (const passive of [ false, true ]) { - if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { - continue; - } - - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); - globalScene.clearPhaseQueueSplice(); - } -} - -async function applySingleAbAttrs( +function applySingleAbAttrs( pokemon: Pokemon, passive: boolean, attrType: Constructor, @@ -4903,13 +5085,7 @@ async function applySingleAbAttrs( } globalScene.setPhaseQueueSplice(); - - let result = applyFunc(attr, passive); - // TODO Remove this when promises get reworked - if (result instanceof Promise) { - result = await result; - } - if (result) { + if (applyFunc(attr, passive)) { if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { pokemon.summonData.abilitiesApplied.push(ability.id); } @@ -5074,7 +5250,14 @@ function calculateShellBellRecovery(pokemon: Pokemon): number { * @extends AbAttr */ export class PostDamageAbAttr extends AbAttr { - public applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], source?: Pokemon): boolean | Promise { + public applyPostDamage( + pokemon: Pokemon, + damage: number, + passive: boolean, + simulated: boolean, + args: any[], + source?: Pokemon, + ): boolean { return false; } } @@ -5111,7 +5294,14 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { * @param source The Pokemon that dealt damage * @returns `true` if the switch-out logic was successfully applied */ - public override applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], source?: Pokemon): boolean | Promise { + public override applyPostDamage( + pokemon: Pokemon, + damage: number, + passive: boolean, + simulated: boolean, + args: any[], + source?: Pokemon, + ): boolean { const moveHistory = pokemon.getMoveHistory(); // Will not activate when the Pokémon's HP is lowered by cutting its own HP const fordbiddenAttackingMoves = [ Moves.BELLY_DRUM, Moves.SUBSTITUTE, Moves.CURSE, Moves.PAIN_SPLIT ]; @@ -5165,44 +5355,164 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { return this.helper.getFailedText(target); } } +async function applyAbAttrsInternal( + attrType: Constructor, + pokemon: Pokemon | null, + applyFunc: AbAttrApplyFunc, + args: any[], + showAbilityInstant: boolean = false, + simulated: boolean = false, + messages: string[] = [], + gainedMidTurn: boolean = false +) { + for (const passive of [ false, true ]) { + if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { + continue; + } - -export function applyAbAttrs(attrType: Constructor, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), args, false, simulated); + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); + globalScene.clearPhaseQueueSplice(); + } } -export function applyPostBattleInitAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattleInit(pokemon, passive, simulated, args), args, false, simulated); +export function applyAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + cancelled: Utils.BooleanHolder | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), + args, + false, + simulated, + ); } -export function applyPreDefendAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), args, false, simulated); +export function applyPostBattleInitAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostBattleInit(pokemon, passive, simulated, args), + args, + false, + simulated, + ); } -export function applyPostDefendAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated); +export function applyPreDefendAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + attacker: Pokemon, + move: Move | null, + cancelled: Utils.BooleanHolder | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), + args, + false, + simulated, + ); } -export function applyPostMoveUsedAbAttrs(attrType: Constructor, - pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), args, false, simulated); +export function applyPostDefendAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), + args, + false, + simulated, + ); } -export function applyStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args); -} -export function applyPostSetStatusAbAttrs(attrType: Constructor, - pokemon: Pokemon, effect: StatusEffect, sourcePokemon?: Pokemon | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated); +export function applyPostMoveUsedAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + move: PokemonMove, + source: Pokemon, + targets: BattlerIndex[], + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), + args, + false, + simulated, + ); } -export function applyPostDamageAbAttrs(attrType: Constructor, - pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean = false, args: any[], source?: Pokemon): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args, source), args); +export function applyStatMultiplierAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + stat: BattleStat, + statValue: Utils.NumberHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), + args, + ); +} +export function applyPostSetStatusAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + effect: StatusEffect, + sourcePokemon?: Pokemon | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), + args, + false, + simulated, + ); +} + +export function applyPostDamageAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + damage: number, + passive: boolean, + simulated: boolean = false, + args: any[], + source?: Pokemon, +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args, source), + args, + ); } /** @@ -5215,109 +5525,352 @@ export function applyPostDamageAbAttrs(attrType: Constructor, * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat * @param args unused */ -export function applyFieldStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args); +export function applyFieldStatMultiplierAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + stat: Stat, + statValue: Utils.NumberHolder, + checkedPokemon: Pokemon, + hasApplied: Utils.BooleanHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), + args, + ); } -export function applyPreAttackAbAttrs(attrType: Constructor, - pokemon: Pokemon, defender: Pokemon | null, move: Move, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, simulated, defender, move, args), args, false, simulated); +export function applyPreAttackAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + defender: Pokemon | null, + move: Move, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreAttack(pokemon, passive, simulated, defender, move, args), + args, + false, + simulated, + ); } -export function applyPostAttackAbAttrs(attrType: Constructor, - pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), args, false, simulated); +export function applyPostAttackAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), + args, + false, + simulated, + ); } -export function applyPostKnockOutAbAttrs(attrType: Constructor, - pokemon: Pokemon, knockedOut: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), args, false, simulated); +export function applyPostKnockOutAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + knockedOut: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), + args, + false, + simulated, + ); } -export function applyPostVictoryAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostVictory(pokemon, passive, simulated, args), args, false, simulated); +export function applyPostVictoryAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostVictory(pokemon, passive, simulated, args), + args, + false, + simulated, + ); } -export function applyPostSummonAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, false, simulated); +export function applyPostSummonAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), + args, + false, + simulated, + ); } -export function applyPreSwitchOutAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), args, true, simulated); +export function applyPreSwitchOutAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), + args, + true, + simulated, + ); } -export function applyPreLeaveFieldAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), args, true, simulated); +export function applyPreStatStageChangeAbAttrs( + attrType: Constructor, + pokemon: Pokemon | null, + stat: BattleStat, + cancelled: Utils.BooleanHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), + args, + false, + simulated, + ); } -export function applyPreStatStageChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated); +export function applyPostStatStageChangeAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + stats: BattleStat[], + stages: integer, + selfTarget: boolean, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), + args, + false, + simulated, + ); } -export function applyPostStatStageChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, stats: BattleStat[], stages: number, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), args, false, simulated); +export function applyPreSetStatusAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + effect: StatusEffect | undefined, + cancelled: Utils.BooleanHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), + args, + false, + simulated, + ); } -export function applyPreSetStatusAbAttrs(attrType: Constructor, - pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), args, false, simulated); +export function applyPreApplyBattlerTagAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + tag: BattlerTag, + cancelled: Utils.BooleanHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), + args, + false, + simulated, + ); } -export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor, - pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), args, false, simulated); +export function applyPreWeatherEffectAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + weather: Weather | null, + cancelled: Utils.BooleanHolder, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), + args, + true, + simulated, + ); } -export function applyPreWeatherEffectAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), args, true, simulated); +export function applyPostTurnAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostTurn(pokemon, passive, simulated, args), + args, + false, + simulated, + ); } -export function applyPostTurnAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTurn(pokemon, passive, simulated, args), args, false, simulated); +export function applyPostWeatherChangeAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + weather: WeatherType, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, simulated, weather, args), + args, + false, + simulated, + ); } -export function applyPostWeatherChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: WeatherType, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, simulated, weather, args), args, false, simulated); +export function applyPostWeatherLapseAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + weather: Weather | null, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, simulated, weather, args), + args, + false, + simulated, + ); } -export function applyPostWeatherLapseAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: Weather | null, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, simulated, weather, args), args, false, simulated); +export function applyPostTerrainChangeAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + terrain: TerrainType, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, simulated, terrain, args), + args, + false, + simulated, + ); } -export function applyPostTerrainChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, terrain: TerrainType, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, simulated, terrain, args), args, false, simulated); +export function applyCheckTrappedAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + trapped: Utils.BooleanHolder, + otherPokemon: Pokemon, + messages: string[], + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), + args, + false, + simulated, + messages, + ); } -export function applyCheckTrappedAbAttrs(attrType: Constructor, - pokemon: Pokemon, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, messages: string[], simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), args, false, simulated, messages); +export function applyPostBattleAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostBattle(pokemon, passive, simulated, args), + args, + false, + simulated, + ); } -export function applyPostBattleAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattle(pokemon, passive, simulated, args), args, false, simulated); +export function applyPostFaintAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + attacker?: Pokemon, + move?: Move, + hitResult?: HitResult, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), + args, + false, + simulated, + ); } -export function applyPostFaintAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker?: Pokemon, move?: Move, hitResult?: HitResult, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated); -} - -export function applyPostItemLostAbAttrs(attrType: Constructor, - pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), args); +export function applyPostItemLostAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), + args, + ); } /** diff --git a/src/data/move.ts b/src/data/move.ts index acc68eb6a26..aa593001c54 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,5 +1,16 @@ -import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims"; -import { CommandedTag, EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; +import { ChargeAnim, MoveChargeAnim } from "./battle-anims"; +import { + CommandedTag, + EncoreTag, + GulpMissileTag, + HelpingHandTag, + SemiInvulnerableTag, + ShellTrapTag, + StockpilingTag, + SubstituteTag, + TrappedTag, + TypeBoostTag, +} from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; import type { AttackMoveResult, TurnMove } from "../field/pokemon"; import type Pokemon from "../field/pokemon"; @@ -30,7 +41,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; +import { BATTLE_STATS, type BattleStat, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -46,6 +57,10 @@ import { applyChallenges, ChallengeType } from "./challenge"; import { SwitchType } from "#enums/switch-type"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; +import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; +import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; +import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; +import { MoveAnimPhase } from "#app/phases/move-anim-phase"; export enum MoveCategory { PHYSICAL, @@ -1057,7 +1072,7 @@ export abstract class MoveAttr { * @param args Set of unique arguments needed by this attribute * @returns true if application of the ability succeeds */ - apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean | Promise { + apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean { return true; } @@ -1200,7 +1215,7 @@ export class MoveEffectAttr extends MoveAttr { } /** Applies move effects so long as they are able based on {@linkcode canApply} */ - apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { return this.canApply(user, target, move, args); } @@ -1866,7 +1881,7 @@ export class FlameBurstAttr extends MoveEffectAttr { * @param args - n/a * @returns A boolean indicating whether the effect was successfully applied. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const targetAlly = target.getAlly(); const cancelled = new Utils.BooleanHolder(false); @@ -2406,32 +2421,27 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { this.chance = chance; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - if (move.hitsSubstitute(user, target)) { - return resolve(false); - } - const rand = Phaser.Math.RND.realInRange(0, 1); - if (rand >= this.chance) { - return resolve(false); - } - const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); - if (heldItems.length) { - const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; - const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? - const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); - const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; - globalScene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { - if (success) { - globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); - } - resolve(success); - }); - return; - } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (move.hitsSubstitute(user, target)) { + return false; + } - resolve(false); - }); + const rand = Phaser.Math.RND.realInRange(0, 1); + if (rand >= this.chance) { + return false; + } + const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); + if (heldItems.length) { + const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; + const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? + const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier); + const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; + if (globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) { + globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); + return true; + } + } + return false; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { @@ -2875,9 +2885,7 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr { } export class OverrideMoveEffectAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { - //const overridden = args[0] as Utils.BooleanHolder; - //const virtual = arg[1] as boolean; + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { return true; } } @@ -2903,26 +2911,27 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { this.chargeText = chargeText; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // Edge case for the move applied on a pokemon that has fainted if (!target) { - return Promise.resolve(true); + return true; } - const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - return new Promise(resolve => { - if (args.length < 2 || !args[1]) { - new MoveChargeAnim(this.chargeAnim, move.id, user).play(false, () => { - (args[0] as Utils.BooleanHolder).value = true; - globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); - user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); - resolve(true); - }); - } else { - globalScene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }), null, () => resolve(true)); - } - }); + const overridden = args[0] as Utils.BooleanHolder; + const virtual = args[1] as boolean; + + if (!virtual) { + overridden.value = true; + globalScene.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user))); + globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); + user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); + const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); + } else { + globalScene.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name })); + } + + return true; } } @@ -3053,7 +3062,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { * @param args unused * @returns whether stat stages were changed */ - apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) { return false; } @@ -3131,7 +3140,7 @@ export class SecretPowerAttr extends MoveEffectAttr { * Used to apply the secondary effect to the target Pokemon * @returns `true` if a secondary effect is successfully applied */ - override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { if (!super.apply(user, target, move, args)) { return false; } @@ -3286,8 +3295,8 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr { super(); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { - const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6); + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6); if (randStats.length > 0) { const boostStat = [ randStats[user.randSeedInt(randStats.length)] ]; globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); @@ -3324,17 +3333,14 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr { this.messageCallback = messageCallback; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true); - user.updateInfo().then(() => { - const ret = super.apply(user, target, move, args); - if (this.messageCallback) { - this.messageCallback(user); - } - resolve(ret); - }); - }); + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true); + user.updateInfo(); // TODO: This is a Promise and might cause desync issues + const ret = super.apply(user, target, move, args); + if (this.messageCallback) { + this.messageCallback(user); + } + return ret; } getCondition(): MoveConditionFunc { @@ -3426,28 +3432,27 @@ export class ResetStatsAttr extends MoveEffectAttr { super(); this.targetAllPokemon = targetAllPokemon; } - async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - const promises: Promise[] = []; - if (this.targetAllPokemon) { // Target all pokemon on the field when Freezy Frost or Haze are used + + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (this.targetAllPokemon) { + // Target all pokemon on the field when Freezy Frost or Haze are used const activePokemon = globalScene.getField(true); - activePokemon.forEach(p => promises.push(this.resetStats(p))); + activePokemon.forEach((p) => this.resetStats(p)); globalScene.queueMessage(i18next.t("moveTriggers:statEliminated")); } else { // Affects only the single target when Clear Smog is used if (!move.hitsSubstitute(user, target)) { - promises.push(this.resetStats(target)); + this.resetStats(target); globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); } } - - await Promise.all(promises); return true; } - async resetStats(pokemon: Pokemon): Promise { + private resetStats(pokemon: Pokemon): void { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, 0); } - return pokemon.updateInfo(); + pokemon.updateInfo(); // TODO: This is still a Promise and might cause desync issues } } @@ -3503,43 +3508,28 @@ export class SwapStatStagesAttr extends MoveEffectAttr { } export class HpSplitAttr extends MoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - if (!super.apply(user, target, move, args)) { - return resolve(false); - } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) { + return false; + } - const infoUpdates: Promise[] = []; - - const hpValue = Math.floor((target.hp + user.hp) / 2); - if (user.hp < hpValue) { - const healing = user.heal(hpValue - user.hp); + const hpValue = Math.floor((target.hp + user.hp) / 2); + [ user, target ].forEach((p) => { + if (p.hp < hpValue) { + const healing = p.heal(hpValue - p.hp); if (healing) { - globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL); + globalScene.damageNumberHandler.add(p, healing, HitResult.HEAL); } - } else if (user.hp > hpValue) { - const damage = user.damage(user.hp - hpValue, true); + } else if (p.hp > hpValue) { + const damage = p.damage(p.hp - hpValue, true); if (damage) { - globalScene.damageNumberHandler.add(user, damage); + globalScene.damageNumberHandler.add(p, damage); } } - infoUpdates.push(user.updateInfo()); - - if (target.hp < hpValue) { - const healing = target.heal(hpValue - target.hp); - if (healing) { - globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL); - } - } else if (target.hp > hpValue) { - const damage = target.damage(target.hp - hpValue, true); - if (damage) { - globalScene.damageNumberHandler.add(target, damage); - } - } - infoUpdates.push(target.updateInfo()); - - return Promise.all(infoUpdates).then(() => resolve(true)); + p.updateInfo(); // TODO: This is still a Promise }); + + return true; } } @@ -6024,40 +6014,34 @@ export class RevivalBlessingAttr extends MoveEffectAttr { * @param args N/A * @returns Promise, true if function succeeds. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - // If user is player, checks if the user has fainted pokemon - if (user instanceof PlayerPokemon - && globalScene.getPlayerParty().findIndex(p => p.isFainted()) > -1) { - (user as PlayerPokemon).revivalBlessing().then(() => { - resolve(true); - }); - // If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party - } else if (user instanceof EnemyPokemon - && user.hasTrainer() - && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { - // Selects a random fainted pokemon - const faintedPokemon = globalScene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); - const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; - const slotIndex = globalScene.getEnemyParty().findIndex(p => pokemon.id === p.id); - pokemon.resetStatus(); - pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + // If user is player, checks if the user has fainted pokemon + if (user instanceof PlayerPokemon && globalScene.getPlayerParty().findIndex((p) => p.isFainted()) > -1) { + globalScene.unshiftPhase(new RevivalBlessingPhase(user)); + return true; + } else if (user instanceof EnemyPokemon && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) { + // If used by an enemy trainer with at least one fainted non-boss Pokemon, this + // revives one of said Pokemon selected at random. + const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss()); + const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; + const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id); + pokemon.resetStatus(); + pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); + globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); - if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) { - const allyPokemon = user.getAlly(); - if (slotIndex <= 1) { - globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false)); - } else if (allyPokemon.isFainted()) { - globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); - } + if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) { + const allyPokemon = user.getAlly(); + if (slotIndex <= 1) { + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false)); + } else if (allyPokemon.isFainted()) { + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); } - resolve(true); - } else { - globalScene.queueMessage(i18next.t("battle:attackFailed")); - resolve(false); } - }); + return true; + } else { + globalScene.queueMessage(i18next.t("battle:attackFailed")); + return false; + } } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -6579,7 +6563,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { class CallMoveAttr extends OverrideMoveEffectAttr { protected invalidMoves: Moves[]; protected hasTarget: boolean; - async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); if (moveTargets.targets.length === 0) { @@ -6589,11 +6573,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true }); + globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id)); globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); - - await Promise.resolve(initMoveAnim(move.id).then(() => { - loadMoveAnimAssets([ move.id ], true); - })); return true; } } @@ -6626,7 +6607,7 @@ export class RandomMoveAttr extends CallMoveAttr { * @param move Move being used * @param args Unused */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); let moveId: Moves = Moves.NONE; do { @@ -6663,7 +6644,7 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { * @param move Move being used * @param args Unused */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { return super.apply(user, target, allMoves[this.moveId], args); } @@ -6965,145 +6946,141 @@ const invalidCopycatMoves = [ ]; export class NaturePowerAttr extends OverrideMoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - let moveId; - switch (globalScene.arena.getTerrainType()) { - // this allows terrains to 'override' the biome move - case TerrainType.NONE: - switch (globalScene.arena.biomeType) { - case Biome.TOWN: - moveId = Moves.ROUND; - break; - case Biome.METROPOLIS: - moveId = Moves.TRI_ATTACK; - break; - case Biome.SLUM: - moveId = Moves.SLUDGE_BOMB; - break; - case Biome.PLAINS: - moveId = Moves.SILVER_WIND; - break; - case Biome.GRASS: - moveId = Moves.GRASS_KNOT; - break; - case Biome.TALL_GRASS: - moveId = Moves.POLLEN_PUFF; - break; - case Biome.MEADOW: - moveId = Moves.GIGA_DRAIN; - break; - case Biome.FOREST: - moveId = Moves.BUG_BUZZ; - break; - case Biome.JUNGLE: - moveId = Moves.LEAF_STORM; - break; - case Biome.SEA: - moveId = Moves.HYDRO_PUMP; - break; - case Biome.SWAMP: - moveId = Moves.MUD_BOMB; - break; - case Biome.BEACH: - moveId = Moves.SCALD; - break; - case Biome.LAKE: - moveId = Moves.BUBBLE_BEAM; - break; - case Biome.SEABED: - moveId = Moves.BRINE; - break; - case Biome.ISLAND: - moveId = Moves.LEAF_TORNADO; - break; - case Biome.MOUNTAIN: - moveId = Moves.AIR_SLASH; - break; - case Biome.BADLANDS: - moveId = Moves.EARTH_POWER; - break; - case Biome.DESERT: - moveId = Moves.SCORCHING_SANDS; - break; - case Biome.WASTELAND: - moveId = Moves.DRAGON_PULSE; - break; - case Biome.CONSTRUCTION_SITE: - moveId = Moves.STEEL_BEAM; - break; - case Biome.CAVE: - moveId = Moves.POWER_GEM; - break; - case Biome.ICE_CAVE: - moveId = Moves.ICE_BEAM; - break; - case Biome.SNOWY_FOREST: - moveId = Moves.FROST_BREATH; - break; - case Biome.VOLCANO: - moveId = Moves.LAVA_PLUME; - break; - case Biome.GRAVEYARD: - moveId = Moves.SHADOW_BALL; - break; - case Biome.RUINS: - moveId = Moves.ANCIENT_POWER; - break; - case Biome.TEMPLE: - moveId = Moves.EXTRASENSORY; - break; - case Biome.DOJO: - moveId = Moves.FOCUS_BLAST; - break; - case Biome.FAIRY_CAVE: - moveId = Moves.ALLURING_VOICE; - break; - case Biome.ABYSS: - moveId = Moves.OMINOUS_WIND; - break; - case Biome.SPACE: - moveId = Moves.DRACO_METEOR; - break; - case Biome.FACTORY: - moveId = Moves.FLASH_CANNON; - break; - case Biome.LABORATORY: - moveId = Moves.ZAP_CANNON; - break; - case Biome.POWER_PLANT: - moveId = Moves.CHARGE_BEAM; - break; - case Biome.END: - moveId = Moves.ETERNABEAM; - break; - } - break; - case TerrainType.MISTY: - moveId = Moves.MOONBLAST; - break; - case TerrainType.ELECTRIC: - moveId = Moves.THUNDERBOLT; - break; - case TerrainType.GRASSY: - moveId = Moves.ENERGY_BALL; - break; - case TerrainType.PSYCHIC: - moveId = Moves.PSYCHIC; - break; - default: - // Just in case there's no match - moveId = Moves.TRI_ATTACK; - break; - } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + let moveId; + switch (globalScene.arena.getTerrainType()) { + // this allows terrains to 'override' the biome move + case TerrainType.NONE: + switch (globalScene.arena.biomeType) { + case Biome.TOWN: + moveId = Moves.ROUND; + break; + case Biome.METROPOLIS: + moveId = Moves.TRI_ATTACK; + break; + case Biome.SLUM: + moveId = Moves.SLUDGE_BOMB; + break; + case Biome.PLAINS: + moveId = Moves.SILVER_WIND; + break; + case Biome.GRASS: + moveId = Moves.GRASS_KNOT; + break; + case Biome.TALL_GRASS: + moveId = Moves.POLLEN_PUFF; + break; + case Biome.MEADOW: + moveId = Moves.GIGA_DRAIN; + break; + case Biome.FOREST: + moveId = Moves.BUG_BUZZ; + break; + case Biome.JUNGLE: + moveId = Moves.LEAF_STORM; + break; + case Biome.SEA: + moveId = Moves.HYDRO_PUMP; + break; + case Biome.SWAMP: + moveId = Moves.MUD_BOMB; + break; + case Biome.BEACH: + moveId = Moves.SCALD; + break; + case Biome.LAKE: + moveId = Moves.BUBBLE_BEAM; + break; + case Biome.SEABED: + moveId = Moves.BRINE; + break; + case Biome.ISLAND: + moveId = Moves.LEAF_TORNADO; + break; + case Biome.MOUNTAIN: + moveId = Moves.AIR_SLASH; + break; + case Biome.BADLANDS: + moveId = Moves.EARTH_POWER; + break; + case Biome.DESERT: + moveId = Moves.SCORCHING_SANDS; + break; + case Biome.WASTELAND: + moveId = Moves.DRAGON_PULSE; + break; + case Biome.CONSTRUCTION_SITE: + moveId = Moves.STEEL_BEAM; + break; + case Biome.CAVE: + moveId = Moves.POWER_GEM; + break; + case Biome.ICE_CAVE: + moveId = Moves.ICE_BEAM; + break; + case Biome.SNOWY_FOREST: + moveId = Moves.FROST_BREATH; + break; + case Biome.VOLCANO: + moveId = Moves.LAVA_PLUME; + break; + case Biome.GRAVEYARD: + moveId = Moves.SHADOW_BALL; + break; + case Biome.RUINS: + moveId = Moves.ANCIENT_POWER; + break; + case Biome.TEMPLE: + moveId = Moves.EXTRASENSORY; + break; + case Biome.DOJO: + moveId = Moves.FOCUS_BLAST; + break; + case Biome.FAIRY_CAVE: + moveId = Moves.ALLURING_VOICE; + break; + case Biome.ABYSS: + moveId = Moves.OMINOUS_WIND; + break; + case Biome.SPACE: + moveId = Moves.DRACO_METEOR; + break; + case Biome.FACTORY: + moveId = Moves.FLASH_CANNON; + break; + case Biome.LABORATORY: + moveId = Moves.ZAP_CANNON; + break; + case Biome.POWER_PLANT: + moveId = Moves.CHARGE_BEAM; + break; + case Biome.END: + moveId = Moves.ETERNABEAM; + break; + } + break; + case TerrainType.MISTY: + moveId = Moves.MOONBLAST; + break; + case TerrainType.ELECTRIC: + moveId = Moves.THUNDERBOLT; + break; + case TerrainType.GRASSY: + moveId = Moves.ENERGY_BALL; + break; + case TerrainType.PSYCHIC: + moveId = Moves.PSYCHIC; + break; + default: + // Just in case there's no match + moveId = Moves.TRI_ATTACK; + break; + } - user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); - globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(moveId).then(() => { - loadMoveAnimAssets([ moveId ], true) - .then(() => resolve(true)); - }); - }); + user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); + globalScene.unshiftPhase(new LoadMoveAnimPhase(moveId)); + globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); + return true; } } @@ -7121,7 +7098,7 @@ export class CopyMoveAttr extends CallMoveAttr { this.invalidMoves = invalidMoves; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { this.hasTarget = this.mirrorMove; const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove; return super.apply(user, target, allMoves[lastMove], args); @@ -7682,50 +7659,15 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr { * Used by Transform */ export class TransformAttr extends MoveEffectAttr { - async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) { return false; } - const promises: Promise[] = []; - user.summonData.speciesForm = target.getSpeciesForm(); - user.summonData.gender = target.getGender(); - - // Power Trick's effect will not preserved after using Transform - user.removeTag(BattlerTagType.POWER_TRICK); - - // Copy all stats (except HP) - for (const s of EFFECTIVE_STATS) { - user.setStat(s, target.getStat(s, false), false); - } - - // Copy all stat stages - for (const s of BATTLE_STATS) { - user.setStatStage(s, target.getStatStage(s)); - } - - user.summonData.moveset = target.getMoveset().map((m) => { - if (m) { - // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. - return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); - } else { - console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`); - return new PokemonMove(Moves.NONE); - } - }); - user.summonData.types = target.getTypes(); - promises.push(user.updateInfo()); + globalScene.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex())); globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); - promises.push(user.loadAssets(false).then(() => { - user.playAnim(); - user.updateInfo(); - // If the new ability activates immediately, it needs to happen after all the transform animations - user.setTempAbility(target.getAbility()); - })); - - await Promise.all(promises); return true; } } @@ -8128,44 +8070,54 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) = export type MoveAttrFilter = (attr: MoveAttr) => boolean; -function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const attrPromises: Promise[] = []; - const moveAttrs = move.attrs.filter(a => attrFilter(a)); - for (const attr of moveAttrs) { - const result = attr.apply(user, target, move, args); - if (result instanceof Promise) { - attrPromises.push(result); - } - } - Promise.allSettled(attrPromises).then(() => resolve()); - }); +function applyMoveAttrsInternal( + attrFilter: MoveAttrFilter, + user: Pokemon | null, + target: Pokemon | null, + move: Move, + args: any[], +): void { + move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args)); } -function applyMoveChargeAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, args: any[]): Promise { - return new Promise(resolve => { - const chargeAttrPromises: Promise[] = []; - const chargeMoveAttrs = move.chargeAttrs.filter(a => attrFilter(a)); - for (const attr of chargeMoveAttrs) { - const result = attr.apply(user, target, move, args); - if (result instanceof Promise) { - chargeAttrPromises.push(result); - } - } - Promise.allSettled(chargeAttrPromises).then(() => resolve()); - }); +function applyMoveChargeAttrsInternal( + attrFilter: MoveAttrFilter, + user: Pokemon | null, + target: Pokemon | null, + move: ChargingMove, + args: any[], +): void { + move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args)); } -export function applyMoveAttrs(attrType: Constructor, user: Pokemon | null, target: Pokemon | null, move: Move, ...args: any[]): Promise { - return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); +export function applyMoveAttrs( + attrType: Constructor, + user: Pokemon | null, + target: Pokemon | null, + move: Move, + ...args: any[] +): void { + applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); } -export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon | null, move: Move, ...args: any[]): Promise { - return applyMoveAttrsInternal(attrFilter, user, target, move, args); +export function applyFilteredMoveAttrs( + attrFilter: MoveAttrFilter, + user: Pokemon, + target: Pokemon | null, + move: Move, + ...args: any[] +): void { + applyMoveAttrsInternal(attrFilter, user, target, move, args); } -export function applyMoveChargeAttrs(attrType: Constructor, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, ...args: any[]): Promise { - return applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); +export function applyMoveChargeAttrs( + attrType: Constructor, + user: Pokemon | null, + target: Pokemon | null, + move: ChargingMove, + ...args: any[] +): void { + applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); } export class MoveCondition { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 246f82b8164..214c667f1c1 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -104,7 +104,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; @@ -4510,43 +4509,6 @@ export class PlayerPokemon extends Pokemon { this.friendship = Math.max(this.friendship + friendship, 0); } } - /** - * Handles Revival Blessing when used by player. - * @returns Promise to revive a pokemon. - * @see {@linkcode RevivalBlessingAttr} - */ - revivalBlessing(): Promise { - return new Promise(resolve => { - globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:number, option: PartyOption) => { - if (slotIndex >= 0 && slotIndex < 6) { - const pokemon = globalScene.getPlayerParty()[slotIndex]; - if (!pokemon || !pokemon.isFainted()) { - resolve(); - } - - pokemon.resetTurnData(); - pokemon.resetStatus(); - pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true); - - if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) { - const allyPokemon = this.getAlly(); - if (slotIndex <= 1) { - // Revived ally pokemon - globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true)); - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); - } else if (allyPokemon.isFainted()) { - // Revived party pokemon, and ally pokemon is fainted - globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true)); - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); - } - } - - } - globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve()); - }, PartyUiHandler.FilterFainted); - }); - } getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise { if (!evolution) { @@ -4728,70 +4690,62 @@ export class PlayerPokemon extends Pokemon { } /** - * Returns a Promise to fuse two PlayerPokemon together - * @param pokemon The PlayerPokemon to fuse to this one - */ - fuse(pokemon: PlayerPokemon): Promise { - return new Promise(resolve => { - this.fusionSpecies = pokemon.species; - this.fusionFormIndex = pokemon.formIndex; - this.fusionAbilityIndex = pokemon.abilityIndex; - this.fusionShiny = pokemon.shiny; - this.fusionVariant = pokemon.variant; - this.fusionGender = pokemon.gender; - this.fusionLuck = pokemon.luck; - this.fusionCustomPokemonData = pokemon.customPokemonData; - if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { - this.pauseEvolutions = true; - } + * Returns a Promise to fuse two PlayerPokemon together + * @param pokemon The PlayerPokemon to fuse to this one + */ + fuse(pokemon: PlayerPokemon): void { + this.fusionSpecies = pokemon.species; + this.fusionFormIndex = pokemon.formIndex; + this.fusionAbilityIndex = pokemon.abilityIndex; + this.fusionShiny = pokemon.shiny; + this.fusionVariant = pokemon.variant; + this.fusionGender = pokemon.gender; + this.fusionLuck = pokemon.luck; + this.fusionCustomPokemonData = pokemon.customPokemonData; + if (pokemon.pauseEvolutions || this.pauseEvolutions) { + this.pauseEvolutions = true; + } - globalScene.validateAchv(achvs.SPLICE); - globalScene.gameData.gameStats.pokemonFused++; + globalScene.validateAchv(achvs.SPLICE); + globalScene.gameData.gameStats.pokemonFused++; - // Store the average HP% that each Pokemon has - const maxHp = this.getMaxHp(); - const newHpPercent = ((pokemon.hp / pokemon.getMaxHp()) + (this.hp / maxHp)) / 2; + // Store the average HP% that each Pokemon has + const maxHp = this.getMaxHp(); + const newHpPercent = (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2; - this.generateName(); - this.calculateStats(); + this.generateName(); + this.calculateStats(); - // Set this Pokemon's HP to the average % of both fusion components - this.hp = Math.round(maxHp * newHpPercent); - if (!this.isFainted()) { - // If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum - this.hp = Math.min(this.hp, maxHp); - this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two - } else if (!pokemon.isFainted()) { - // If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero - this.hp = Math.max(this.hp, 1); - this.status = pokemon.status; // Inherit the other Pokemon's status - } + // Set this Pokemon's HP to the average % of both fusion components + this.hp = Math.round(maxHp * newHpPercent); + if (!this.isFainted()) { + // If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum + this.hp = Math.min(this.hp, maxHp); + this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two + } else if (!pokemon.isFainted()) { + // If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero + this.hp = Math.max(this.hp, 1); + this.status = pokemon.status; // Inherit the other Pokemon's status + } - this.generateCompatibleTms(); - this.updateInfo(true); - const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon); - let partyMemberIndex = globalScene.getPlayerParty().indexOf(this); - if (partyMemberIndex > fusedPartyMemberIndex) { - partyMemberIndex--; - } - const fusedPartyMemberHeldModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; - const transferModifiers: Promise[] = []; - for (const modifier of fusedPartyMemberHeldModifiers) { - transferModifiers.push(globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false)); - } - Promise.allSettled(transferModifiers).then(() => { - globalScene.updateModifiers(true, true).then(() => { - globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); - globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; - const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); - pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); - pokemon.destroy(); - this.updateFusionPalette(); - resolve(); - }); - }); - }); + this.generateCompatibleTms(); + this.updateInfo(true); + const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon); + let partyMemberIndex = globalScene.getPlayerParty().indexOf(this); + if (partyMemberIndex > fusedPartyMemberIndex) { + partyMemberIndex--; + } + const fusedPartyMemberHeldModifiers = globalScene.findModifiers((m) => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; + for (const modifier of fusedPartyMemberHeldModifiers) { + globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); + } + globalScene.updateModifiers(true, true); + globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); + globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; + const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); + pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); + pokemon.destroy(); + this.updateFusionPalette(); } unfuse(): Promise { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 3af2aa2144f..d5b0f9a9c0e 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -158,7 +158,7 @@ export abstract class Modifier { * Handles applying of {@linkcode Modifier} * @param args collection of all passed parameters */ - abstract apply(...args: unknown[]): boolean | Promise; + abstract apply(...args: unknown[]): boolean; } export abstract class PersistentModifier extends Modifier { @@ -1949,7 +1949,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier { * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item * @param args Additional arguments passed to {@linkcode ConsumablePokemonModifier.apply} */ - abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean | Promise; + abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean; getPokemon() { return globalScene.getPlayerParty().find(p => p.id === this.pokemonId); @@ -2288,8 +2288,8 @@ export class FusePokemonModifier extends ConsumablePokemonModifier { * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} * @returns always Promise */ - override async apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): Promise { - await playerPokemon.fuse(playerPokemon2); + override apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): boolean { + playerPokemon.fuse(playerPokemon2); return true; } } @@ -3149,12 +3149,10 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } const randItemIndex = pokemon.randSeedInt(itemModifiers.length); const randItem = itemModifiers[randItemIndex]; - heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { - if (success) { - transferredModifierTypes.push(randItem.type); - itemModifiers.splice(randItemIndex, 1); - } - })); + if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) { + transferredModifierTypes.push(randItem.type); + itemModifiers.splice(randItemIndex, 1); + } } Promise.all(heldItemTransferPromises).then(() => { diff --git a/src/phases/load-move-anim-phase.ts b/src/phases/load-move-anim-phase.ts new file mode 100644 index 00000000000..66cb90744e0 --- /dev/null +++ b/src/phases/load-move-anim-phase.ts @@ -0,0 +1,20 @@ +import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; +import type { Moves } from "#enums/moves"; +import { Phase } from "#app/phase"; + +/** + * Phase for synchronous move animation loading. + * Should be used when a move invokes another move that + * isn't already loaded (e.g. for Metronome) + */ +export class LoadMoveAnimPhase extends Phase { + constructor(protected moveId: Moves) { + super(); + } + + public override start(): void { + initMoveAnim(this.moveId) + .then(() => loadMoveAnimAssets([ this.moveId ], true)) + .then(() => this.end()); + } +} diff --git a/src/phases/move-anim-phase.ts b/src/phases/move-anim-phase.ts new file mode 100644 index 00000000000..005445924a0 --- /dev/null +++ b/src/phases/move-anim-phase.ts @@ -0,0 +1,20 @@ +import type { MoveAnim } from "#app/data/battle-anims"; +import { Phase } from "#app/phase"; + +/** + * Plays the given {@linkcode MoveAnim} sequentially. + */ +export class MoveAnimPhase extends Phase { + constructor( + protected anim: Anim, + protected onSubstitute: boolean = false, + ) { + super(); + } + + public override start(): void { + super.start(); + + this.anim.play(this.onSubstitute, () => this.end()); + } +} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 65d8d95cde6..4a88e344844 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -61,7 +61,7 @@ import { PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; -import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils"; +import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils"; import { type nil } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import type { Moves } from "#enums/moves"; @@ -143,86 +143,86 @@ export class MoveEffectPhase extends PokemonPhase { const move = this.move.getMove(); // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual).then(() => { - // If other effects were overriden, stop this phase before they can be applied - if (overridden.value) { - return this.end(); - } + applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual); - user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + // If other effects were overriden, stop this phase before they can be applied + if (overridden.value) { + return this.end(); + } - // If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that - // the move executes correctly (ensures all hits of a multi-hit are properly calculated) - if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) { - user.turnData.hitsLeft = -1; - user.turnData.hitCount = 0; - user.turnData.extraTurns--; - } + user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - /** - * If this phase is for the first hit of the invoked move, - * resolve the move's total hit count. This block combines the - * effects of the move itself, Parental Bond, and Multi-Lens to do so. - */ - if (user.turnData.hitsLeft === -1) { - const hitCount = new NumberHolder(1); - // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount); - // If Parental Bond is applicable, add another hit - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); - // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); - // Set the user's relevant turnData fields to reflect the final hit count - user.turnData.hitCount = hitCount.value; - user.turnData.hitsLeft = hitCount.value; - } + // If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that + // the move executes correctly (ensures all hits of a multi-hit are properly calculated) + if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) { + user.turnData.hitsLeft = -1; + user.turnData.hitCount = 0; + user.turnData.extraTurns--; + } - /** - * Log to be entered into the user's move history once the move result is resolved. - * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully - * used in the sense of "Does it have an effect on the user?". - */ - const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; + /** + * If this phase is for the first hit of the invoked move, + * resolve the move's total hit count. This block combines the + * effects of the move itself, Parental Bond, and Multi-Lens to do so. + */ + if (user.turnData.hitsLeft === -1) { + const hitCount = new NumberHolder(1); + // Assume single target for multi hit + applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount); + // If Parental Bond is applicable, add another hit + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); + // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses + globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); + // Set the user's relevant turnData fields to reflect the final hit count + user.turnData.hitCount = hitCount.value; + user.turnData.hitsLeft = hitCount.value; + } - /** - * Stores results of hit checks of the invoked move against all targets, organized by battler index. - * @see {@linkcode hitCheck} - */ - const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); - const hasActiveTargets = targets.some(t => t.isActive(true)); + /** + * Log to be entered into the user's move history once the move result is resolved. + * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully + * used in the sense of "Does it have an effect on the user?". + */ + const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; - /** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */ - const isImmune = targets[0]?.hasAbilityWithAttr(TypeImmunityAbAttr) - && (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) - && !targets[0]?.getTag(SemiInvulnerableTag); + /** + * Stores results of hit checks of the invoked move against all targets, organized by battler index. + * @see {@linkcode hitCheck} + */ + const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); + const hasActiveTargets = targets.some(t => t.isActive(true)); + + /** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */ + const isImmune = targets[0]?.hasAbilityWithAttr(TypeImmunityAbAttr) + && (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) + && !targets[0]?.getTag(SemiInvulnerableTag); const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected && targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT)); - /** - * If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target - * (and not random target) and failed the hit check against its target (MISS), log the move - * as FAILed or MISSed (depending on the conditions above) and end this phase. - */ - if (!hasActiveTargets || (!mayBounce && !move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { - this.stopMultiHit(); - if (hasActiveTargets) { - globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" })); - moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); - } else { - globalScene.queueMessage(i18next.t("battle:attackFailed")); - moveHistoryEntry.result = MoveResult.FAIL; - } - user.pushMoveHistory(moveHistoryEntry); - return this.end(); + /** + * If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target + * (and not random target) and failed the hit check against its target (MISS), log the move + * as FAILed or MISSed (depending on the conditions above) and end this phase. + */ + if (!hasActiveTargets || (!mayBounce && !move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { + this.stopMultiHit(); + if (hasActiveTargets) { + globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" })); + moveHistoryEntry.result = MoveResult.MISS; + applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); + } else { + globalScene.queueMessage(i18next.t("battle:attackFailed")); + moveHistoryEntry.result = MoveResult.FAIL; } + user.pushMoveHistory(moveHistoryEntry); + return this.end(); + } - /** All move effect attributes are chained together in this array to be applied asynchronously. */ - const applyAttrs: Promise[] = []; - - const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; - // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => { + const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; + // Move animation only needs one target + new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play( + move.hitsSubstitute(user, this.getFirstTarget()!), + () => { /** Has the move successfully hit a target (for damage) yet? */ let hasHit: boolean = false; @@ -313,7 +313,7 @@ export class MoveEffectPhase extends PokemonPhase { } /** Does this phase represent the invoked move's first strike? */ - const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); + const firstHit = user.turnData.hitsLeft === user.turnData.hitCount; // Only log the move's result on the first strike if (firstHit) { @@ -363,7 +363,7 @@ export class MoveEffectPhase extends PokemonPhase { } /** Does this phase represent the invoked move's last strike? */ - const lastHit = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()); + const lastHit = user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive(); /** * If the user can change forms by using the invoked move, @@ -381,43 +381,29 @@ export class MoveEffectPhase extends PokemonPhase { } } - /** - * Create a Promise that applies *all* effects from the invoked move's MoveEffectAttrs. - * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger - * type requires different conditions to be met with respect to the move's hit result. - */ - const k = new Promise((resolve) => { - //Start promise chain and apply PRE_APPLY move attributes - let promiseChain: Promise = applyFilteredMoveAttrs((attr: MoveAttr) => - attr instanceof MoveEffectAttr - && attr.trigger === MoveEffectTrigger.PRE_APPLY - && (!attr.firstHitOnly || firstHit) - && (!attr.lastHitOnly || lastHit) - && hitResult !== HitResult.NO_EFFECT, user, target, move); + applyFilteredMoveAttrs( + (attr: MoveAttr) => + attr instanceof MoveEffectAttr && + attr.trigger === MoveEffectTrigger.PRE_APPLY && + (!attr.firstHitOnly || firstHit) && + (!attr.lastHitOnly || lastHit) && + hitResult !== HitResult.NO_EFFECT, + user, + target, + move, + ); - /** Don't complete if the move failed */ - if (hitResult === HitResult.FAIL) { - return resolve(); - } - - /** Apply Move/Ability Effects in correct order */ - promiseChain = promiseChain - .then(this.applySelfTargetEffects(user, target, firstHit, lastHit)); + if (hitResult !== HitResult.FAIL) { + this.applySelfTargetEffects(user, target, firstHit, lastHit); if (hitResult !== HitResult.NO_EFFECT) { - promiseChain - .then(this.applyPostApplyEffects(user, target, firstHit, lastHit)) - .then(this.applyHeldItemFlinchCheck(user, target, dealsDamage)) - .then(this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget)) - .then(() => resolve()); + this.applyPostApplyEffects(user, target, firstHit, lastHit); + this.applyHeldItemFlinchCheck(user, target, dealsDamage); + this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget); } else { - promiseChain - .then(() => applyMoveAttrs(NoEffectAttr, user, null, move)) - .then(resolve); + applyMoveAttrs(NoEffectAttr, user, null, move); } - }); - - applyAttrs.push(k); + } } // Apply queued phases @@ -425,41 +411,35 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.appendToPhase(queuedPhases, MoveEndPhase); } // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved - const postTarget = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) ? - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : - null; - - if (postTarget) { - if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after - applyAttrs[applyAttrs.length - 1].then(() => postTarget); - } else { // Otherwise, push a new asynchronous move effect - applyAttrs.push(postTarget); - } + if (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) { + applyFilteredMoveAttrs( + (attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, + user, + null, + move, + ); } - // Wait for all move effects to finish applying, then end this phase - Promise.allSettled(applyAttrs).then(() => { - /** - * Remove the target's substitute (if it exists and has expired) - * after all targeted effects have applied. - * This prevents blocked effects from applying until after this hit resolves. - */ - targets.forEach(target => { - const substitute = target.getTag(SubstituteTag); - if (substitute && substitute.hp <= 0) { - target.lapseTag(BattlerTagType.SUBSTITUTE); - } - }); - - const moveType = user.getMoveType(move, true); - if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { - user.stellarTypesBoosted.push(moveType); + /** + * Remove the target's substitute (if it exists and has expired) + * after all targeted effects have applied. + * This prevents blocked effects from applying until after this hit resolves. + */ + targets.forEach((target) => { + const substitute = target.getTag(SubstituteTag); + if (substitute && substitute.hp <= 0) { + target.lapseTag(BattlerTagType.SUBSTITUTE); } - - this.end(); }); - }); - }); + + const moveType = user.getMoveType(move, true); + if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { + user.stellarTypesBoosted.push(moveType); + } + + this.end(); + }, + ); } public override end(): void { @@ -500,7 +480,7 @@ export class MoveEffectPhase extends PokemonPhase { * @param lastHit - `true` if this is the last hit in a multi-hit attack * @returns a function intended to be passed into a `then()` call. */ - protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise { + protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => void { return () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY @@ -518,7 +498,7 @@ export class MoveEffectPhase extends PokemonPhase { * @param lastHit - `true` if this is the last hit in a multi-hit attack * @returns a function intended to be passed into a `then()` call. */ - protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise { + protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => void { return () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY @@ -537,8 +517,8 @@ export class MoveEffectPhase extends PokemonPhase { * @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move} * @returns a function intended to be passed into a `then()` call. */ - protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): Promise { - return applyFilteredMoveAttrs((attr: MoveAttr) => + protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): void { + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit) @@ -554,21 +534,18 @@ export class MoveEffectPhase extends PokemonPhase { * @param hitResult - The {@linkcode HitResult} of the attempted move * @returns a `Promise` intended to be passed into a `then()` call. */ - protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): Promise { - return executeIf(!target.isFainted() || target.canApplyAbility(), () => - applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult) - .then(() => { + protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { + if (!target.isFainted() || target.canApplyAbility()) { + applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult); - if (!this.move.getMove().hitsSubstitute(user, target)) { - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { - globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); - } + if (!this.move.getMove().hitsSubstitute(user, target)) { + if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { + globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); + } - target.lapseTags(BattlerTagLapseType.AFTER_HIT); - } - - }) - ); + target.lapseTags(BattlerTagLapseType.AFTER_HIT); + } + } } /** @@ -583,17 +560,15 @@ export class MoveEffectPhase extends PokemonPhase { * @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move} * @returns a function intended to be passed into a `then()` call. */ - protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, isProtected : boolean, hitResult: HitResult, firstTarget: boolean) : () => Promise { - return () => executeIf(!isProtected, () => - this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget).then(() => - this.applyOnGetHitAbEffects(user, target, hitResult)).then(() => - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult)).then(() => { // Item Stealing Effects - - if (this.move.getMove() instanceof AttackMove) { - globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); - } - }) - ); + protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean, isProtected: boolean, hitResult: HitResult, firstTarget: boolean): void { + if (!isProtected) { + this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget); + this.applyOnGetHitAbEffects(user, target, hitResult); + applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult); + if (this.move.getMove() instanceof AttackMove) { + globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + } + } } /** diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts new file mode 100644 index 00000000000..1c61530c4c5 --- /dev/null +++ b/src/phases/pokemon-transform-phase.ts @@ -0,0 +1,71 @@ +import type { BattlerIndex } from "#app/battle"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; +import { PokemonMove } from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { PokemonPhase } from "./pokemon-phase"; + +/** + * Transforms a Pokemon into another Pokemon on the field. + * Used for Transform (move) and Imposter (ability) + */ +export class PokemonTransformPhase extends PokemonPhase { + protected targetIndex: BattlerIndex; + private playSound: boolean; + + constructor(userIndex: BattlerIndex, targetIndex: BattlerIndex, playSound: boolean = false) { + super(userIndex); + + this.targetIndex = targetIndex; + this.playSound = playSound; + } + + public override start(): void { + const user = this.getPokemon(); + const target = globalScene.getField(true).find((p) => p.getBattlerIndex() === this.targetIndex); + + if (!target) { + return this.end(); + } + + // Power Trick's effect is removed after using Transform + user.removeTag(BattlerTagType.POWER_TRICK); + + // Copy all stats (except HP) + for (const s of EFFECTIVE_STATS) { + user.setStat(s, target.getStat(s, false), false); + } + + // Copy all stat stages + for (const s of BATTLE_STATS) { + user.setStatStage(s, target.getStatStage(s)); + } + + user.summonData.moveset = target.getMoveset().map((m) => { + if (m) { + // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. + return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); + } else { + console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`); + return new PokemonMove(Moves.NONE); + } + }); + user.summonData.types = target.getTypes(); + + const promises = [ user.updateInfo() ]; + + if (this.playSound) { + globalScene.playSound("battle_anims/PRSFX- Transform"); + } + + promises.push( + user.loadAssets(false).then(() => { + user.playAnim(); + user.updateInfo(); + }), + ); + + Promise.allSettled(promises).then(() => this.end()); + } +} diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts new file mode 100644 index 00000000000..a063e325a31 --- /dev/null +++ b/src/phases/revival-blessing-phase.ts @@ -0,0 +1,61 @@ +import { SwitchType } from "#enums/switch-type"; +import { globalScene } from "#app/global-scene"; +import type { PartyOption } from "#app/ui/party-ui-handler"; +import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; +import { Mode } from "#app/ui/ui"; +import i18next from "i18next"; +import * as Utils from "#app/utils"; +import { BattlePhase } from "#app/phases/battle-phase"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; +import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; +import type { PlayerPokemon } from "#app/field/pokemon"; + +/** + * Sets the Party UI and handles the effect of Revival Blessing + * when used by one of the player's Pokemon. + */ +export class RevivalBlessingPhase extends BattlePhase { + constructor(protected user: PlayerPokemon) { + super(); + } + + public override start(): void { + globalScene.ui.setMode( + Mode.PARTY, + PartyUiMode.REVIVAL_BLESSING, + this.user.getFieldIndex(), + (slotIndex: integer, option: PartyOption) => { + if (slotIndex >= 0 && slotIndex < 6) { + const pokemon = globalScene.getPlayerParty()[slotIndex]; + if (!pokemon || !pokemon.isFainted()) { + return this.end(); + } + + pokemon.resetTurnData(); + pokemon.resetStatus(); + pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); + globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true); + + if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) { + const allyPokemon = this.user.getAlly(); + if (slotIndex <= 1) { + // Revived ally pokemon + globalScene.unshiftPhase( + new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true), + ); + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + } else if (allyPokemon.isFainted()) { + // Revived party pokemon, and ally pokemon is fainted + globalScene.unshiftPhase( + new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true), + ); + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + } + } + } + globalScene.ui.setMode(Mode.MESSAGE).then(() => this.end()); + }, + PartyUiHandler.FilterFainted, + ); + } +}