Compare commits

...

1 Commits

Author SHA1 Message Date
Matthew Olker 89b1dde5ee Initial cleanup 2024-05-15 14:06:50 -04:00
5 changed files with 395 additions and 347 deletions

View File

@ -1,10 +1,13 @@
import Phaser from 'phaser'; import Phaser from 'phaser';
import UI, { Mode } from './ui/ui'; import UI from './ui/ui';
import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from './phases'; import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase,
ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from './phases';
import Pokemon, { PlayerPokemon, EnemyPokemon } from './field/pokemon'; 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 * 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 { PokeballType } from './data/pokeball';
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims'; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims';
import { Phase } from './phase'; import { Phase } from './phase';
@ -12,15 +15,14 @@ import { initGameSpeed } from './system/game-speed';
import { Biome } from "./data/enums/biome"; import { Biome } from "./data/enums/biome";
import { Arena, ArenaBase } from './field/arena'; import { Arena, ArenaBase } from './field/arena';
import { GameData, PlayerGender } from './system/game-data'; import { GameData, PlayerGender } from './system/game-data';
import StarterSelectUiHandler from './ui/starter-select-ui-handler';
import { TextStyle, addTextObject } from './ui/text'; import { TextStyle, addTextObject } from './ui/text';
import { Moves } from "./data/enums/moves"; import { Moves } from "./data/enums/moves";
import { allMoves } from "./data/move"; import { allMoves } from "./data/move";
import { initMoves } 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 AbilityBar from './ui/ability-bar';
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability';
import { Abilities } from "./data/enums/abilities";
import { allAbilities } from "./data/ability"; import { allAbilities } from "./data/ability";
import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle';
import { GameMode, GameModes, gameModes } from './game-mode'; 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 SoundFade from 'phaser3-rex-plugins/plugins/soundfade';
import { pokemonPrevolutions } from './data/pokemon-evolutions'; import { pokemonPrevolutions } from './data/pokemon-evolutions';
import PokeballTray from './ui/pokeball-tray'; 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 { Species } from './data/enums/species';
import InvertPostFX from './pipelines/invert'; import InvertPostFX from './pipelines/invert';
import { Achv, ModifierAchv, MoneyAchv, achvs } from './system/achv'; 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<boolean> { tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferStack: boolean, playSound: boolean, instant?: boolean, ignoreUpdate?: boolean): Promise<boolean> {
return new Promise(resolve => { return new Promise(async resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null;
const cancelled = new Utils.BooleanHolder(false); 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) if (cancelled.value)
return resolve(false); return resolve(false);
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier;
@ -1732,12 +1732,14 @@ export default class BattleScene extends SceneBase {
removeOld = !(--itemModifier.stackCount); removeOld = !(--itemModifier.stackCount);
} }
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) {
const addModifier = () => { const addModifier = async () => {
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
if (target.isPlayer()) if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => resolve(true)); await this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
else } else {
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => resolve(true)); await this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
}
resolve(true);
} else } else
resolve(false); resolve(false);
}; };
@ -1748,7 +1750,7 @@ export default class BattleScene extends SceneBase {
return; return;
} }
resolve(false); resolve(false);
}); }
}); });
} }

View File

@ -1,13 +1,10 @@
//import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { AttackMove, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; import { AttackMove, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import Pokemon from "../field/pokemon"; import Pokemon from "../field/pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import stringify, { Element } from "json-stable-stringify"; import { Element } from "json-stable-stringify";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { getTypeRgb } from "./type";
//import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget { export enum AnimFrameTarget {
USER, USER,
@ -711,14 +708,12 @@ export abstract class BattleAnim {
return ret; return ret;
} }
play(scene: BattleScene, callback?: Function) { async play(scene: BattleScene, skipAnimation: boolean = false) {
const isOppAnim = this.isOppAnim(); const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user : this.target; const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user; const target = !isOppAnim ? this.target : this.user;
if (!target.isOnField()) { if (!scene.moveAnimations || skipAnimation) {
if (callback)
callback();
return; return;
} }
@ -732,34 +727,31 @@ export abstract class BattleAnim {
}; };
const spritePriorities: integer[] = []; const spritePriorities: integer[] = [];
const cleanUpAndComplete = () => { const cleanUpAndComplete = (): Promise<any> => {
userSprite.setPosition(0, 0); return new Promise(() => {
userSprite.setScale(1); userSprite.setPosition(0, 0);
userSprite.setAlpha(1); userSprite.setScale(1);
userSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; userSprite.setAlpha(1);
userSprite.setAngle(0); userSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ];
targetSprite.setPosition(0, 0); userSprite.setAngle(0);
targetSprite.setScale(1); targetSprite.setPosition(0, 0);
targetSprite.setAlpha(1); targetSprite.setScale(1);
targetSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ]; targetSprite.setAlpha(1);
targetSprite.setAngle(0); targetSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ];
if (!this.isHideUser()) targetSprite.setAngle(0);
userSprite.setVisible(true); if (!this.isHideUser())
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) userSprite.setVisible(true);
targetSprite.setVisible(true); if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser()))
for (let ms of Object.values(spriteCache).flat()) { targetSprite.setVisible(true);
if (ms) for (let ms of Object.values(spriteCache).flat()) {
ms.destroy(); if (ms)
} ms.destroy();
if (this.bgSprite) }
this.bgSprite.destroy(); if (this.bgSprite)
if (callback) this.bgSprite.destroy();
callback(); });
}; };
if (!scene.moveAnimations)
return cleanUpAndComplete();
const anim = this.getAnim(); const anim = this.getAnim();
const userInitialX = user.x; const userInitialX = user.x;
@ -913,7 +905,7 @@ export abstract class BattleAnim {
f++; f++;
r--; r--;
}, },
onComplete: () => { onComplete: async () => {
for (let ms of Object.values(spriteCache).flat()) { for (let ms of Object.values(spriteCache).flat()) {
if (ms && !ms.getData('locked')) if (ms && !ms.getData('locked'))
ms.destroy(); ms.destroy();
@ -924,7 +916,7 @@ export abstract class BattleAnim {
onComplete: () => cleanUpAndComplete() onComplete: () => cleanUpAndComplete()
}); });
} else } else
cleanUpAndComplete(); await cleanUpAndComplete();
} }
}); });
} }

View File

@ -12,7 +12,9 @@ import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type"; 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 { Abilities } from "./enums/abilities";
import { allAbilities } from './ability'; import { allAbilities } from './ability';
import { PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
@ -129,8 +131,8 @@ export default class Move implements Localizable {
localize(): void { 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; 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.name = this.id ? `${(i18next.t(`move:${i18nKey}.name`) as any).toString()}${this.nameAppend}` : '';
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`).toString()}${this.nameAppend}` : ''; this.effect = this.id ? `${(i18next.t(`move:${i18nKey}.effect`) as any).toString()}${this.nameAppend}` : '';
} }
getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] { getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] {
@ -514,6 +516,11 @@ export class MoveEffectAttr extends MoveAttr {
public trigger: MoveEffectTrigger; public trigger: MoveEffectTrigger;
public firstHitOnly: boolean; 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) { constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false) {
super(selfTarget); super(selfTarget);
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY; this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
@ -1599,19 +1606,18 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
const lastMove = user.getLastXMoves().find(() => true); const lastMove = user.getLastXMoves().find(() => true);
if (!lastMove || lastMove.move !== move.id || (lastMove.result !== MoveResult.OTHER && (this.sameTurn || lastMove.turn !== user.scene.currentBattle.turn))) { 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; (args[0] as Utils.BooleanHolder).value = true;
new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => { new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene);
user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`));
if (this.tagType) if (this.tagType)
user.addTag(this.tagType, 1, move.id, user.id); user.addTag(this.tagType, 1, move.id, user.id);
if (this.chargeEffect) if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, user, target, move); applyMoveAttrs(MoveEffectAttr, user, target, move);
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
if (this.sameTurn) 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.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); user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
resolve(true); resolve(true);
});
} else { } else {
user.lapseTag(BattlerTagType.CHARGING); user.lapseTag(BattlerTagType.CHARGING);
resolve(false); resolve(false);
@ -1697,14 +1703,13 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
if (args.length < 2 || !args[1]) { if (args.length < 2 || !args[1]) {
new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => { new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene);
(args[0] as Utils.BooleanHolder).value = true; (args[0] as Utils.BooleanHolder).value = true;
user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`));
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); 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()); user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex());
resolve(true); resolve(true);
});
} else } else
user.scene.ui.showText(getPokemonMessage(user.scene.getPokemonById(target.id), ` took\nthe ${move.name} attack!`), null, () => resolve(true)); 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.condition = condition || null;
this.showMessage = showMessage; 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)) if(this.condition && !this.condition(user, target, move))
return false; return false;
const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage); 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; export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> { function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> {
return new Promise(resolve => { return new Promise(async resolve => {
const attrPromises: Promise<boolean>[] = [];
const moveAttrs = move.attrs.filter(a => attrFilter(a)); const moveAttrs = move.attrs.filter(a => attrFilter(a));
for (let attr of moveAttrs) { for (let attr of moveAttrs) {
const result = attr.apply(user, target, move, args); await attr.apply(user, target, move, args);
if (result instanceof Promise)
attrPromises.push(result);
} }
Promise.allSettled(attrPromises).then(() => resolve()); resolve();
}); });
} }

View File

@ -4,13 +4,20 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant';
import { variantData } from '#app/data/variant'; import { variantData } from '#app/data/variant';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info';
import { Moves } from "../data/enums/moves"; 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 Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory,
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; 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 * as Utils from '../utils';
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type';
import { getLevelTotalExp } from '../data/exp'; import { getLevelTotalExp } from '../data/exp';
import { Stat } from '../data/pokemon-stat'; 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 { PokeballType } from '../data/pokeball';
import { Gender } from '../data/gender'; import { Gender } from '../data/gender';
import { initMoveAnim, loadMoveAnimAssets } from '../data/battle-anims'; 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 { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag';
import { ArenaTagType } from "../data/enums/arena-tag-type"; import { ArenaTagType } from "../data/enums/arena-tag-type";
import { Biome } from "../data/enums/biome"; 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 { Abilities } from "#app/data/enums/abilities";
import PokemonData from '../system/pokemon-data'; import PokemonData from '../system/pokemon-data';
import Battle, { BattlerIndex } from '../battle'; import { BattlerIndex } from '../battle';
import { BattleSpec } from "../enums/battle-spec"; import { BattleSpec } from "../enums/battle-spec";
import { Mode } from '../ui/ui'; import { Mode } from '../ui/ui';
import PartyUiHandler, { PartyOption, PartyUiMode } from '../ui/party-ui-handler'; 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; return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier;
} }
/**
*
* @param moveType
* @param source
* @returns TypeDamageMultiplier
*/
getAttackTypeEffectiveness(moveType: Type, source?: Pokemon): TypeDamageMultiplier { getAttackTypeEffectiveness(moveType: Type, source?: Pokemon): TypeDamageMultiplier {
if (moveType === Type.STELLAR) if (moveType === Type.STELLAR)
return this.isTerastallized() ? 2 : 1; 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]; 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<HitResult> {
let result: HitResult; let result: HitResult;
const move = battlerMove.getMove(); 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(); 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); 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; const moveCategory = variableCategory.value as MoveCategory;
// check if move changes type
const variableType = new Utils.IntegerHolder(move.type); const variableType = new Utils.IntegerHolder(move.type);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); const typeChangeMovePowerMultiplier = new Utils.IntegerHolder(1);
applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); await applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType);
// 2nd argument is for MoveTypeChangePowerMultiplierAbAttr // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr
applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); await applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier); await applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier);
const type = variableType.value as Type; const type = variableType.value as Type;
const types = this.getTypes(true, true); const types = this.getTypes(true, true);
// struggle
const cancelled = new Utils.BooleanHolder(false);
const typeless = !!move.getAttrs(TypelessAttr).length; 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))) const typeMultiplier = new Utils.IntegerHolder(1);
? this.getAttackTypeEffectiveness(type, source)
: 1); if (!typeless) {
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); const targetImmuneToStatusMove = move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).immuneType));
if (typeless) typeMultiplier.value = (moveCategory !== MoveCategory.STATUS || targetImmuneToStatusMove)
typeMultiplier.value = 1; ? this.getAttackTypeEffectiveness(type, source)
: 1;
await applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
}
if (types.find(t => move.isTypeImmune(t))) if (types.find(t => move.isTypeImmune(t)))
typeMultiplier.value = 0; typeMultiplier.value = 0;
const cancelled = new Utils.BooleanHolder(false);
switch (moveCategory) { switch (moveCategory) {
case MoveCategory.PHYSICAL: case MoveCategory.PHYSICAL:
case MoveCategory.SPECIAL: case MoveCategory.SPECIAL:
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power); const power = new Utils.IntegerHolder(move.power);
const sourceTeraType = source.getTeraType(); 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)) 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; power.value = 60;
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); await applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, 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; power.value *= typeChangeMovePowerMultiplier.value;
if (!typeless) if (!typeless)
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); await applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
if (!cancelled.value) { if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); await applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); defendingSidePlayField.forEach(async (p: PlayerPokemon | EnemyPokemon) => await applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
} }
if (cancelled.value) if (cancelled.value)
@ -1474,11 +1499,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
source.removeTag(typeBoost.tagType); source.removeTag(typeBoost.tagType);
} }
} }
const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); const arenaAttackTypeMultiplier = new Utils.IntegerHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()));
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); 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) if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS)
power.value /= 2; 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); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!typeless) { if (!typeless) {
this.scene.arena.applyTags(WeakenMoveTypeTag, type, power); 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) { if (source.turnData.hitsLeft === 1) {
switch (result) { this.queueHitResultMessage(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;
}
} }
if (damage) 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)); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
} }
if (!typeMultiplier.value) 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; result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS;
break; break;
} }
@ -1672,6 +1681,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return result; 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 { damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false): integer {
if (this.isFainted()) if (this.isFainted())
return 0; return 0;

View File

@ -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 { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from './utils'; import * as Utils from './utils';
import { Moves } from "./data/enums/moves"; 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 { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; 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 PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; 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 { biomeLinks, getBiomeName } from "./data/biomes";
import { Biome } from "./data/enums/biome"; import { Biome } from "./data/enums/biome";
import { ModifierTier } from "./modifier/modifier-tier"; 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 SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { BattlerTagLapseType, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags"; import { BattlerTagLapseType, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags";
import { BattlerTagType } from "./data/enums/battler-tag-type"; 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 { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type"; 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -45,7 +55,8 @@ import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account"; import { loggedInUser, updateUserInfo } from "./account";
import { PlayerGender, SessionSaveData } from "./system/game-data"; import { PlayerGender, SessionSaveData } from "./system/game-data";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; 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 { battleSpecDialogue, getCharVariantFromDialogue } from "./data/dialogue";
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler";
import { Setting } from "./system/settings"; 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 { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler";
import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run";
import { GameModes, gameModes } from "./game-mode"; 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 i18next from './plugins/i18n';
import { Abilities } from "./data/enums/abilities"; import { Abilities } from "./data/enums/abilities";
import * as Overrides from './overrides'; import * as Overrides from './overrides';
@ -717,7 +728,7 @@ export class EncounterPhase extends BattlePhase {
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS)
battle.enemyParty[e].ivs = new Array(6).fill(31); battle.enemyParty[e].ivs = new Array(6).fill(31);
this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { 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) { if (this.scene.currentBattle.battleType !== BattleType.TRAINER) {
enemyField.map(p => this.scene.pushPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()))); 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) if (ivScannerModifier)
enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); 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()); const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted());
if (availablePartyMembers[0].isOnField()) if (availablePartyMembers[0].isOnField())
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, availablePartyMembers[0]); applyPostBattleInitAbAttrs(Ability.PostBattleInitAbAttr, availablePartyMembers[0]);
else else
this.scene.pushPhase(new SummonPhase(this.scene, 0)); this.scene.pushPhase(new SummonPhase(this.scene, 0));
@ -936,7 +947,7 @@ export class EncounterPhase extends BattlePhase {
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true));
if (availablePartyMembers[1].isOnField()) if (availablePartyMembers[1].isOnField())
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, availablePartyMembers[1]); applyPostBattleInitAbAttrs(Ability.PostBattleInitAbAttr, availablePartyMembers[1]);
else else
this.scene.pushPhase(new SummonPhase(this.scene, 1)); 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); this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
for (let pokemon of this.scene.getParty().filter(p => p.isOnField())) 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(); const enemyField = this.scene.getEnemyField();
this.scene.tweens.add({ this.scene.tweens.add({
@ -1051,7 +1062,7 @@ export class PostSummonPhase extends PokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
this.scene.arena.applyTags(ArenaTrapTag, pokemon); 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) => { const setNextBiome = (nextBiome: Biome) => {
if (this.scene.currentBattle.waveIndex % 10 === 1) { 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 PartyHealPhase(this.scene, false));
} }
this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); 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])) .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
.map(b => !Array.isArray(b) ? b : b[0]); .map(b => !Array.isArray(b) ? b : b[0]);
}, this.scene.currentBattle.waveIndex); }, 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[]; let biomeChoices: Biome[];
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) biomeChoices = (!Array.isArray(biomeLinks[currentBiome])
@ -1388,7 +1399,7 @@ export class SwitchSummonPhase extends SummonPhase {
if (!this.batonPass) if (!this.batonPass)
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); (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 ? this.scene.ui.showText(this.player ?
i18next.t('battle:playerComeBack', { pokemonName: pokemon.name }) : i18next.t('battle:playerComeBack', { pokemonName: pokemon.name }) :
@ -1420,10 +1431,10 @@ export class SwitchSummonPhase extends SummonPhase {
this.lastPokemon = this.getPokemon(); this.lastPokemon = this.getPokemon();
if (this.batonPass && switchedPokemon) { if (this.batonPass && switchedPokemon) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id)); (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)) { if (!this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) {
const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier const batonPassModifier = this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier
&& (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as Modifiers.SwitchEffectTransferModifier;
if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) if (batonPassModifier && !this.scene.findModifier(m => m instanceof Modifiers.SwitchEffectTransferModifier && (m as Modifiers.SwitchEffectTransferModifier).pokemonId === switchedPokemon.id))
this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false); this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false);
} }
} }
@ -1800,7 +1811,7 @@ export class CommandPhase extends FieldPhase {
const trapped = new Utils.BooleanHolder(false); const trapped = new Utils.BooleanHolder(false);
const batonPass = isSwitch && args[0] as boolean; const batonPass = isSwitch && args[0] as boolean;
if (!batonPass) if (!batonPass)
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped)); enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(Ability.CheckTrappedAbAttr, enemyPokemon, trapped));
if (batonPass || (!trapTag && !trapped.value)) { if (batonPass || (!trapTag && !trapped.value)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args } ? { 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 trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
const trapped = new Utils.BooleanHolder(false); 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) { if (!trapTag && !trapped.value) {
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); 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 => { this.scene.getField(true).filter(p => p.summonData).map(p => {
const bypassSpeed = new Utils.BooleanHolder(false); 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; battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
}); });
@ -1996,8 +2007,8 @@ export class TurnStartPhase extends FieldPhase {
const aPriority = new Utils.IntegerHolder(aMove.priority); const aPriority = new Utils.IntegerHolder(aMove.priority);
const bPriority = new Utils.IntegerHolder(bMove.priority); const bPriority = new Utils.IntegerHolder(bMove.priority);
applyAbAttrs(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() === 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() === b), null, bMove, bPriority);
if (aPriority.value !== bPriority.value) if (aPriority.value !== bPriority.value)
return aPriority.value < bPriority.value ? 1 : -1; return aPriority.value < bPriority.value ? 1 : -1;
@ -2082,11 +2093,11 @@ export class TurnEndPhase extends FieldPhase {
pokemon.summonData.disabledMove = Moves.NONE; 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) if (hasUsableBerry)
this.scene.unshiftPhase(new BerryPhase(this.scene, pokemon.getBattlerIndex())); 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()) { if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
@ -2094,13 +2105,13 @@ export class TurnEndPhase extends FieldPhase {
} }
if (!pokemon.isPlayer()) { if (!pokemon.isPlayer()) {
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); this.scene.applyModifiers(Modifiers.EnemyTurnHealModifier, false, pokemon);
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, 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++; pokemon.battleSummonData.turnCount++;
}; };
@ -2139,14 +2150,14 @@ export class BattleEndPhase extends BattlePhase {
} }
for (let pokemon of this.scene.getParty().filter(p => !p.isFainted())) for (let pokemon of this.scene.getParty().filter(p => !p.isFainted()))
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); applyPostBattleAbAttrs(Ability.PostBattleAbAttr, pokemon);
this.scene.clearEnemyHeldItemModifiers(); 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) { for (let m of lapsingModifiers) {
const args: any[] = []; const args: any[] = [];
if (m instanceof LapsingPokemonHeldItemModifier) if (m instanceof Modifiers.LapsingPokemonHeldItemModifier)
args.push(this.scene.getPokemonById(m.pokemonId)); args.push(this.scene.getPokemonById(m.pokemonId));
if (!m.lapse(args)) if (!m.lapse(args))
this.scene.removeModifier(m); this.scene.removeModifier(m);
@ -2177,10 +2188,9 @@ export class CommonAnimPhase extends PokemonPhase {
this.targetIndex = targetIndex; this.targetIndex = targetIndex;
} }
start() { async 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, () => { 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(); this.end();
});
} }
} }
@ -2244,12 +2254,12 @@ export class MovePhase extends BattlePhase {
: null; : null;
if (moveTarget) { if (moveTarget) {
var oldTarget = moveTarget.value; 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. //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 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) { 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(BlockRedirectAbAttr))); this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(Ability.BlockRedirectAbAttr)));
} }
moveTarget.value = oldTarget; moveTarget.value = oldTarget;
} }
@ -2272,7 +2282,7 @@ export class MovePhase extends BattlePhase {
const targets = this.scene.getField(true).filter(p => { const targets = this.scene.getField(true).filter(p => {
if (this.targets.indexOf(p.getBattlerIndex()) > -1) { if (this.targets.indexOf(p.getBattlerIndex()) > -1) {
const hiddenTag = p.getTag(HiddenTag); 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 false;
return true; return true;
} }
@ -2290,7 +2300,7 @@ export class MovePhase extends BattlePhase {
for (let opponent of targetedOpponents) { for (let opponent of targetedOpponents) {
if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) // If we're already at max PP usage, stop checking if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) // If we're already at max PP usage, stop checking
break; break;
if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) // Accounting for abilities like Pressure if (opponent.hasAbilityWithAttr(Ability.IncreasePpAbAttr)) // Accounting for abilities like Pressure
ppUsed++; ppUsed++;
} }
@ -2435,7 +2445,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.targets = targets; this.targets = targets;
} }
start() { async start() {
super.start(); super.start();
const user = this.getUserPokemon(); const user = this.getUserPokemon();
@ -2447,118 +2457,130 @@ export class MoveEffectPhase extends PokemonPhase {
const overridden = new Utils.BooleanHolder(false); const overridden = new Utils.BooleanHolder(false);
// Assume single target for override // 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) if (overridden.value)
return this.end(); return this.end();
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
if (user.turnData.hitsLeft === undefined) { if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount);
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length)
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); this.scene.applyModifiers(Modifiers.PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; 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 skipAnimation = ():boolean => {
const activeTargets = targets.map(t => t.isActive(true)); const allProtected: boolean = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && targets.every(target => target.findTags(t => t instanceof ProtectedTag).length);
if (!activeTargets.length || (!this.move.getMove().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { 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.hitCount = 1;
user.turnData.hitsLeft = 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; moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
} else { continue;
this.scene.queueMessage(i18next.t('battle:attackFailed'));
moveHistoryEntry.result = MoveResult.FAIL;
}
return this.end();
} }
const applyAttrs: Promise<void>[] = []; 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 const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
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 isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); moveHistoryEntry.result = MoveResult.SUCCESS;
const firstHit = 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;
moveHistoryEntry.result = MoveResult.SUCCESS; this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
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); private async applyMoveEffectAttr(user: Pokemon, target: Pokemon, hitResult: HitResult, firstHit: boolean, isProtected: boolean): Promise<void> {
await applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove());
applyAttrs.push(new Promise(resolve => { if (hitResult !== HitResult.FAIL) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove()));
user, target, this.move.getMove()).then(() => { // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
if (hitResult !== HitResult.FAIL) { if (!chargeEffect) {
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 * @param isSelfTarget does the move target self
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY * @returns Move Attribute Filter
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => { */
if (hitResult !== HitResult.NO_EFFECT) { const postApplyAttrFilter = (isSelfTarget: boolean): MoveAttrFilter => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY return ((attr: MoveAttr) =>
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { attr instanceof MoveEffectAttr &&
if (hitResult < HitResult.NO_EFFECT) { (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY &&
const flinched = new Utils.BooleanHolder(false); (attr as MoveEffectAttr).selfTarget === isSelfTarget &&
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); (!attr.firstHitOnly || firstHit)
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();
});
}));
} }
// Trigger effect which should only apply one time after all targeted effects have already applied // self target POST_APPLY attributes
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET, await applyFilteredMoveAttrs(postApplyAttrFilter(true), user, target, this.move.getMove());
user, null, this.move.getMove()); // immunity
if (hitResult !== HitResult.NO_EFFECT) {
if (applyAttrs.length) // If there is a pending asynchronous move effect, do this after // opponent target POST_APPLY attributes
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); await applyFilteredMoveAttrs(postApplyAttrFilter(false), user, target, this.move.getMove())
else // Otherwise, push a new asynchronous move effect //EFFECTIVE, SUPER_EFFECTIVE, NOT_VERY_EFFECTIVE, ONE_HIT_KO
applyAttrs.push(postTarget); if (hitResult < HitResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false);
Promise.allSettled(applyAttrs).then(() => this.end()); 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() { end() {
@ -2570,7 +2592,7 @@ export class MoveEffectPhase extends PokemonPhase {
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
if (hitsTotal > 1) if (hitsTotal > 1)
this.scene.queueMessage(i18next.t('battle:attackHitsCount', { count: hitsTotal })); 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) if (user.turnData.hitsLeft < user.turnData.hitCount)
return true; return true;
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) if (user.hasAbilityWithAttr(Ability.AlwaysHitAbAttr) || target.hasAbilityWithAttr(Ability.AlwaysHitAbAttr))
return true; return true;
const hiddenTag = target.getTag(HiddenTag); const hiddenTag = target.getTag(HiddenTag);
@ -2607,7 +2629,7 @@ export class MoveEffectPhase extends PokemonPhase {
const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length; const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length;
if (!isOhko) 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) if (this.scene.arena.weather?.weatherType === WeatherType.FOG)
moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9); 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 userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]);
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); applyAbAttrs(Ability.IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel); applyAbAttrs(Ability.IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel);
applyMoveAttrs(IgnoreOpponentStatChangesAttr, user, target, this.move.getMove(), 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); const rand = user.randSeedInt(100, 1);
@ -2631,10 +2653,10 @@ export class MoveEffectPhase extends PokemonPhase {
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); : 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); 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; accuracyMultiplier.value /= evasionMultiplier.value;
@ -2702,13 +2724,12 @@ export class MoveAnimTestPhase extends BattlePhase {
initMoveAnim(this.scene, moveId).then(() => { initMoveAnim(this.scene, moveId).then(() => {
loadMoveAnimAssets(this.scene, [ moveId ], true) loadMoveAnimAssets(this.scene, [ moveId ], true)
.then(() => { .then(async () => {
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, () => { 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) if (player)
this.playMoveAnim(moveQueue, false); this.playMoveAnim(moveQueue, false);
else else
this.playMoveAnim(moveQueue, true); 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); this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
if (!cancelled.value && !this.selfTarget && this.levels < 0) if (!cancelled.value && !this.selfTarget && this.levels < 0)
applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); applyPreStatChangeAbAttrs(Ability.ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
return !cancelled.value; return !cancelled.value;
}); });
@ -2781,7 +2802,7 @@ export class StatChangePhase extends PokemonPhase {
const levels = new Utils.IntegerHolder(this.levels); const levels = new Utils.IntegerHolder(this.levels);
if (!this.ignoreAbilities) if (!this.ignoreAbilities)
applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); applyAbAttrs(Ability.StatChangeMultiplierAbAttr, pokemon, null, levels);
const battleStats = this.getPokemon().summonData.battleStats; 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]); 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) if (levels.value > 0 && this.canBeCopied)
for (let opponent of pokemon.getOpponents()) 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(); pokemon.updateInfo();
@ -2922,19 +2943,19 @@ export class WeatherEffectPhase extends CommonAnimPhase {
this.weather = weather; this.weather = weather;
} }
start() { async start() {
if (this.weather.isDamaging()) { if (this.weather.isDamaging()) {
const cancelled = new Utils.BooleanHolder(false); 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) { if (!cancelled.value) {
const inflictDamage = (pokemon: Pokemon) => { const inflictDamage = (pokemon: Pokemon) => {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); applyPreWeatherEffectAbAttrs(Ability.PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(Ability.BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) if (cancelled.value)
return; return;
@ -2954,7 +2975,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
} }
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType), null, () => { 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(); super.start();
}); });
@ -2976,19 +2997,18 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect
} }
start() { async start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (!pokemon.status) { if (!pokemon.status) {
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
if (this.cureTurn) if (this.cureTurn)
pokemon.status.cureTurn = this.cureTurn; pokemon.status.cureTurn = this.cureTurn;
pokemon.updateInfo(true); pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { await new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene);
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText))); this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText)));
if (pokemon.status.isPostTurn()) if (pokemon.status.isPostTurn())
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex));
this.end(); this.end();
});
return; return;
} }
} else if (pokemon.status.effect === this.statusEffect) } else if (pokemon.status.effect === this.statusEffect)
@ -3002,12 +3022,12 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
super(scene, battlerIndex); super(scene, battlerIndex);
} }
start() { async start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(Ability.BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); 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)); this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage));
pokemon.updateInfo(); pokemon.updateInfo();
} }
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); await new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene);
} else }
this.end(); this.end();
} else } else
this.end(); this.end();
} }
@ -3141,7 +3161,7 @@ export class DamagePhase extends PokemonPhase {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.scene.fadeOutBgm(Utils.fixedInt(2000), false); this.scene.fadeOutBgm(Utils.fixedInt(2000), false);
this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => { 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); pokemon.generateAndPopulateMoveset(1);
this.scene.setFieldScale(0.75); this.scene.setFieldScale(0.75);
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -3177,7 +3197,7 @@ export class FaintPhase extends PokemonPhase {
super.start(); super.start();
if (!this.preventEndure) { 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) {
if (!--instantReviveModifier.stackCount) if (!--instantReviveModifier.stackCount)
@ -3198,22 +3218,20 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData?.attacksReceived?.length) { if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; 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); 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) { if (pokemon.turnData?.attacksReceived?.length) {
const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId);
if (defeatSource?.isOnField()) { if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); applyPostVictoryAbAttrs(Ability.PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvMove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); const pvAttrs = pvMove.getAttrs(PostVictoryStatChangeAttr);
if (pvattrs.length) { pvAttrs.forEach((pvAttr: PostVictoryStatChangeAttr) => {
for (let pvattr of pvattrs) { pvAttr.applyPostVictory(defeatSource, defeatSource, pvMove);
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); })
}
}
} }
} }
@ -3310,9 +3328,9 @@ export class VictoryPhase extends PokemonPhase {
const participantIds = this.scene.currentBattle.playerParticipantIds; const participantIds = this.scene.currentBattle.playerParticipantIds;
const party = this.scene.getParty(); const party = this.scene.getParty();
const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; const expShareModifier = this.scene.findModifier(m => m instanceof Modifiers.ExpShareModifier) as Modifiers.ExpShareModifier;
const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; const expBalanceModifier = this.scene.findModifier(m => m instanceof Modifiers.ExpBalanceModifier) as Modifiers.ExpBalanceModifier;
const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof Modifiers.MultipleParticipantExpBonusModifier) as Modifiers.MultipleParticipantExpBonusModifier;
const nonFaintedPartyMembers = party.filter(p => p.hp); const nonFaintedPartyMembers = party.filter(p => p.hp);
const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel());
const partyMemberExp = []; const partyMemberExp = [];
@ -3342,7 +3360,7 @@ export class VictoryPhase extends PokemonPhase {
if (partyMember.pokerus) if (partyMember.pokerus)
expMultiplier *= 1.5; expMultiplier *= 1.5;
const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); 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)); partyMemberExp.push(Math.floor(pokemonExp.value));
} }
@ -3481,7 +3499,7 @@ export class MoneyRewardPhase extends BattlePhase {
start() { start() {
const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); 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); this.scene.addMoney(moneyAmount.value);
@ -3776,7 +3794,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
let exp = new Utils.NumberHolder(this.expValue); 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); exp.value = Math.floor(exp.value);
this.scene.ui.showText(i18next.t('battle:expGain', { pokemonName: pokemon.name, exp: exp.value }), null, () => { this.scene.ui.showText(i18next.t('battle:expGain', { pokemonName: pokemon.name, exp: exp.value }), null, () => {
const lastLevel = pokemon.level; const lastLevel = pokemon.level;
@ -3804,7 +3822,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
let exp = new Utils.NumberHolder(this.expValue); 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); exp.value = Math.floor(exp.value);
const lastLevel = pokemon.level; const lastLevel = pokemon.level;
@ -3994,16 +4012,16 @@ export class BerryPhase extends CommonAnimPhase {
} }
start() { start() {
let berryModifiers: BerryModifier[]; let berryModifiers: Modifiers.BerryModifier[];
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
const cancelled = new Utils.BooleanHolder(false); 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) if (cancelled.value)
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is too\nnervous to eat berries!')); 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) { for (let berryModifier of berryModifiers) {
if (berryModifier.consumed) { if (berryModifier.consumed) {
if (!--berryModifier.stackCount) if (!--berryModifier.stackCount)
@ -4041,7 +4059,7 @@ export class PokemonHealPhase extends CommonAnimPhase {
this.preventFullHeal = preventFullHeal; this.preventFullHeal = preventFullHeal;
} }
start() { async start() {
if (!this.skipAnim && (this.revive || this.getPokemon().hp) && this.getPokemon().getHpRatio() < 1) if (!this.skipAnim && (this.revive || this.getPokemon().hp) && this.getPokemon().getHpRatio() < 1)
super.start(); super.start();
else else
@ -4064,10 +4082,10 @@ export class PokemonHealPhase extends CommonAnimPhase {
if (!fullHp || this.hpHealed < 0) { if (!fullHp || this.hpHealed < 0) {
const hpRestoreMultiplier = new Utils.IntegerHolder(1); const hpRestoreMultiplier = new Utils.IntegerHolder(1);
if (!this.revive) 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)); const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value));
if (healAmount.value < 0) { if (healAmount.value < 0) {
pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL); pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult);
healAmount.value = 0; healAmount.value = 0;
} }
// Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) // 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 addToParty = () => {
const newPokemon = pokemon.addToParty(this.pokeballType); 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) if (this.scene.getParty().filter(p => p.isShiny()).length === 6)
this.scene.validateAchv(achvs.SHINY_PARTY); this.scene.validateAchv(achvs.SHINY_PARTY);
Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { 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 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); 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) { if (playerPokemon.randSeedInt(256) < escapeChance.value) {
this.scene.playSound('flee'); this.scene.playSound('flee');
@ -4420,7 +4438,7 @@ export class SelectModifierPhase extends BattlePhase {
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
const modifierCount = new Utils.IntegerHolder(3); const modifierCount = new Utils.IntegerHolder(3);
if (this.isPlayer()) if (this.isPlayer())
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); this.scene.applyModifiers(Modifiers.ExtraModifierModifier, true, modifierCount);
const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value);
const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { 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) => { 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) { 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(() => { 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 const itemModifiers = this.scene.findModifiers(m => m instanceof Modifiers.PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; && (m as Modifiers.PokemonHeldItemModifier).getTransferrable(true) && (m as Modifiers.PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as Modifiers.PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex]; const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, true); this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, true);
}); });
@ -4629,7 +4647,7 @@ export class AddEnemyBuffModifierPhase extends Phase {
const count = Math.ceil(waveIndex / 250); const count = Math.ceil(waveIndex / 250);
for (let i = 0; i < count; i++) 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()); this.scene.updateModifiers(false, true).then(() => this.end());
} }
} }
@ -4741,12 +4759,11 @@ export class ScanIvsPhase extends PokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
this.scene.ui.showText(i18next.t('battle:ivScannerUseQuestion', { pokemonName: pokemon.name }), null, () => { 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.setMode(Mode.MESSAGE);
this.scene.ui.clearText(); this.scene.ui.clearText();
new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { 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.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end());
});
}, () => { }, () => {
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.clearText(); this.scene.ui.clearText();