diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f15cf1a52..9c23da9f2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,10 +1,13 @@ import Phaser from 'phaser'; -import UI, { Mode } from './ui/ui'; -import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from './phases'; +import UI from './ui/ui'; +import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, + ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from './phases'; import Pokemon, { PlayerPokemon, EnemyPokemon } from './field/pokemon'; -import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies, initSpecies, speciesStarters } from './data/pokemon-species'; +import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species'; import * as Utils from './utils'; -import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from './modifier/modifier'; +import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, + HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, + FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from './modifier/modifier'; import { PokeballType } from './data/pokeball'; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims'; import { Phase } from './phase'; @@ -12,15 +15,14 @@ import { initGameSpeed } from './system/game-speed'; import { Biome } from "./data/enums/biome"; import { Arena, ArenaBase } from './field/arena'; import { GameData, PlayerGender } from './system/game-data'; -import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import { TextStyle, addTextObject } from './ui/text'; import { Moves } from "./data/enums/moves"; import { allMoves } from "./data/move"; import { initMoves } from './data/move'; -import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from './modifier/modifier-type'; +import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, + getModifierPoolForType, getPartyLuckValue } from './modifier/modifier-type'; import AbilityBar from './ui/ability-bar'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; -import { Abilities } from "./data/enums/abilities"; import { allAbilities } from "./data/ability"; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; import { GameMode, GameModes, gameModes } from './game-mode'; @@ -33,9 +35,6 @@ import TrainerData from './system/trainer-data'; import SoundFade from 'phaser3-rex-plugins/plugins/soundfade'; import { pokemonPrevolutions } from './data/pokemon-evolutions'; import PokeballTray from './ui/pokeball-tray'; -import { Setting, settingOptions } from './system/settings'; -import SettingsUiHandler from './ui/settings-ui-handler'; -import MessageUiHandler from './ui/message-ui-handler'; import { Species } from './data/enums/species'; import InvertPostFX from './pipelines/invert'; import { Achv, ModifierAchv, MoneyAchv, achvs } from './system/achv'; @@ -1708,10 +1707,11 @@ export default class BattleScene extends SceneBase { } tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferStack: boolean, playSound: boolean, instant?: boolean, ignoreUpdate?: boolean): Promise { - return new Promise(resolve => { + return new Promise(async resolve => { const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const cancelled = new Utils.BooleanHolder(false); - Utils.executeIf(source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source, cancelled)).then(() => { + if (source && source.isPlayer() !== target.isPlayer()) { + await applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); if (cancelled.value) return resolve(false); const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; @@ -1732,12 +1732,14 @@ export default class BattleScene extends SceneBase { removeOld = !(--itemModifier.stackCount); } if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { - const addModifier = () => { + const addModifier = async () => { if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { - if (target.isPlayer()) - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => resolve(true)); - else - this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => resolve(true)); + if (target.isPlayer()) { + await this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); + } else { + await this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); + } + resolve(true); } else resolve(false); }; @@ -1748,7 +1750,7 @@ export default class BattleScene extends SceneBase { return; } resolve(false); - }); + } }); } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index a5d3993f5..07af474e7 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,13 +1,10 @@ -//import { battleAnimRawData } from "./battle-anim-raw-data"; import BattleScene from "../battle-scene"; import { AttackMove, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; import Pokemon from "../field/pokemon"; import * as Utils from "../utils"; import { BattlerIndex } from "../battle"; -import stringify, { Element } from "json-stable-stringify"; +import { Element } from "json-stable-stringify"; import { Moves } from "./enums/moves"; -import { getTypeRgb } from "./type"; -//import fs from 'vite-plugin-fs/browser'; export enum AnimFrameTarget { USER, @@ -711,14 +708,12 @@ export abstract class BattleAnim { return ret; } - play(scene: BattleScene, callback?: Function) { + async play(scene: BattleScene, skipAnimation: boolean = false) { const isOppAnim = this.isOppAnim(); const user = !isOppAnim ? this.user : this.target; const target = !isOppAnim ? this.target : this.user; - if (!target.isOnField()) { - if (callback) - callback(); + if (!scene.moveAnimations || skipAnimation) { return; } @@ -732,34 +727,31 @@ export abstract class BattleAnim { }; const spritePriorities: integer[] = []; - const cleanUpAndComplete = () => { - userSprite.setPosition(0, 0); - userSprite.setScale(1); - userSprite.setAlpha(1); - userSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; - userSprite.setAngle(0); - targetSprite.setPosition(0, 0); - targetSprite.setScale(1); - targetSprite.setAlpha(1); - targetSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; - targetSprite.setAngle(0); - if (!this.isHideUser()) - userSprite.setVisible(true); - if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) - targetSprite.setVisible(true); - for (let ms of Object.values(spriteCache).flat()) { - if (ms) - ms.destroy(); - } - if (this.bgSprite) - this.bgSprite.destroy(); - if (callback) - callback(); + const cleanUpAndComplete = (): Promise => { + return new Promise(() => { + userSprite.setPosition(0, 0); + userSprite.setScale(1); + userSprite.setAlpha(1); + userSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; + userSprite.setAngle(0); + targetSprite.setPosition(0, 0); + targetSprite.setScale(1); + targetSprite.setAlpha(1); + targetSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; + targetSprite.setAngle(0); + if (!this.isHideUser()) + userSprite.setVisible(true); + if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) + targetSprite.setVisible(true); + for (let ms of Object.values(spriteCache).flat()) { + if (ms) + ms.destroy(); + } + if (this.bgSprite) + this.bgSprite.destroy(); + }); }; - if (!scene.moveAnimations) - return cleanUpAndComplete(); - const anim = this.getAnim(); const userInitialX = user.x; @@ -913,7 +905,7 @@ export abstract class BattleAnim { f++; r--; }, - onComplete: () => { + onComplete: async () => { for (let ms of Object.values(spriteCache).flat()) { if (ms && !ms.getData('locked')) ms.destroy(); @@ -924,7 +916,7 @@ export abstract class BattleAnim { onComplete: () => cleanUpAndComplete() }); } else - cleanUpAndComplete(); + await cleanUpAndComplete(); } }); } diff --git a/src/data/move.ts b/src/data/move.ts index 46216eb75..4fcba0b52 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -12,7 +12,9 @@ import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; -import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr } from "./ability"; +import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, + IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, + PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr } from "./ability"; import { Abilities } from "./enums/abilities"; import { allAbilities } from './ability'; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -129,8 +131,8 @@ export default class Move implements Localizable { localize(): void { const i18nKey = Moves[this.id].split('_').filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join('') as unknown as string; - this.name = this.id ? `${i18next.t(`move:${i18nKey}.name`).toString()}${this.nameAppend}` : ''; - this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`).toString()}${this.nameAppend}` : ''; + this.name = this.id ? `${(i18next.t(`move:${i18nKey}.name`) as any).toString()}${this.nameAppend}` : ''; + this.effect = this.id ? `${(i18next.t(`move:${i18nKey}.effect`) as any).toString()}${this.nameAppend}` : ''; } getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] { @@ -514,6 +516,11 @@ export class MoveEffectAttr extends MoveAttr { public trigger: MoveEffectTrigger; public firstHitOnly: boolean; + /** + * @param selfTarget move targets self + * @param trigger when does the move effect happen + * @param firstHitOnly only happens upon first hit + */ constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false) { super(selfTarget); this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY; @@ -1599,19 +1606,18 @@ export class ChargeAttr extends OverrideMoveEffectAttr { const lastMove = user.getLastXMoves().find(() => true); if (!lastMove || lastMove.move !== move.id || (lastMove.result !== MoveResult.OTHER && (this.sameTurn || lastMove.turn !== user.scene.currentBattle.turn))) { (args[0] as Utils.BooleanHolder).value = true; - new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => { - user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); - if (this.tagType) - user.addTag(this.tagType, 1, move.id, user.id); - if (this.chargeEffect) - applyMoveAttrs(MoveEffectAttr, user, target, move); - user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); - if (this.sameTurn) - user.scene.pushMovePhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], user.moveset.find(m => m.moveId === move.id), true), this.followUpPriority); - user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); - resolve(true); - }); + new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene); + user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); + if (this.tagType) + user.addTag(this.tagType, 1, move.id, user.id); + if (this.chargeEffect) + applyMoveAttrs(MoveEffectAttr, user, target, move); + user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); + user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); + if (this.sameTurn) + user.scene.pushMovePhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], user.moveset.find(m => m.moveId === move.id), true), this.followUpPriority); + user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); + resolve(true); } else { user.lapseTag(BattlerTagType.CHARGING); resolve(false); @@ -1697,14 +1703,13 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { if (args.length < 2 || !args[1]) { - new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => { - (args[0] as Utils.BooleanHolder).value = true; - user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); - user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex()); + new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene); + (args[0] as Utils.BooleanHolder).value = true; + user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); + user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); + user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex()); - resolve(true); - }); + resolve(true); } else user.scene.ui.showText(getPokemonMessage(user.scene.getPokemonById(target.id), ` took\nthe ${move.name} attack!`), null, () => resolve(true)); }); @@ -1795,11 +1800,13 @@ export class PostVictoryStatChangeAttr extends MoveAttr { this.condition = condition || null; this.showMessage = showMessage; } - applyPostVictory(user: Pokemon, target: Pokemon, move: Move): void { + + applyPostVictory(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { if(this.condition && !this.condition(user, target, move)) return false; const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage); - statChangeAttr.apply(user, target, move); + statChangeAttr.apply(user, target, move, args); + return true; } } @@ -4250,15 +4257,12 @@ const failIfDampCondition: MoveConditionFunc = (user, target, move) => { export type MoveAttrFilter = (attr: MoveAttr) => boolean; function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const attrPromises: Promise[] = []; + return new Promise(async resolve => { const moveAttrs = move.attrs.filter(a => attrFilter(a)); for (let attr of moveAttrs) { - const result = attr.apply(user, target, move, args); - if (result instanceof Promise) - attrPromises.push(result); + await attr.apply(user, target, move, args); } - Promise.allSettled(attrPromises).then(() => resolve()); + resolve(); }); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index eb08af3da..6c1d3af62 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,13 +4,20 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr } from "../data/move"; -import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, + TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, + ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, + CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr } from "../data/move"; +import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, + getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; import { getLevelTotalExp } from '../data/exp'; import { Stat } from '../data/pokemon-stat'; -import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, TerastallizeModifier } from '../modifier/modifier'; +import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, + EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, + PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, + TempBattleStatBoosterModifier, TerastallizeModifier } from '../modifier/modifier'; import { PokeballType } from '../data/pokeball'; import { Gender } from '../data/gender'; import { initMoveAnim, loadMoveAnimAssets } from '../data/battle-anims'; @@ -27,10 +34,16 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr } from '../data/ability'; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, + FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, + ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, + VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, + applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, + DamageBoostAbAttr, + IgnoreTypeStatusEffectImmunityAbAttr} from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; -import Battle, { BattlerIndex } from '../battle'; +import { BattlerIndex } from '../battle'; import { BattleSpec } from "../enums/battle-spec"; import { Mode } from '../ui/ui'; import PartyUiHandler, { PartyOption, PartyUiMode } from '../ui/party-ui-handler'; @@ -954,6 +967,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; } + /** + * + * @param moveType + * @param source + * @returns TypeDamageMultiplier + */ getAttackTypeEffectiveness(moveType: Type, source?: Pokemon): TypeDamageMultiplier { if (moveType === Type.STELLAR) return this.isTerastallized() ? 2 : 1; @@ -1412,56 +1431,62 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; } - apply(source: Pokemon, battlerMove: PokemonMove): HitResult { + async apply(source: Pokemon, battlerMove: PokemonMove): Promise { let result: HitResult; const move = battlerMove.getMove(); - let damage = new Utils.NumberHolder(0); + let damage = new Utils.IntegerHolder(0); const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); - + + // check if move changes from special to physical const variableCategory = new Utils.IntegerHolder(move.category); - applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); + await applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); const moveCategory = variableCategory.value as MoveCategory; + // check if move changes type const variableType = new Utils.IntegerHolder(move.type); - const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); - applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); + const typeChangeMovePowerMultiplier = new Utils.IntegerHolder(1); + await applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr - applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); - applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier); + await applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); + await applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier); + const type = variableType.value as Type; const types = this.getTypes(true, true); - - const cancelled = new Utils.BooleanHolder(false); + // struggle const typeless = !!move.getAttrs(TypelessAttr).length; - const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).immuneType))) - ? this.getAttackTypeEffectiveness(type, source) - : 1); - applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); - if (typeless) - typeMultiplier.value = 1; + const typeMultiplier = new Utils.IntegerHolder(1); + + if (!typeless) { + const targetImmuneToStatusMove = move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).immuneType)); + typeMultiplier.value = (moveCategory !== MoveCategory.STATUS || targetImmuneToStatusMove) + ? this.getAttackTypeEffectiveness(type, source) + : 1; + await applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); + } if (types.find(t => move.isTypeImmune(t))) typeMultiplier.value = 0; - + + const cancelled = new Utils.BooleanHolder(false); switch (moveCategory) { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: const isPhysical = moveCategory === MoveCategory.PHYSICAL; - const power = new Utils.NumberHolder(move.power); + const power = new Utils.IntegerHolder(move.power); const sourceTeraType = source.getTeraType(); if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) power.value = 60; - applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); - this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power)); + await applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); + this.scene.getField(true).map(async (p: Pokemon) => await applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power)); - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power); + await applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power); power.value *= typeChangeMovePowerMultiplier.value; if (!typeless) - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); + await applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); + await applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); + defendingSidePlayField.forEach(async (p: PlayerPokemon | EnemyPokemon) => await applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); } if (cancelled.value) @@ -1474,11 +1499,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.removeTag(typeBoost.tagType); } } - const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); - applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); + const arenaAttackTypeMultiplier = new Utils.IntegerHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); + await applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) power.value /= 2; - applyMoveAttrs(VariablePowerAttr, source, this, move, power); + await applyMoveAttrs(VariablePowerAttr, source, this, move, power); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); if (!typeless) { this.scene.arena.applyTags(WeakenMoveTypeTag, type, power); @@ -1633,23 +1658,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (source.turnData.hitsLeft === 1) { - switch (result) { - case HitResult.SUPER_EFFECTIVE: - this.scene.queueMessage(i18next.t('battle:hitResultSuperEffective')); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.queueMessage(i18next.t('battle:hitResultNotVeryEffective')); - break; - case HitResult.NO_EFFECT: - this.scene.queueMessage(i18next.t('battle:hitResultNoEffect', { pokemonName: this.name })); - break; - case HitResult.IMMUNE: - this.scene.queueMessage(`${this.name} is unaffected!`); - break; - case HitResult.ONE_HIT_KO: - this.scene.queueMessage(i18next.t('battle:hitResultOneHitKO')); - break; - } + this.queueHitResultMessage(result); } if (damage) @@ -1664,7 +1673,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); } if (!typeMultiplier.value) - this.scene.queueMessage(i18next.t('battle:hitResultNoEffect', { pokemonName: this.name })); + this.queueHitResultMessage(HitResult.NO_EFFECT); result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; break; } @@ -1672,6 +1681,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return result; } + /** + * Prints the appropriate message for the effectiveness of a move on this pokemon + * @param result effectiveness of type HitResult + */ + private queueHitResultMessage(result: HitResult) { + switch (result) { + case HitResult.SUPER_EFFECTIVE: + this.scene.queueMessage(i18next.t('battle:hitResultSuperEffective')); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.queueMessage(i18next.t('battle:hitResultNotVeryEffective')); + break; + case HitResult.NO_EFFECT: + this.scene.queueMessage(i18next.t('battle:hitResultNoEffect', { pokemonName: this.name })); + break; + case HitResult.IMMUNE: + this.scene.queueMessage(`${this.name} is unaffected!`); + break; + case HitResult.ONE_HIT_KO: + this.scene.queueMessage(i18next.t('battle:hitResultOneHitKO')); + break; + } + } + damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false): integer { if (this.isFainted()) return 0; diff --git a/src/phases.ts b/src/phases.ts index e2755328c..ed38e557f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,12 +1,17 @@ -import BattleScene, { AnySound, bypassLogin, startingWave } from "./battle-scene"; +import BattleScene, { bypassLogin, startingWave } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import * as Utils from './utils'; import { Moves } from "./data/enums/moves"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, SacrificialAttr } from "./data/move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, + MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, + AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, + OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, MoveAttrFilter, + SacrificialAttr} from "./data/move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier } from "./modifier/modifier"; +import * as Modifiers from "./modifier/modifier"; +import { overrideModifiers, overrideHeldItems, Modifier } from "./modifier/modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; @@ -19,7 +24,9 @@ import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } fr import { biomeLinks, getBiomeName } from "./data/biomes"; import { Biome } from "./data/enums/biome"; import { ModifierTier } from "./modifier/modifier-tier"; -import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; +import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, + PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, + getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { BattlerTagLapseType, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags"; import { BattlerTagType } from "./data/enums/battler-tag-type"; @@ -30,7 +37,10 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr } from "./data/ability"; +import * as Ability from "./data/ability"; +import { applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostBattleInitAbAttrs, + applyPostDefendAbAttrs, applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostStatChangeAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostVictoryAbAttrs, + applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs }from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -45,7 +55,8 @@ import { vouchers } from "./system/voucher"; import { loggedInUser, updateUserInfo } from "./account"; import { PlayerGender, SessionSaveData } from "./system/game-data"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, + SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue } from "./data/dialogue"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; import { Setting } from "./system/settings"; @@ -55,7 +66,7 @@ import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; import { GameModes, gameModes } from "./game-mode"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./data/pokemon-species"; +import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; import i18next from './plugins/i18n'; import { Abilities } from "./data/enums/abilities"; import * as Overrides from './overrides'; @@ -717,7 +728,7 @@ export class EncounterPhase extends BattlePhase { if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) battle.enemyParty[e].ivs = new Array(6).fill(31); this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { - applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); + applyAbAttrs(Ability.SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); }); } } @@ -919,7 +930,7 @@ export class EncounterPhase extends BattlePhase { if (this.scene.currentBattle.battleType !== BattleType.TRAINER) { enemyField.map(p => this.scene.pushPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()))); - const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); + const ivScannerModifier = this.scene.findModifier(m => m instanceof Modifiers.IvScannerModifier); if (ivScannerModifier) enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); } @@ -928,7 +939,7 @@ export class EncounterPhase extends BattlePhase { const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()); if (availablePartyMembers[0].isOnField()) - applyPostBattleInitAbAttrs(PostBattleInitAbAttr, availablePartyMembers[0]); + applyPostBattleInitAbAttrs(Ability.PostBattleInitAbAttr, availablePartyMembers[0]); else this.scene.pushPhase(new SummonPhase(this.scene, 0)); @@ -936,7 +947,7 @@ export class EncounterPhase extends BattlePhase { if (availablePartyMembers.length > 1) { this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); if (availablePartyMembers[1].isOnField()) - applyPostBattleInitAbAttrs(PostBattleInitAbAttr, availablePartyMembers[1]); + applyPostBattleInitAbAttrs(Ability.PostBattleInitAbAttr, availablePartyMembers[1]); else this.scene.pushPhase(new SummonPhase(this.scene, 1)); } @@ -1025,7 +1036,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); for (let pokemon of this.scene.getParty().filter(p => p.isOnField())) - applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); + applyAbAttrs(Ability.PostBiomeChangeAbAttr, pokemon, null); const enemyField = this.scene.getEnemyField(); this.scene.tweens.add({ @@ -1051,7 +1062,7 @@ export class PostSummonPhase extends PokemonPhase { const pokemon = this.getPokemon(); this.scene.arena.applyTags(ArenaTrapTag, pokemon); - applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); + applyPostSummonAbAttrs(Ability.PostSummonAbAttr, pokemon).then(() => this.end()); } } @@ -1067,7 +1078,7 @@ export class SelectBiomePhase extends BattlePhase { const setNextBiome = (nextBiome: Biome) => { if (this.scene.currentBattle.waveIndex % 10 === 1) { - this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); + this.scene.applyModifiers(Modifiers.MoneyInterestModifier, true, this.scene); this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); } this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); @@ -1087,7 +1098,7 @@ export class SelectBiomePhase extends BattlePhase { .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .map(b => !Array.isArray(b) ? b : b[0]); }, this.scene.currentBattle.waveIndex); - if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { + if (biomes.length > 1 && this.scene.findModifier(m => m instanceof Modifiers.MapModifier)) { let biomeChoices: Biome[]; this.scene.executeWithSeedOffset(() => { biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) @@ -1388,7 +1399,7 @@ export class SwitchSummonPhase extends SummonPhase { if (!this.batonPass) (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); - applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, pokemon); + applyPreSwitchOutAbAttrs(Ability.PreSwitchOutAbAttr, pokemon); this.scene.ui.showText(this.player ? i18next.t('battle:playerComeBack', { pokemonName: pokemon.name }) : @@ -1420,10 +1431,10 @@ export class SwitchSummonPhase extends SummonPhase { this.lastPokemon = this.getPokemon(); if (this.batonPass && switchedPokemon) { (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id)); - if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) { - const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier - && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; - if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) + if (!this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) { + const batonPassModifier = this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier + && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as Modifiers.SwitchEffectTransferModifier; + if (batonPassModifier && !this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false); } } @@ -1800,7 +1811,7 @@ export class CommandPhase extends FieldPhase { const trapped = new Utils.BooleanHolder(false); const batonPass = isSwitch && args[0] as boolean; if (!batonPass) - enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped)); + enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(Ability.CheckTrappedAbAttr, enemyPokemon, trapped)); if (batonPass || (!trapTag && !trapped.value)) { this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } @@ -1898,7 +1909,7 @@ export class EnemyCommandPhase extends FieldPhase { const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; const trapped = new Utils.BooleanHolder(false); - opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped)); + opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(Ability.CheckTrappedAbAttr, playerPokemon, trapped)); if (!trapTag && !trapped.value) { const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); @@ -1974,7 +1985,7 @@ export class TurnStartPhase extends FieldPhase { this.scene.getField(true).filter(p => p.summonData).map(p => { const bypassSpeed = new Utils.BooleanHolder(false); - this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + this.scene.applyModifiers(Modifiers.BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); @@ -1996,8 +2007,8 @@ export class TurnStartPhase extends FieldPhase { const aPriority = new Utils.IntegerHolder(aMove.priority); const bPriority = new Utils.IntegerHolder(bMove.priority); - applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority); - applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority); + applyAbAttrs(Ability.IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority); + applyAbAttrs(Ability.IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority); if (aPriority.value !== bPriority.value) return aPriority.value < bPriority.value ? 1 : -1; @@ -2082,11 +2093,11 @@ export class TurnEndPhase extends FieldPhase { pokemon.summonData.disabledMove = Moves.NONE; } - const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer()); + const hasUsableBerry = !!this.scene.findModifier(m => m instanceof Modifiers.BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer()); if (hasUsableBerry) this.scene.unshiftPhase(new BerryPhase(this.scene, pokemon.getBattlerIndex())); - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + this.scene.applyModifiers(Modifiers.TurnHealModifier, pokemon.isPlayer(), pokemon); if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), @@ -2094,13 +2105,13 @@ export class TurnEndPhase extends FieldPhase { } if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + this.scene.applyModifiers(Modifiers.EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(Modifiers.EnemyStatusEffectHealChanceModifier, false, pokemon); } - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); + applyPostTurnAbAttrs(Ability.PostTurnAbAttr, pokemon); - this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + this.scene.applyModifiers(Modifiers.TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); pokemon.battleSummonData.turnCount++; }; @@ -2139,14 +2150,14 @@ export class BattleEndPhase extends BattlePhase { } for (let pokemon of this.scene.getParty().filter(p => !p.isFainted())) - applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + applyPostBattleAbAttrs(Ability.PostBattleAbAttr, pokemon); this.scene.clearEnemyHeldItemModifiers(); - const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + const lapsingModifiers = this.scene.findModifiers(m => m instanceof Modifiers.LapsingPersistentModifier || m instanceof Modifiers.LapsingPokemonHeldItemModifier) as (Modifiers.LapsingPersistentModifier | Modifiers.LapsingPokemonHeldItemModifier)[]; for (let m of lapsingModifiers) { const args: any[] = []; - if (m instanceof LapsingPokemonHeldItemModifier) + if (m instanceof Modifiers.LapsingPokemonHeldItemModifier) args.push(this.scene.getPokemonById(m.pokemonId)); if (!m.lapse(args)) this.scene.removeModifier(m); @@ -2177,10 +2188,9 @@ export class CommonAnimPhase extends PokemonPhase { this.targetIndex = targetIndex; } - start() { - new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene, () => { - this.end(); - }); + async start() { + await new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene); + this.end(); } } @@ -2244,12 +2254,12 @@ export class MovePhase extends BattlePhase { : null; if (moveTarget) { var oldTarget = moveTarget.value; - this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); + this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(Ability.RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); //Check if this move is immune to being redirected, and restore its target to the intended target if it is. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().getAttrs(BypassRedirectAttr).length)) { + if ((this.pokemon.hasAbilityWithAttr(Ability.BlockRedirectAbAttr) || this.move.getMove().getAttrs(BypassRedirectAttr).length)) { //If an ability prevented this move from being redirected, display its ability pop up. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().getAttrs(BypassRedirectAttr).length) && oldTarget != moveTarget.value) { - this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); + if ((this.pokemon.hasAbilityWithAttr(Ability.BlockRedirectAbAttr) && !this.move.getMove().getAttrs(BypassRedirectAttr).length) && oldTarget != moveTarget.value) { + this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(Ability.BlockRedirectAbAttr))); } moveTarget.value = oldTarget; } @@ -2272,7 +2282,7 @@ export class MovePhase extends BattlePhase { const targets = this.scene.getField(true).filter(p => { if (this.targets.indexOf(p.getBattlerIndex()) > -1) { const hiddenTag = p.getTag(HiddenTag); - if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length && !p.hasAbilityWithAttr(AlwaysHitAbAttr) && !this.pokemon.hasAbilityWithAttr(AlwaysHitAbAttr)) + if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length && !p.hasAbilityWithAttr(Ability.AlwaysHitAbAttr) && !this.pokemon.hasAbilityWithAttr(Ability.AlwaysHitAbAttr)) return false; return true; } @@ -2290,7 +2300,7 @@ export class MovePhase extends BattlePhase { for (let opponent of targetedOpponents) { if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) // If we're already at max PP usage, stop checking break; - if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) // Accounting for abilities like Pressure + if (opponent.hasAbilityWithAttr(Ability.IncreasePpAbAttr)) // Accounting for abilities like Pressure ppUsed++; } @@ -2435,7 +2445,7 @@ export class MoveEffectPhase extends PokemonPhase { this.targets = targets; } - start() { + async start() { super.start(); const user = this.getUserPokemon(); @@ -2447,118 +2457,130 @@ export class MoveEffectPhase extends PokemonPhase { const overridden = new Utils.BooleanHolder(false); // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => { + await applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual); - if (overridden.value) - return this.end(); - - user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + if (overridden.value) + return this.end(); + + user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - if (user.turnData.hitsLeft === undefined) { - const hitCount = new Utils.IntegerHolder(1); - // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); - if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) - this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); - user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; + if (user.turnData.hitsLeft === undefined) { + const hitCount = new Utils.IntegerHolder(1); + // Assume single target for multi hit + applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); + if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) + this.scene.applyModifiers(Modifiers.PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); + user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; + } + + const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; + user.pushMoveHistory(moveHistoryEntry); + + const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); + const activeTargets = targets.map(t => t.isActive(true)); + if (!activeTargets.length || (!this.move.getMove().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { + user.turnData.hitCount = 1; + user.turnData.hitsLeft = 1; + if (activeTargets.length) { + this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); + moveHistoryEntry.result = MoveResult.MISS; + applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); + } else { + this.scene.queueMessage(i18next.t('battle:attackFailed')); + moveHistoryEntry.result = MoveResult.FAIL; } + return this.end(); + } - const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; - user.pushMoveHistory(moveHistoryEntry); - const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); - const activeTargets = targets.map(t => t.isActive(true)); - if (!activeTargets.length || (!this.move.getMove().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { + const skipAnimation = ():boolean => { + const allProtected: boolean = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && targets.every(target => target.findTags(t => t instanceof ProtectedTag).length); + const allMiss: boolean = Object.keys(targetHitChecks).every(key => !targetHitChecks[key]); + // TODO: skip animation if all targets are immune + return allProtected || allMiss; + } + // Move animation only needs one target + new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, skipAnimation()); + + for (let target of targets) { + // missed target + if (!targetHitChecks[target.getBattlerIndex()]) { user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; - if (activeTargets.length) { - this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); + this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); + if (moveHistoryEntry.result === MoveResult.PENDING) moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); - } else { - this.scene.queueMessage(i18next.t('battle:attackFailed')); - moveHistoryEntry.result = MoveResult.FAIL; - } - return this.end(); + applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); + continue; } - const applyAttrs: Promise[] = []; + const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && !!target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); - // Move animation only needs one target - new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { - for (let target of targets) { - if (!targetHitChecks[target.getBattlerIndex()]) { - user.turnData.hitCount = 1; - user.turnData.hitsLeft = 1; - this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); - if (moveHistoryEntry.result === MoveResult.PENDING) - moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); - continue; - } + const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; - const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); + moveHistoryEntry.result = MoveResult.SUCCESS; + + // target.apply will apply damage/healing before returning hit result + const hitResult = !isProtected ? await target.apply(user, this.move) : HitResult.NO_EFFECT; - const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; + this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - moveHistoryEntry.result = MoveResult.SUCCESS; - - const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT; + await this.applyMoveEffectAttr(user, target, hitResult, firstHit, isProtected); + } + // Trigger effect which should only apply one time after all targeted effects have already applied + await applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET, user, null, this.move.getMove()); + this.end(); + } - this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - - applyAttrs.push(new Promise(resolve => { - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), - user, target, this.move.getMove()).then(() => { - if (hitResult !== HitResult.FAIL) { - const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove())); - // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => { - if (hitResult !== HitResult.NO_EFFECT) { - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { - if (hitResult < HitResult.NO_EFFECT) { - const flinched = new Utils.BooleanHolder(false); - user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); - if (flinched.value) - target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); - } - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), - user, target, this.move.getMove()).then(() => { - return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => { - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) - user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); - })).then(() => { - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult).then(() => { - if (this.move.getMove() instanceof AttackMove) - this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); - resolve(); - }); - }); - }) - ).then(() => resolve()); - }); - } else - applyMoveAttrs(NoEffectAttr, user, null, this.move.getMove()).then(() => resolve()); - }); - } else - resolve(); - }); - })); + private async applyMoveEffectAttr(user: Pokemon, target: Pokemon, hitResult: HitResult, firstHit: boolean, isProtected: boolean): Promise { + await applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()); + if (hitResult !== HitResult.FAIL) { + const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove())); + // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present + if (!chargeEffect) { + /** + * @param isSelfTarget does the move target self + * @returns Move Attribute Filter + */ + const postApplyAttrFilter = (isSelfTarget: boolean): MoveAttrFilter => { + return ((attr: MoveAttr) => + attr instanceof MoveEffectAttr && + (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY && + (attr as MoveEffectAttr).selfTarget === isSelfTarget && + (!attr.firstHitOnly || firstHit) + ) } - // Trigger effect which should only apply one time after all targeted effects have already applied - const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET, - user, null, this.move.getMove()); - - 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); - - Promise.allSettled(applyAttrs).then(() => this.end()); - }); - }); + // self target POST_APPLY attributes + await applyFilteredMoveAttrs(postApplyAttrFilter(true), user, target, this.move.getMove()); + // immunity + if (hitResult !== HitResult.NO_EFFECT) { + // opponent target POST_APPLY attributes + await applyFilteredMoveAttrs(postApplyAttrFilter(false), user, target, this.move.getMove()) + //EFFECTIVE, SUPER_EFFECTIVE, NOT_VERY_EFFECTIVE, ONE_HIT_KO + if (hitResult < HitResult.NO_EFFECT) { + const flinched = new Utils.BooleanHolder(false); + user.scene.applyModifiers(Modifiers.FlinchChanceModifier, user.isPlayer(), user, flinched); + if (flinched.value) + target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); + } + if (!isProtected && !chargeEffect) { + // hit effects + await applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()); + // target survived + if (!target.isFainted() || target.canApplyAbility()) { + await applyPostDefendAbAttrs(Ability.PostDefendAbAttr, target, user, this.move, hitResult); + // endless tokens + if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) + user.scene.applyShuffledModifiers(this.scene, Modifiers.EnemyAttackStatusEffectChanceModifier, false, target); + await applyPostAttackAbAttrs(Ability.PostAttackAbAttr, user, target, this.move, hitResult) + // grip claw + if (this.move.getMove() instanceof AttackMove) + this.scene.applyModifiers(Modifiers.ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); + } + } + } + } + } } end() { @@ -2570,7 +2592,7 @@ export class MoveEffectPhase extends PokemonPhase { const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); if (hitsTotal > 1) this.scene.queueMessage(i18next.t('battle:attackHitsCount', { count: hitsTotal })); - this.scene.applyModifiers(HitHealModifier, this.player, user); + this.scene.applyModifiers(Modifiers.HitHealModifier, this.player, user); } } @@ -2587,7 +2609,7 @@ export class MoveEffectPhase extends PokemonPhase { if (user.turnData.hitsLeft < user.turnData.hitCount) return true; - if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) + if (user.hasAbilityWithAttr(Ability.AlwaysHitAbAttr) || target.hasAbilityWithAttr(Ability.AlwaysHitAbAttr)) return true; const hiddenTag = target.getTag(HiddenTag); @@ -2607,7 +2629,7 @@ export class MoveEffectPhase extends PokemonPhase { const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length; if (!isOhko) - user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + user.scene.applyModifiers(Modifiers.PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); if (this.scene.arena.weather?.weatherType === WeatherType.FOG) moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9); @@ -2617,10 +2639,10 @@ export class MoveEffectPhase extends PokemonPhase { const userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]); const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); - applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); - applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel); + applyAbAttrs(Ability.IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); + applyAbAttrs(Ability.IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel); applyMoveAttrs(IgnoreOpponentStatChangesAttr, user, target, this.move.getMove(), targetEvasionLevel); - this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel); + this.scene.applyModifiers(Modifiers.TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel); const rand = user.randSeedInt(100, 1); @@ -2631,10 +2653,10 @@ export class MoveEffectPhase extends PokemonPhase { : 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); } - applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, this.move.getMove()); + applyBattleStatMultiplierAbAttrs(Ability.BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, this.move.getMove()); const evasionMultiplier = new Utils.NumberHolder(1); - applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this.getTarget(), BattleStat.EVA, evasionMultiplier); + applyBattleStatMultiplierAbAttrs(Ability.BattleStatMultiplierAbAttr, this.getTarget(), BattleStat.EVA, evasionMultiplier); accuracyMultiplier.value /= evasionMultiplier.value; @@ -2702,13 +2724,12 @@ export class MoveAnimTestPhase extends BattlePhase { initMoveAnim(this.scene, moveId).then(() => { loadMoveAnimAssets(this.scene, [ moveId ], true) - .then(() => { - new MoveAnim(moveId, player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon(), (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).getBattlerIndex()).play(this.scene, () => { - if (player) - this.playMoveAnim(moveQueue, false); - else - this.playMoveAnim(moveQueue, true); - }); + .then(async () => { + await new MoveAnim(moveId, player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon(), (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).getBattlerIndex()).play(this.scene); + if (player) + this.playMoveAnim(moveQueue, false); + else + this.playMoveAnim(moveQueue, true); }); }); } @@ -2773,7 +2794,7 @@ export class StatChangePhase extends PokemonPhase { this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); if (!cancelled.value && !this.selfTarget && this.levels < 0) - applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); + applyPreStatChangeAbAttrs(Ability.ProtectStatAbAttr, this.getPokemon(), stat, cancelled); return !cancelled.value; }); @@ -2781,7 +2802,7 @@ export class StatChangePhase extends PokemonPhase { const levels = new Utils.IntegerHolder(this.levels); if (!this.ignoreAbilities) - applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); + applyAbAttrs(Ability.StatChangeMultiplierAbAttr, pokemon, null, levels); const battleStats = this.getPokemon().summonData.battleStats; const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]); @@ -2798,9 +2819,9 @@ export class StatChangePhase extends PokemonPhase { if (levels.value > 0 && this.canBeCopied) for (let opponent of pokemon.getOpponents()) - applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); + applyAbAttrs(Ability.StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); - applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); + applyPostStatChangeAbAttrs(Ability.PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); pokemon.updateInfo(); @@ -2922,19 +2943,19 @@ export class WeatherEffectPhase extends CommonAnimPhase { this.weather = weather; } - start() { + async start() { if (this.weather.isDamaging()) { const cancelled = new Utils.BooleanHolder(false); - this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); + this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(Ability.SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); if (!cancelled.value) { const inflictDamage = (pokemon: Pokemon) => { const cancelled = new Utils.BooleanHolder(false); - applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyPreWeatherEffectAbAttrs(Ability.PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); + applyAbAttrs(Ability.BlockNonDirectDamageAbAttr, pokemon, cancelled); if (cancelled.value) return; @@ -2954,7 +2975,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { } this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType), null, () => { - this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); + this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(Ability.PostWeatherLapseAbAttr, pokemon, this.weather)); super.start(); }); @@ -2976,19 +2997,18 @@ export class ObtainStatusEffectPhase extends PokemonPhase { this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect } - start() { + async start() { const pokemon = this.getPokemon(); if (!pokemon.status) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { if (this.cureTurn) pokemon.status.cureTurn = this.cureTurn; pokemon.updateInfo(true); - new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { - this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText))); - if (pokemon.status.isPostTurn()) - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); - this.end(); - }); + await new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene); + this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText))); + if (pokemon.status.isPostTurn()) + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); + this.end(); return; } } else if (pokemon.status.effect === this.statusEffect) @@ -3002,12 +3022,12 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { super(scene, battlerIndex); } - start() { + async start() { const pokemon = this.getPokemon(); if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { pokemon.status.incrementTurn(); const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs(Ability.BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); @@ -3027,9 +3047,9 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage)); pokemon.updateInfo(); } - new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); - } else - this.end(); + await new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene); + } + this.end(); } else this.end(); } @@ -3141,7 +3161,7 @@ export class DamagePhase extends PokemonPhase { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { this.scene.fadeOutBgm(Utils.fixedInt(2000), false); this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => { - this.scene.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true); + this.scene.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as Modifiers.PersistentModifier, false, true); pokemon.generateAndPopulateMoveset(1); this.scene.setFieldScale(0.75); this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); @@ -3177,7 +3197,7 @@ export class FaintPhase extends PokemonPhase { super.start(); if (!this.preventEndure) { - const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; + const instantReviveModifier = this.scene.applyModifier(Modifiers.PokemonInstantReviveModifier, this.player, this.getPokemon()) as Modifiers.PokemonInstantReviveModifier; if (instantReviveModifier) { if (!--instantReviveModifier.stackCount) @@ -3198,22 +3218,20 @@ export class FaintPhase extends PokemonPhase { if (pokemon.turnData?.attacksReceived?.length) { const lastAttack = pokemon.turnData.attacksReceived[0]; - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result); + applyPostFaintAbAttrs(Ability.PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result); } const alivePlayField = this.scene.getField(true); - alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); + alivePlayField.forEach(p => applyPostKnockOutAbAttrs(Ability.PostKnockOutAbAttr, p, pokemon)); if (pokemon.turnData?.attacksReceived?.length) { const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); if (defeatSource?.isOnField()) { - applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); - const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; - const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); - if (pvattrs.length) { - for (let pvattr of pvattrs) { - pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); - } - } + applyPostVictoryAbAttrs(Ability.PostVictoryAbAttr, defeatSource); + const pvMove = allMoves[pokemon.turnData.attacksReceived[0].move]; + const pvAttrs = pvMove.getAttrs(PostVictoryStatChangeAttr); + pvAttrs.forEach((pvAttr: PostVictoryStatChangeAttr) => { + pvAttr.applyPostVictory(defeatSource, defeatSource, pvMove); + }) } } @@ -3310,9 +3328,9 @@ export class VictoryPhase extends PokemonPhase { const participantIds = this.scene.currentBattle.playerParticipantIds; const party = this.scene.getParty(); - const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; - const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; + const expShareModifier = this.scene.findModifier(m => m instanceof Modifiers.ExpShareModifier) as Modifiers.ExpShareModifier; + const expBalanceModifier = this.scene.findModifier(m => m instanceof Modifiers.ExpBalanceModifier) as Modifiers.ExpBalanceModifier; + const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof Modifiers.MultipleParticipantExpBonusModifier) as Modifiers.MultipleParticipantExpBonusModifier; const nonFaintedPartyMembers = party.filter(p => p.hp); const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); const partyMemberExp = []; @@ -3342,7 +3360,7 @@ export class VictoryPhase extends PokemonPhase { if (partyMember.pokerus) expMultiplier *= 1.5; const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); - this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + this.scene.applyModifiers(Modifiers.PokemonExpBoosterModifier, true, partyMember, pokemonExp); partyMemberExp.push(Math.floor(pokemonExp.value)); } @@ -3481,7 +3499,7 @@ export class MoneyRewardPhase extends BattlePhase { start() { const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); - this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + this.scene.applyModifiers(Modifiers.MoneyMultiplierModifier, true, moneyAmount); this.scene.addMoney(moneyAmount.value); @@ -3776,7 +3794,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); let exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); + this.scene.applyModifiers(Modifiers.ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); this.scene.ui.showText(i18next.t('battle:expGain', { pokemonName: pokemon.name, exp: exp.value }), null, () => { const lastLevel = pokemon.level; @@ -3804,7 +3822,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); let exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); + this.scene.applyModifiers(Modifiers.ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); const lastLevel = pokemon.level; @@ -3994,16 +4012,16 @@ export class BerryPhase extends CommonAnimPhase { } start() { - let berryModifiers: BerryModifier[]; + let berryModifiers: Modifiers.BerryModifier[]; const pokemon = this.getPokemon(); const cancelled = new Utils.BooleanHolder(false); - pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); + pokemon.getOpponents().map(opp => applyAbAttrs(Ability.PreventBerryUseAbAttr, opp, cancelled)); if (cancelled.value) pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is too\nnervous to eat berries!')); - else if ((berryModifiers = this.scene.applyModifiers(BerryModifier, this.player, pokemon) as BerryModifier[])) { + else if ((berryModifiers = this.scene.applyModifiers(Modifiers.BerryModifier, this.player, pokemon) as Modifiers.BerryModifier[])) { for (let berryModifier of berryModifiers) { if (berryModifier.consumed) { if (!--berryModifier.stackCount) @@ -4041,7 +4059,7 @@ export class PokemonHealPhase extends CommonAnimPhase { this.preventFullHeal = preventFullHeal; } - start() { + async start() { if (!this.skipAnim && (this.revive || this.getPokemon().hp) && this.getPokemon().getHpRatio() < 1) super.start(); else @@ -4064,10 +4082,10 @@ export class PokemonHealPhase extends CommonAnimPhase { if (!fullHp || this.hpHealed < 0) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); if (!this.revive) - this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); + this.scene.applyModifiers(Modifiers.HealingBoosterModifier, this.player, hpRestoreMultiplier); const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (healAmount.value < 0) { - pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL); + pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult); healAmount.value = 0; } // Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) @@ -4302,7 +4320,7 @@ export class AttemptCapturePhase extends PokemonPhase { }; const addToParty = () => { const newPokemon = pokemon.addToParty(this.pokeballType); - const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + const modifiers = this.scene.findModifiers(m => m instanceof Modifiers.PokemonHeldItemModifier, false); if (this.scene.getParty().filter(p => p.isShiny()).length === 6) this.scene.validateAchv(achvs.SHINY_PARTY); Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { @@ -4368,7 +4386,7 @@ export class AttemptRunPhase extends PokemonPhase { const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); - applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); + applyAbAttrs(Ability.RunSuccessAbAttr, playerPokemon, null, escapeChance); if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound('flee'); @@ -4420,7 +4438,7 @@ export class SelectModifierPhase extends BattlePhase { regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) - this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + this.scene.applyModifiers(Modifiers.ExtraModifierModifier, true, modifierCount); const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { @@ -4455,8 +4473,8 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, toSlotIndex: integer) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)).then(() => { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + const itemModifiers = this.scene.findModifiers(m => m instanceof Modifiers.PokemonHeldItemModifier + && (m as Modifiers.PokemonHeldItemModifier).getTransferrable(true) && (m as Modifiers.PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as Modifiers.PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, true); }); @@ -4629,7 +4647,7 @@ export class AddEnemyBuffModifierPhase extends Phase { const count = Math.ceil(waveIndex / 250); for (let i = 0; i < count; i++) - this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); + this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof Modifiers.EnemyPersistentModifier, false), this.scene), true, true); this.scene.updateModifiers(false, true).then(() => this.end()); } } @@ -4741,12 +4759,11 @@ export class ScanIvsPhase extends PokemonPhase { const pokemon = this.getPokemon(); this.scene.ui.showText(i18next.t('battle:ivScannerUseQuestion', { pokemonName: pokemon.name }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.CONFIRM, async () => { this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.clearText(); - new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { - this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); - }); + await new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene); + this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); }, () => { this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.clearText();