mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-25 09:42:20 +02:00
Remove Promises from moves and abilities
This commit is contained in:
parent
bec73fd8d3
commit
380fb877dc
@ -2582,90 +2582,83 @@ export default class BattleScene extends SceneBase {
|
||||
return Math.floor(moneyValue / 10) * 10;
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise<boolean> {
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): boolean {
|
||||
if (!modifier) {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
let success = false;
|
||||
const soundName = modifier.type.soundName;
|
||||
this.validateAchvs(ModifierAchv, modifier);
|
||||
const modifiersToRemove: PersistentModifier[] = [];
|
||||
const modifierPromises: Promise<boolean>[] = [];
|
||||
if (modifier instanceof PersistentModifier) {
|
||||
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) {
|
||||
if (modifier instanceof PokemonFormChangeItemModifier) {
|
||||
const pokemon = this.getPokemonById(modifier.pokemonId);
|
||||
if (pokemon) {
|
||||
success = modifier.apply(pokemon, true);
|
||||
}
|
||||
let success = false;
|
||||
const soundName = modifier.type.soundName;
|
||||
this.validateAchvs(ModifierAchv, modifier);
|
||||
const modifiersToRemove: PersistentModifier[] = [];
|
||||
if (modifier instanceof PersistentModifier) {
|
||||
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) {
|
||||
if (modifier instanceof PokemonFormChangeItemModifier) {
|
||||
const pokemon = this.getPokemonById(modifier.pokemonId);
|
||||
if (pokemon) {
|
||||
success = modifier.apply(pokemon, true);
|
||||
}
|
||||
if (playSound && !this.sound.get(soundName)) {
|
||||
this.playSound(soundName);
|
||||
}
|
||||
} else if (!virtual) {
|
||||
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
|
||||
this.queueMessage(i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }), undefined, false, 3000);
|
||||
return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant).then(success => resolve(success));
|
||||
}
|
||||
|
||||
for (const rm of modifiersToRemove) {
|
||||
this.removeModifier(rm);
|
||||
}
|
||||
|
||||
if (!ignoreUpdate && !virtual) {
|
||||
return this.updateModifiers(true, instant).then(() => resolve(success));
|
||||
}
|
||||
} else if (modifier instanceof ConsumableModifier) {
|
||||
if (playSound && !this.sound.get(soundName)) {
|
||||
this.playSound(soundName);
|
||||
}
|
||||
|
||||
if (modifier instanceof ConsumablePokemonModifier) {
|
||||
for (const p in this.party) {
|
||||
const pokemon = this.party[p];
|
||||
|
||||
const args: unknown[] = [];
|
||||
if (modifier instanceof PokemonHpRestoreModifier) {
|
||||
if (!(modifier as PokemonHpRestoreModifier).fainted) {
|
||||
const hpRestoreMultiplier = new Utils.NumberHolder(1);
|
||||
this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier);
|
||||
args.push(hpRestoreMultiplier.value);
|
||||
} else {
|
||||
args.push(1);
|
||||
}
|
||||
} else if (modifier instanceof FusePokemonModifier) {
|
||||
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
|
||||
} else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) {
|
||||
args.push(cost);
|
||||
}
|
||||
|
||||
if (modifier.shouldApply(pokemon, ...args)) {
|
||||
const result = modifier.apply(pokemon, ...args);
|
||||
if (result instanceof Promise) {
|
||||
modifierPromises.push(result.then(s => success ||= s));
|
||||
} else {
|
||||
success ||= result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.allSettled([ this.party.map(p => p.updateInfo(instant)), ...modifierPromises ]).then(() => resolve(success));
|
||||
} else {
|
||||
const args = [ this ];
|
||||
if (modifier.shouldApply(...args)) {
|
||||
const result = modifier.apply(...args);
|
||||
if (result instanceof Promise) {
|
||||
return result.then(success => resolve(success));
|
||||
} else {
|
||||
success ||= result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!virtual) {
|
||||
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
|
||||
this.queueMessage(
|
||||
i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }),
|
||||
undefined,
|
||||
false,
|
||||
3000
|
||||
);
|
||||
return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant);
|
||||
}
|
||||
|
||||
resolve(success);
|
||||
});
|
||||
for (const rm of modifiersToRemove) {
|
||||
this.removeModifier(rm);
|
||||
}
|
||||
|
||||
if (!ignoreUpdate && !virtual) {
|
||||
this.updateModifiers(true, instant);
|
||||
}
|
||||
} else if (modifier instanceof ConsumableModifier) {
|
||||
if (playSound && !this.sound.get(soundName)) {
|
||||
this.playSound(soundName);
|
||||
}
|
||||
|
||||
if (modifier instanceof ConsumablePokemonModifier) {
|
||||
for (const p in this.party) {
|
||||
const pokemon = this.party[p];
|
||||
|
||||
const args: unknown[] = [];
|
||||
if (modifier instanceof PokemonHpRestoreModifier) {
|
||||
if (!(modifier as PokemonHpRestoreModifier).fainted) {
|
||||
const hpRestoreMultiplier = new Utils.NumberHolder(1);
|
||||
this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier);
|
||||
args.push(hpRestoreMultiplier.value);
|
||||
} else {
|
||||
args.push(1);
|
||||
}
|
||||
} else if (modifier instanceof FusePokemonModifier) {
|
||||
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
|
||||
} else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) {
|
||||
args.push(cost);
|
||||
}
|
||||
|
||||
if (modifier.shouldApply(pokemon, ...args)) {
|
||||
const result = modifier.apply(pokemon, ...args);
|
||||
success ||= result;
|
||||
}
|
||||
}
|
||||
|
||||
this.party.map((p) => p.updateInfo(instant));
|
||||
} else {
|
||||
const args = [ this ];
|
||||
if (modifier.shouldApply(...args)) {
|
||||
const result = modifier.apply(...args);
|
||||
success ||= result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise<void> {
|
||||
@ -2683,7 +2676,8 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
}
|
||||
if (!ignoreUpdate) {
|
||||
this.updateModifiers(false, instant).then(() => resolve());
|
||||
this.updateModifiers(false, instant);
|
||||
resolve();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@ -2704,66 +2698,82 @@ export default class BattleScene extends SceneBase {
|
||||
* @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`.
|
||||
* @returns `true` if the transfer was successful
|
||||
*/
|
||||
tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null;
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => {
|
||||
if (cancelled.value) {
|
||||
return resolve(false);
|
||||
}
|
||||
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier;
|
||||
newItemModifier.pokemonId = target.id;
|
||||
const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier;
|
||||
let removeOld = true;
|
||||
if (matchingModifier) {
|
||||
const maxStackCount = matchingModifier.getMaxStackCount();
|
||||
if (matchingModifier.stackCount >= maxStackCount) {
|
||||
return resolve(false);
|
||||
}
|
||||
const countTaken = Math.min(transferQuantity, itemModifier.stackCount, maxStackCount - matchingModifier.stackCount);
|
||||
itemModifier.stackCount -= countTaken;
|
||||
newItemModifier.stackCount = matchingModifier.stackCount + countTaken;
|
||||
removeOld = !itemModifier.stackCount;
|
||||
} else {
|
||||
const countTaken = Math.min(transferQuantity, itemModifier.stackCount);
|
||||
itemModifier.stackCount -= countTaken;
|
||||
newItemModifier.stackCount = countTaken;
|
||||
}
|
||||
removeOld = !itemModifier.stackCount;
|
||||
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) {
|
||||
const addModifier = () => {
|
||||
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
|
||||
if (target.isPlayer()) {
|
||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => {
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => {
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve(false);
|
||||
tryTransferHeldItemModifier(
|
||||
itemModifier: PokemonHeldItemModifier,
|
||||
target: Pokemon,
|
||||
playSound: boolean,
|
||||
transferQuantity: number = 1,
|
||||
instant?: boolean,
|
||||
ignoreUpdate?: boolean,
|
||||
itemLost: boolean = true,
|
||||
): boolean {
|
||||
const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null;
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
|
||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
||||
}
|
||||
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier;
|
||||
newItemModifier.pokemonId = target.id;
|
||||
const matchingModifier = this.findModifier(
|
||||
(m) => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier;
|
||||
|
||||
if (matchingModifier) {
|
||||
const maxStackCount = matchingModifier.getMaxStackCount();
|
||||
if (matchingModifier.stackCount >= maxStackCount) {
|
||||
return false;
|
||||
}
|
||||
const countTaken = Math.min(
|
||||
transferQuantity,
|
||||
itemModifier.stackCount,
|
||||
maxStackCount - matchingModifier.stackCount,
|
||||
);
|
||||
itemModifier.stackCount -= countTaken;
|
||||
newItemModifier.stackCount = matchingModifier.stackCount + countTaken;
|
||||
} else {
|
||||
const countTaken = Math.min(transferQuantity, itemModifier.stackCount);
|
||||
itemModifier.stackCount -= countTaken;
|
||||
newItemModifier.stackCount = countTaken;
|
||||
}
|
||||
|
||||
const removeOld = itemModifier.stackCount === 0;
|
||||
|
||||
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) {
|
||||
const addModifier = () => {
|
||||
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
|
||||
if (target.isPlayer()) {
|
||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
}
|
||||
};
|
||||
if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
|
||||
this.updateModifiers(source.isPlayer(), instant).then(() => addModifier());
|
||||
return true;
|
||||
} else {
|
||||
addModifier();
|
||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => {
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
};
|
||||
if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
|
||||
this.updateModifiers(source.isPlayer(), instant);
|
||||
addModifier();
|
||||
} else {
|
||||
addModifier();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removePartyMemberModifiers(partyMemberIndex: number): Promise<void> {
|
||||
@ -2773,7 +2783,8 @@ export default class BattleScene extends SceneBase {
|
||||
for (const m of modifiersToRemove) {
|
||||
this.modifiers.splice(this.modifiers.indexOf(m), 1);
|
||||
}
|
||||
this.updateModifiers().then(() => resolve());
|
||||
this.updateModifiers();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2841,7 +2852,8 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.updateModifiers(false).then(() => resolve());
|
||||
this.updateModifiers(false);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2853,7 +2865,8 @@ export default class BattleScene extends SceneBase {
|
||||
for (const m of modifiersToRemove) {
|
||||
this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1);
|
||||
}
|
||||
this.updateModifiers(false).then(() => this.updateUIPositions());
|
||||
this.updateModifiers(false);
|
||||
this.updateUIPositions();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2865,46 +2878,43 @@ export default class BattleScene extends SceneBase {
|
||||
for (const m of modifiersToRemove) {
|
||||
this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1);
|
||||
}
|
||||
this.updateModifiers(false).then(() => this.updateUIPositions());
|
||||
this.updateModifiers(false);
|
||||
this.updateUIPositions();
|
||||
}
|
||||
|
||||
setModifiersVisible(visible: boolean) {
|
||||
[ this.modifierBar, this.enemyModifierBar ].map(m => m.setVisible(visible));
|
||||
}
|
||||
|
||||
updateModifiers(player?: boolean, instant?: boolean): Promise<void> {
|
||||
if (player === undefined) {
|
||||
player = true;
|
||||
updateModifiers(player: boolean = true, instant?: boolean): void {
|
||||
const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]);
|
||||
for (let m = 0; m < modifiers.length; m++) {
|
||||
const modifier = modifiers[m];
|
||||
if (
|
||||
modifier instanceof PokemonHeldItemModifier &&
|
||||
!this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId)
|
||||
) {
|
||||
modifiers.splice(m--, 1);
|
||||
}
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
const modifiers = player ? this.modifiers : this.enemyModifiers as PersistentModifier[];
|
||||
for (let m = 0; m < modifiers.length; m++) {
|
||||
const modifier = modifiers[m];
|
||||
if (modifier instanceof PokemonHeldItemModifier && !this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId)) {
|
||||
modifiers.splice(m--, 1);
|
||||
}
|
||||
}
|
||||
for (const modifier of modifiers) {
|
||||
if (modifier instanceof PersistentModifier) {
|
||||
(modifier as PersistentModifier).virtualStackCount = 0;
|
||||
}
|
||||
for (const modifier of modifiers) {
|
||||
if (modifier instanceof PersistentModifier) {
|
||||
(modifier as PersistentModifier).virtualStackCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const modifiersClone = modifiers.slice(0);
|
||||
for (const modifier of modifiersClone) {
|
||||
if (!modifier.getStackCount()) {
|
||||
modifiers.splice(modifiers.indexOf(modifier), 1);
|
||||
}
|
||||
const modifiersClone = modifiers.slice(0);
|
||||
for (const modifier of modifiersClone) {
|
||||
if (!modifier.getStackCount()) {
|
||||
modifiers.splice(modifiers.indexOf(modifier), 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant).then(() => {
|
||||
(player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers);
|
||||
if (!player) {
|
||||
this.updateUIPositions();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant);
|
||||
(player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers);
|
||||
if (!player) {
|
||||
this.updateUIPositions();
|
||||
}
|
||||
}
|
||||
|
||||
updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise<void> {
|
||||
|
1029
src/data/ability.ts
1029
src/data/ability.ts
File diff suppressed because it is too large
Load Diff
648
src/data/move.ts
648
src/data/move.ts
@ -1,5 +1,16 @@
|
||||
import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims";
|
||||
import { CommandedTag, EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags";
|
||||
import { ChargeAnim, MoveChargeAnim } from "./battle-anims";
|
||||
import {
|
||||
CommandedTag,
|
||||
EncoreTag,
|
||||
GulpMissileTag,
|
||||
HelpingHandTag,
|
||||
SemiInvulnerableTag,
|
||||
ShellTrapTag,
|
||||
StockpilingTag,
|
||||
SubstituteTag,
|
||||
TrappedTag,
|
||||
TypeBoostTag,
|
||||
} from "./battler-tags";
|
||||
import { getPokemonNameWithAffix } from "../messages";
|
||||
import type { AttackMoveResult, TurnMove } from "../field/pokemon";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
@ -30,7 +41,7 @@ import { Biome } from "#enums/biome";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { MoveUsedEvent } from "#app/events/battle-scene";
|
||||
import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat";
|
||||
import { BATTLE_STATS, type BattleStat, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat";
|
||||
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
@ -46,6 +57,10 @@ import { applyChallenges, ChallengeType } from "./challenge";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
|
||||
import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase";
|
||||
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
|
||||
import { MoveAnimPhase } from "#app/phases/move-anim-phase";
|
||||
|
||||
export enum MoveCategory {
|
||||
PHYSICAL,
|
||||
@ -1057,7 +1072,7 @@ export abstract class MoveAttr {
|
||||
* @param args Set of unique arguments needed by this attribute
|
||||
* @returns true if application of the ability succeeds
|
||||
*/
|
||||
apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1200,7 +1215,7 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
}
|
||||
|
||||
/** Applies move effects so long as they are able based on {@linkcode canApply} */
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
return this.canApply(user, target, move, args);
|
||||
}
|
||||
|
||||
@ -1866,7 +1881,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
|
||||
* @param args - n/a
|
||||
* @returns A boolean indicating whether the effect was successfully applied.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const targetAlly = target.getAlly();
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
|
||||
@ -2406,32 +2421,27 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return resolve(false);
|
||||
}
|
||||
const rand = Phaser.Math.RND.realInRange(0, 1);
|
||||
if (rand >= this.chance) {
|
||||
return resolve(false);
|
||||
}
|
||||
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
||||
globalScene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => {
|
||||
if (success) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
}
|
||||
resolve(success);
|
||||
});
|
||||
return;
|
||||
}
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
resolve(false);
|
||||
});
|
||||
const rand = Phaser.Math.RND.realInRange(0, 1);
|
||||
if (rand >= this.chance) {
|
||||
return false;
|
||||
}
|
||||
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
||||
if (globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
@ -2875,9 +2885,7 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr {
|
||||
}
|
||||
|
||||
export class OverrideMoveEffectAttr extends MoveAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
//const overridden = args[0] as Utils.BooleanHolder;
|
||||
//const virtual = arg[1] as boolean;
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2903,26 +2911,27 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
|
||||
this.chargeText = chargeText;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// Edge case for the move applied on a pokemon that has fainted
|
||||
if (!target) {
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
return new Promise(resolve => {
|
||||
if (args.length < 2 || !args[1]) {
|
||||
new MoveChargeAnim(this.chargeAnim, move.id, user).play(false, () => {
|
||||
(args[0] as Utils.BooleanHolder).value = true;
|
||||
globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
|
||||
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
||||
globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex());
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
globalScene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }), null, () => resolve(true));
|
||||
}
|
||||
});
|
||||
const overridden = args[0] as Utils.BooleanHolder;
|
||||
const virtual = args[1] as boolean;
|
||||
|
||||
if (!virtual) {
|
||||
overridden.value = true;
|
||||
globalScene.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user)));
|
||||
globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
|
||||
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
||||
const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex());
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3053,7 +3062,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
* @param args unused
|
||||
* @returns whether stat stages were changed
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) {
|
||||
return false;
|
||||
}
|
||||
@ -3131,7 +3140,7 @@ export class SecretPowerAttr extends MoveEffectAttr {
|
||||
* Used to apply the secondary effect to the target Pokemon
|
||||
* @returns `true` if a secondary effect is successfully applied
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
@ -3286,8 +3295,8 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr {
|
||||
super();
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6);
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6);
|
||||
if (randStats.length > 0) {
|
||||
const boostStat = [ randStats[user.randSeedInt(randStats.length)] ];
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2));
|
||||
@ -3324,17 +3333,14 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
|
||||
this.messageCallback = messageCallback;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
|
||||
user.updateInfo().then(() => {
|
||||
const ret = super.apply(user, target, move, args);
|
||||
if (this.messageCallback) {
|
||||
this.messageCallback(user);
|
||||
}
|
||||
resolve(ret);
|
||||
});
|
||||
});
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
|
||||
user.updateInfo(); // TODO: This is a Promise and might cause desync issues
|
||||
const ret = super.apply(user, target, move, args);
|
||||
if (this.messageCallback) {
|
||||
this.messageCallback(user);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
@ -3426,28 +3432,27 @@ export class ResetStatsAttr extends MoveEffectAttr {
|
||||
super();
|
||||
this.targetAllPokemon = targetAllPokemon;
|
||||
}
|
||||
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
const promises: Promise<void>[] = [];
|
||||
if (this.targetAllPokemon) { // Target all pokemon on the field when Freezy Frost or Haze are used
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (this.targetAllPokemon) {
|
||||
// Target all pokemon on the field when Freezy Frost or Haze are used
|
||||
const activePokemon = globalScene.getField(true);
|
||||
activePokemon.forEach(p => promises.push(this.resetStats(p)));
|
||||
activePokemon.forEach((p) => this.resetStats(p));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
|
||||
} else { // Affects only the single target when Clear Smog is used
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
promises.push(this.resetStats(target));
|
||||
this.resetStats(target);
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
return true;
|
||||
}
|
||||
|
||||
async resetStats(pokemon: Pokemon): Promise<void> {
|
||||
private resetStats(pokemon: Pokemon): void {
|
||||
for (const s of BATTLE_STATS) {
|
||||
pokemon.setStatStage(s, 0);
|
||||
}
|
||||
return pokemon.updateInfo();
|
||||
pokemon.updateInfo(); // TODO: This is still a Promise and might cause desync issues
|
||||
}
|
||||
}
|
||||
|
||||
@ -3503,43 +3508,28 @@ export class SwapStatStagesAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
export class HpSplitAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return resolve(false);
|
||||
}
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const infoUpdates: Promise<void>[] = [];
|
||||
|
||||
const hpValue = Math.floor((target.hp + user.hp) / 2);
|
||||
if (user.hp < hpValue) {
|
||||
const healing = user.heal(hpValue - user.hp);
|
||||
const hpValue = Math.floor((target.hp + user.hp) / 2);
|
||||
[ user, target ].forEach((p) => {
|
||||
if (p.hp < hpValue) {
|
||||
const healing = p.heal(hpValue - p.hp);
|
||||
if (healing) {
|
||||
globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL);
|
||||
globalScene.damageNumberHandler.add(p, healing, HitResult.HEAL);
|
||||
}
|
||||
} else if (user.hp > hpValue) {
|
||||
const damage = user.damage(user.hp - hpValue, true);
|
||||
} else if (p.hp > hpValue) {
|
||||
const damage = p.damage(p.hp - hpValue, true);
|
||||
if (damage) {
|
||||
globalScene.damageNumberHandler.add(user, damage);
|
||||
globalScene.damageNumberHandler.add(p, damage);
|
||||
}
|
||||
}
|
||||
infoUpdates.push(user.updateInfo());
|
||||
|
||||
if (target.hp < hpValue) {
|
||||
const healing = target.heal(hpValue - target.hp);
|
||||
if (healing) {
|
||||
globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL);
|
||||
}
|
||||
} else if (target.hp > hpValue) {
|
||||
const damage = target.damage(target.hp - hpValue, true);
|
||||
if (damage) {
|
||||
globalScene.damageNumberHandler.add(target, damage);
|
||||
}
|
||||
}
|
||||
infoUpdates.push(target.updateInfo());
|
||||
|
||||
return Promise.all(infoUpdates).then(() => resolve(true));
|
||||
p.updateInfo(); // TODO: This is still a Promise
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6024,40 +6014,34 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
* @param args N/A
|
||||
* @returns Promise, true if function succeeds.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
// If user is player, checks if the user has fainted pokemon
|
||||
if (user instanceof PlayerPokemon
|
||||
&& globalScene.getPlayerParty().findIndex(p => p.isFainted()) > -1) {
|
||||
(user as PlayerPokemon).revivalBlessing().then(() => {
|
||||
resolve(true);
|
||||
});
|
||||
// If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party
|
||||
} else if (user instanceof EnemyPokemon
|
||||
&& user.hasTrainer()
|
||||
&& globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) {
|
||||
// Selects a random fainted pokemon
|
||||
const faintedPokemon = globalScene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss());
|
||||
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
|
||||
const slotIndex = globalScene.getEnemyParty().findIndex(p => pokemon.id === p.id);
|
||||
pokemon.resetStatus();
|
||||
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true);
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// If user is player, checks if the user has fainted pokemon
|
||||
if (user instanceof PlayerPokemon && globalScene.getPlayerParty().findIndex((p) => p.isFainted()) > -1) {
|
||||
globalScene.unshiftPhase(new RevivalBlessingPhase(user));
|
||||
return true;
|
||||
} else if (user instanceof EnemyPokemon && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) {
|
||||
// If used by an enemy trainer with at least one fainted non-boss Pokemon, this
|
||||
// revives one of said Pokemon selected at random.
|
||||
const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss());
|
||||
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
|
||||
const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id);
|
||||
pokemon.resetStatus();
|
||||
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true);
|
||||
|
||||
if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) {
|
||||
const allyPokemon = user.getAlly();
|
||||
if (slotIndex <= 1) {
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false));
|
||||
}
|
||||
if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) {
|
||||
const allyPokemon = user.getAlly();
|
||||
if (slotIndex <= 1) {
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false));
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("battle:attackFailed"));
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("battle:attackFailed"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
@ -6579,7 +6563,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr {
|
||||
class CallMoveAttr extends OverrideMoveEffectAttr {
|
||||
protected invalidMoves: Moves[];
|
||||
protected hasTarget: boolean;
|
||||
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined;
|
||||
const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget);
|
||||
if (moveTargets.targets.length === 0) {
|
||||
@ -6589,11 +6573,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
|
||||
? moveTargets.targets
|
||||
: [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already
|
||||
user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true });
|
||||
globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id));
|
||||
globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true));
|
||||
|
||||
await Promise.resolve(initMoveAnim(move.id).then(() => {
|
||||
loadMoveAnimAssets([ move.id ], true);
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -6626,7 +6607,7 @@ export class RandomMoveAttr extends CallMoveAttr {
|
||||
* @param move Move being used
|
||||
* @param args Unused
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE);
|
||||
let moveId: Moves = Moves.NONE;
|
||||
do {
|
||||
@ -6663,7 +6644,7 @@ export class RandomMovesetMoveAttr extends CallMoveAttr {
|
||||
* @param move Move being used
|
||||
* @param args Unused
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return super.apply(user, target, allMoves[this.moveId], args);
|
||||
}
|
||||
|
||||
@ -6965,145 +6946,141 @@ const invalidCopycatMoves = [
|
||||
];
|
||||
|
||||
export class NaturePowerAttr extends OverrideMoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
let moveId;
|
||||
switch (globalScene.arena.getTerrainType()) {
|
||||
// this allows terrains to 'override' the biome move
|
||||
case TerrainType.NONE:
|
||||
switch (globalScene.arena.biomeType) {
|
||||
case Biome.TOWN:
|
||||
moveId = Moves.ROUND;
|
||||
break;
|
||||
case Biome.METROPOLIS:
|
||||
moveId = Moves.TRI_ATTACK;
|
||||
break;
|
||||
case Biome.SLUM:
|
||||
moveId = Moves.SLUDGE_BOMB;
|
||||
break;
|
||||
case Biome.PLAINS:
|
||||
moveId = Moves.SILVER_WIND;
|
||||
break;
|
||||
case Biome.GRASS:
|
||||
moveId = Moves.GRASS_KNOT;
|
||||
break;
|
||||
case Biome.TALL_GRASS:
|
||||
moveId = Moves.POLLEN_PUFF;
|
||||
break;
|
||||
case Biome.MEADOW:
|
||||
moveId = Moves.GIGA_DRAIN;
|
||||
break;
|
||||
case Biome.FOREST:
|
||||
moveId = Moves.BUG_BUZZ;
|
||||
break;
|
||||
case Biome.JUNGLE:
|
||||
moveId = Moves.LEAF_STORM;
|
||||
break;
|
||||
case Biome.SEA:
|
||||
moveId = Moves.HYDRO_PUMP;
|
||||
break;
|
||||
case Biome.SWAMP:
|
||||
moveId = Moves.MUD_BOMB;
|
||||
break;
|
||||
case Biome.BEACH:
|
||||
moveId = Moves.SCALD;
|
||||
break;
|
||||
case Biome.LAKE:
|
||||
moveId = Moves.BUBBLE_BEAM;
|
||||
break;
|
||||
case Biome.SEABED:
|
||||
moveId = Moves.BRINE;
|
||||
break;
|
||||
case Biome.ISLAND:
|
||||
moveId = Moves.LEAF_TORNADO;
|
||||
break;
|
||||
case Biome.MOUNTAIN:
|
||||
moveId = Moves.AIR_SLASH;
|
||||
break;
|
||||
case Biome.BADLANDS:
|
||||
moveId = Moves.EARTH_POWER;
|
||||
break;
|
||||
case Biome.DESERT:
|
||||
moveId = Moves.SCORCHING_SANDS;
|
||||
break;
|
||||
case Biome.WASTELAND:
|
||||
moveId = Moves.DRAGON_PULSE;
|
||||
break;
|
||||
case Biome.CONSTRUCTION_SITE:
|
||||
moveId = Moves.STEEL_BEAM;
|
||||
break;
|
||||
case Biome.CAVE:
|
||||
moveId = Moves.POWER_GEM;
|
||||
break;
|
||||
case Biome.ICE_CAVE:
|
||||
moveId = Moves.ICE_BEAM;
|
||||
break;
|
||||
case Biome.SNOWY_FOREST:
|
||||
moveId = Moves.FROST_BREATH;
|
||||
break;
|
||||
case Biome.VOLCANO:
|
||||
moveId = Moves.LAVA_PLUME;
|
||||
break;
|
||||
case Biome.GRAVEYARD:
|
||||
moveId = Moves.SHADOW_BALL;
|
||||
break;
|
||||
case Biome.RUINS:
|
||||
moveId = Moves.ANCIENT_POWER;
|
||||
break;
|
||||
case Biome.TEMPLE:
|
||||
moveId = Moves.EXTRASENSORY;
|
||||
break;
|
||||
case Biome.DOJO:
|
||||
moveId = Moves.FOCUS_BLAST;
|
||||
break;
|
||||
case Biome.FAIRY_CAVE:
|
||||
moveId = Moves.ALLURING_VOICE;
|
||||
break;
|
||||
case Biome.ABYSS:
|
||||
moveId = Moves.OMINOUS_WIND;
|
||||
break;
|
||||
case Biome.SPACE:
|
||||
moveId = Moves.DRACO_METEOR;
|
||||
break;
|
||||
case Biome.FACTORY:
|
||||
moveId = Moves.FLASH_CANNON;
|
||||
break;
|
||||
case Biome.LABORATORY:
|
||||
moveId = Moves.ZAP_CANNON;
|
||||
break;
|
||||
case Biome.POWER_PLANT:
|
||||
moveId = Moves.CHARGE_BEAM;
|
||||
break;
|
||||
case Biome.END:
|
||||
moveId = Moves.ETERNABEAM;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TerrainType.MISTY:
|
||||
moveId = Moves.MOONBLAST;
|
||||
break;
|
||||
case TerrainType.ELECTRIC:
|
||||
moveId = Moves.THUNDERBOLT;
|
||||
break;
|
||||
case TerrainType.GRASSY:
|
||||
moveId = Moves.ENERGY_BALL;
|
||||
break;
|
||||
case TerrainType.PSYCHIC:
|
||||
moveId = Moves.PSYCHIC;
|
||||
break;
|
||||
default:
|
||||
// Just in case there's no match
|
||||
moveId = Moves.TRI_ATTACK;
|
||||
break;
|
||||
}
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
let moveId;
|
||||
switch (globalScene.arena.getTerrainType()) {
|
||||
// this allows terrains to 'override' the biome move
|
||||
case TerrainType.NONE:
|
||||
switch (globalScene.arena.biomeType) {
|
||||
case Biome.TOWN:
|
||||
moveId = Moves.ROUND;
|
||||
break;
|
||||
case Biome.METROPOLIS:
|
||||
moveId = Moves.TRI_ATTACK;
|
||||
break;
|
||||
case Biome.SLUM:
|
||||
moveId = Moves.SLUDGE_BOMB;
|
||||
break;
|
||||
case Biome.PLAINS:
|
||||
moveId = Moves.SILVER_WIND;
|
||||
break;
|
||||
case Biome.GRASS:
|
||||
moveId = Moves.GRASS_KNOT;
|
||||
break;
|
||||
case Biome.TALL_GRASS:
|
||||
moveId = Moves.POLLEN_PUFF;
|
||||
break;
|
||||
case Biome.MEADOW:
|
||||
moveId = Moves.GIGA_DRAIN;
|
||||
break;
|
||||
case Biome.FOREST:
|
||||
moveId = Moves.BUG_BUZZ;
|
||||
break;
|
||||
case Biome.JUNGLE:
|
||||
moveId = Moves.LEAF_STORM;
|
||||
break;
|
||||
case Biome.SEA:
|
||||
moveId = Moves.HYDRO_PUMP;
|
||||
break;
|
||||
case Biome.SWAMP:
|
||||
moveId = Moves.MUD_BOMB;
|
||||
break;
|
||||
case Biome.BEACH:
|
||||
moveId = Moves.SCALD;
|
||||
break;
|
||||
case Biome.LAKE:
|
||||
moveId = Moves.BUBBLE_BEAM;
|
||||
break;
|
||||
case Biome.SEABED:
|
||||
moveId = Moves.BRINE;
|
||||
break;
|
||||
case Biome.ISLAND:
|
||||
moveId = Moves.LEAF_TORNADO;
|
||||
break;
|
||||
case Biome.MOUNTAIN:
|
||||
moveId = Moves.AIR_SLASH;
|
||||
break;
|
||||
case Biome.BADLANDS:
|
||||
moveId = Moves.EARTH_POWER;
|
||||
break;
|
||||
case Biome.DESERT:
|
||||
moveId = Moves.SCORCHING_SANDS;
|
||||
break;
|
||||
case Biome.WASTELAND:
|
||||
moveId = Moves.DRAGON_PULSE;
|
||||
break;
|
||||
case Biome.CONSTRUCTION_SITE:
|
||||
moveId = Moves.STEEL_BEAM;
|
||||
break;
|
||||
case Biome.CAVE:
|
||||
moveId = Moves.POWER_GEM;
|
||||
break;
|
||||
case Biome.ICE_CAVE:
|
||||
moveId = Moves.ICE_BEAM;
|
||||
break;
|
||||
case Biome.SNOWY_FOREST:
|
||||
moveId = Moves.FROST_BREATH;
|
||||
break;
|
||||
case Biome.VOLCANO:
|
||||
moveId = Moves.LAVA_PLUME;
|
||||
break;
|
||||
case Biome.GRAVEYARD:
|
||||
moveId = Moves.SHADOW_BALL;
|
||||
break;
|
||||
case Biome.RUINS:
|
||||
moveId = Moves.ANCIENT_POWER;
|
||||
break;
|
||||
case Biome.TEMPLE:
|
||||
moveId = Moves.EXTRASENSORY;
|
||||
break;
|
||||
case Biome.DOJO:
|
||||
moveId = Moves.FOCUS_BLAST;
|
||||
break;
|
||||
case Biome.FAIRY_CAVE:
|
||||
moveId = Moves.ALLURING_VOICE;
|
||||
break;
|
||||
case Biome.ABYSS:
|
||||
moveId = Moves.OMINOUS_WIND;
|
||||
break;
|
||||
case Biome.SPACE:
|
||||
moveId = Moves.DRACO_METEOR;
|
||||
break;
|
||||
case Biome.FACTORY:
|
||||
moveId = Moves.FLASH_CANNON;
|
||||
break;
|
||||
case Biome.LABORATORY:
|
||||
moveId = Moves.ZAP_CANNON;
|
||||
break;
|
||||
case Biome.POWER_PLANT:
|
||||
moveId = Moves.CHARGE_BEAM;
|
||||
break;
|
||||
case Biome.END:
|
||||
moveId = Moves.ETERNABEAM;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TerrainType.MISTY:
|
||||
moveId = Moves.MOONBLAST;
|
||||
break;
|
||||
case TerrainType.ELECTRIC:
|
||||
moveId = Moves.THUNDERBOLT;
|
||||
break;
|
||||
case TerrainType.GRASSY:
|
||||
moveId = Moves.ENERGY_BALL;
|
||||
break;
|
||||
case TerrainType.PSYCHIC:
|
||||
moveId = Moves.PSYCHIC;
|
||||
break;
|
||||
default:
|
||||
// Just in case there's no match
|
||||
moveId = Moves.TRI_ATTACK;
|
||||
break;
|
||||
}
|
||||
|
||||
user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true });
|
||||
globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true));
|
||||
initMoveAnim(moveId).then(() => {
|
||||
loadMoveAnimAssets([ moveId ], true)
|
||||
.then(() => resolve(true));
|
||||
});
|
||||
});
|
||||
user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true });
|
||||
globalScene.unshiftPhase(new LoadMoveAnimPhase(moveId));
|
||||
globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7121,7 +7098,7 @@ export class CopyMoveAttr extends CallMoveAttr {
|
||||
this.invalidMoves = invalidMoves;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
this.hasTarget = this.mirrorMove;
|
||||
const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove;
|
||||
return super.apply(user, target, allMoves[lastMove], args);
|
||||
@ -7682,50 +7659,15 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
|
||||
* Used by Transform
|
||||
*/
|
||||
export class TransformAttr extends MoveEffectAttr {
|
||||
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
user.summonData.speciesForm = target.getSpeciesForm();
|
||||
user.summonData.gender = target.getGender();
|
||||
|
||||
// Power Trick's effect will not preserved after using Transform
|
||||
user.removeTag(BattlerTagType.POWER_TRICK);
|
||||
|
||||
// Copy all stats (except HP)
|
||||
for (const s of EFFECTIVE_STATS) {
|
||||
user.setStat(s, target.getStat(s, false), false);
|
||||
}
|
||||
|
||||
// Copy all stat stages
|
||||
for (const s of BATTLE_STATS) {
|
||||
user.setStatStage(s, target.getStatStage(s));
|
||||
}
|
||||
|
||||
user.summonData.moveset = target.getMoveset().map((m) => {
|
||||
if (m) {
|
||||
// If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5.
|
||||
return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5));
|
||||
} else {
|
||||
console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`);
|
||||
return new PokemonMove(Moves.NONE);
|
||||
}
|
||||
});
|
||||
user.summonData.types = target.getTypes();
|
||||
promises.push(user.updateInfo());
|
||||
globalScene.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex()));
|
||||
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
promises.push(user.loadAssets(false).then(() => {
|
||||
user.playAnim();
|
||||
user.updateInfo();
|
||||
// If the new ability activates immediately, it needs to happen after all the transform animations
|
||||
user.setTempAbility(target.getAbility());
|
||||
}));
|
||||
|
||||
await Promise.all(promises);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -8128,44 +8070,54 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) =
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const attrPromises: Promise<boolean>[] = [];
|
||||
const moveAttrs = move.attrs.filter(a => attrFilter(a));
|
||||
for (const attr of moveAttrs) {
|
||||
const result = attr.apply(user, target, move, args);
|
||||
if (result instanceof Promise) {
|
||||
attrPromises.push(result);
|
||||
}
|
||||
}
|
||||
Promise.allSettled(attrPromises).then(() => resolve());
|
||||
});
|
||||
function applyMoveAttrsInternal(
|
||||
attrFilter: MoveAttrFilter,
|
||||
user: Pokemon | null,
|
||||
target: Pokemon | null,
|
||||
move: Move,
|
||||
args: any[],
|
||||
): void {
|
||||
move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
|
||||
}
|
||||
|
||||
function applyMoveChargeAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, args: any[]): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const chargeAttrPromises: Promise<boolean>[] = [];
|
||||
const chargeMoveAttrs = move.chargeAttrs.filter(a => attrFilter(a));
|
||||
for (const attr of chargeMoveAttrs) {
|
||||
const result = attr.apply(user, target, move, args);
|
||||
if (result instanceof Promise) {
|
||||
chargeAttrPromises.push(result);
|
||||
}
|
||||
}
|
||||
Promise.allSettled(chargeAttrPromises).then(() => resolve());
|
||||
});
|
||||
function applyMoveChargeAttrsInternal(
|
||||
attrFilter: MoveAttrFilter,
|
||||
user: Pokemon | null,
|
||||
target: Pokemon | null,
|
||||
move: ChargingMove,
|
||||
args: any[],
|
||||
): void {
|
||||
move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
|
||||
}
|
||||
|
||||
export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon | null, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
|
||||
return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
|
||||
export function applyMoveAttrs(
|
||||
attrType: Constructor<MoveAttr>,
|
||||
user: Pokemon | null,
|
||||
target: Pokemon | null,
|
||||
move: Move,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
|
||||
}
|
||||
|
||||
export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
|
||||
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
|
||||
export function applyFilteredMoveAttrs(
|
||||
attrFilter: MoveAttrFilter,
|
||||
user: Pokemon,
|
||||
target: Pokemon | null,
|
||||
move: Move,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyMoveAttrsInternal(attrFilter, user, target, move, args);
|
||||
}
|
||||
|
||||
export function applyMoveChargeAttrs(attrType: Constructor<MoveAttr>, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, ...args: any[]): Promise<void> {
|
||||
return applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
|
||||
export function applyMoveChargeAttrs(
|
||||
attrType: Constructor<MoveAttr>,
|
||||
user: Pokemon | null,
|
||||
target: Pokemon | null,
|
||||
move: ChargingMove,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
|
||||
}
|
||||
|
||||
export class MoveCondition {
|
||||
|
@ -104,7 +104,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
@ -4510,43 +4509,6 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.friendship = Math.max(this.friendship + friendship, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handles Revival Blessing when used by player.
|
||||
* @returns Promise to revive a pokemon.
|
||||
* @see {@linkcode RevivalBlessingAttr}
|
||||
*/
|
||||
revivalBlessing(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:number, option: PartyOption) => {
|
||||
if (slotIndex >= 0 && slotIndex < 6) {
|
||||
const pokemon = globalScene.getPlayerParty()[slotIndex];
|
||||
if (!pokemon || !pokemon.isFainted()) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
pokemon.resetTurnData();
|
||||
pokemon.resetStatus();
|
||||
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true);
|
||||
|
||||
if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) {
|
||||
const allyPokemon = this.getAlly();
|
||||
if (slotIndex <= 1) {
|
||||
// Revived ally pokemon
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true));
|
||||
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
// Revived party pokemon, and ally pokemon is fainted
|
||||
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true));
|
||||
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve());
|
||||
}, PartyUiHandler.FilterFainted);
|
||||
});
|
||||
}
|
||||
|
||||
getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
|
||||
if (!evolution) {
|
||||
@ -4728,70 +4690,62 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise to fuse two PlayerPokemon together
|
||||
* @param pokemon The PlayerPokemon to fuse to this one
|
||||
*/
|
||||
fuse(pokemon: PlayerPokemon): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.fusionSpecies = pokemon.species;
|
||||
this.fusionFormIndex = pokemon.formIndex;
|
||||
this.fusionAbilityIndex = pokemon.abilityIndex;
|
||||
this.fusionShiny = pokemon.shiny;
|
||||
this.fusionVariant = pokemon.variant;
|
||||
this.fusionGender = pokemon.gender;
|
||||
this.fusionLuck = pokemon.luck;
|
||||
this.fusionCustomPokemonData = pokemon.customPokemonData;
|
||||
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
||||
this.pauseEvolutions = true;
|
||||
}
|
||||
* Returns a Promise to fuse two PlayerPokemon together
|
||||
* @param pokemon The PlayerPokemon to fuse to this one
|
||||
*/
|
||||
fuse(pokemon: PlayerPokemon): void {
|
||||
this.fusionSpecies = pokemon.species;
|
||||
this.fusionFormIndex = pokemon.formIndex;
|
||||
this.fusionAbilityIndex = pokemon.abilityIndex;
|
||||
this.fusionShiny = pokemon.shiny;
|
||||
this.fusionVariant = pokemon.variant;
|
||||
this.fusionGender = pokemon.gender;
|
||||
this.fusionLuck = pokemon.luck;
|
||||
this.fusionCustomPokemonData = pokemon.customPokemonData;
|
||||
if (pokemon.pauseEvolutions || this.pauseEvolutions) {
|
||||
this.pauseEvolutions = true;
|
||||
}
|
||||
|
||||
globalScene.validateAchv(achvs.SPLICE);
|
||||
globalScene.gameData.gameStats.pokemonFused++;
|
||||
globalScene.validateAchv(achvs.SPLICE);
|
||||
globalScene.gameData.gameStats.pokemonFused++;
|
||||
|
||||
// Store the average HP% that each Pokemon has
|
||||
const maxHp = this.getMaxHp();
|
||||
const newHpPercent = ((pokemon.hp / pokemon.getMaxHp()) + (this.hp / maxHp)) / 2;
|
||||
// Store the average HP% that each Pokemon has
|
||||
const maxHp = this.getMaxHp();
|
||||
const newHpPercent = (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2;
|
||||
|
||||
this.generateName();
|
||||
this.calculateStats();
|
||||
this.generateName();
|
||||
this.calculateStats();
|
||||
|
||||
// Set this Pokemon's HP to the average % of both fusion components
|
||||
this.hp = Math.round(maxHp * newHpPercent);
|
||||
if (!this.isFainted()) {
|
||||
// If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum
|
||||
this.hp = Math.min(this.hp, maxHp);
|
||||
this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two
|
||||
} else if (!pokemon.isFainted()) {
|
||||
// If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero
|
||||
this.hp = Math.max(this.hp, 1);
|
||||
this.status = pokemon.status; // Inherit the other Pokemon's status
|
||||
}
|
||||
// Set this Pokemon's HP to the average % of both fusion components
|
||||
this.hp = Math.round(maxHp * newHpPercent);
|
||||
if (!this.isFainted()) {
|
||||
// If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum
|
||||
this.hp = Math.min(this.hp, maxHp);
|
||||
this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two
|
||||
} else if (!pokemon.isFainted()) {
|
||||
// If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero
|
||||
this.hp = Math.max(this.hp, 1);
|
||||
this.status = pokemon.status; // Inherit the other Pokemon's status
|
||||
}
|
||||
|
||||
this.generateCompatibleTms();
|
||||
this.updateInfo(true);
|
||||
const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon);
|
||||
let partyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
if (partyMemberIndex > fusedPartyMemberIndex) {
|
||||
partyMemberIndex--;
|
||||
}
|
||||
const fusedPartyMemberHeldModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const transferModifiers: Promise<boolean>[] = [];
|
||||
for (const modifier of fusedPartyMemberHeldModifiers) {
|
||||
transferModifiers.push(globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false));
|
||||
}
|
||||
Promise.allSettled(transferModifiers).then(() => {
|
||||
globalScene.updateModifiers(true, true).then(() => {
|
||||
globalScene.removePartyMemberModifiers(fusedPartyMemberIndex);
|
||||
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];
|
||||
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id)));
|
||||
pokemon.destroy();
|
||||
this.updateFusionPalette();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.generateCompatibleTms();
|
||||
this.updateInfo(true);
|
||||
const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon);
|
||||
let partyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
if (partyMemberIndex > fusedPartyMemberIndex) {
|
||||
partyMemberIndex--;
|
||||
}
|
||||
const fusedPartyMemberHeldModifiers = globalScene.findModifiers((m) => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
for (const modifier of fusedPartyMemberHeldModifiers) {
|
||||
globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false);
|
||||
}
|
||||
globalScene.updateModifiers(true, true);
|
||||
globalScene.removePartyMemberModifiers(fusedPartyMemberIndex);
|
||||
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];
|
||||
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id)));
|
||||
pokemon.destroy();
|
||||
this.updateFusionPalette();
|
||||
}
|
||||
|
||||
unfuse(): Promise<void> {
|
||||
|
@ -158,7 +158,7 @@ export abstract class Modifier {
|
||||
* Handles applying of {@linkcode Modifier}
|
||||
* @param args collection of all passed parameters
|
||||
*/
|
||||
abstract apply(...args: unknown[]): boolean | Promise<boolean>;
|
||||
abstract apply(...args: unknown[]): boolean;
|
||||
}
|
||||
|
||||
export abstract class PersistentModifier extends Modifier {
|
||||
@ -1949,7 +1949,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
|
||||
* @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item
|
||||
* @param args Additional arguments passed to {@linkcode ConsumablePokemonModifier.apply}
|
||||
*/
|
||||
abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean | Promise<boolean>;
|
||||
abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean;
|
||||
|
||||
getPokemon() {
|
||||
return globalScene.getPlayerParty().find(p => p.id === this.pokemonId);
|
||||
@ -2288,8 +2288,8 @@ export class FusePokemonModifier extends ConsumablePokemonModifier {
|
||||
* @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon}
|
||||
* @returns always Promise<true>
|
||||
*/
|
||||
override async apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): Promise<boolean> {
|
||||
await playerPokemon.fuse(playerPokemon2);
|
||||
override apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): boolean {
|
||||
playerPokemon.fuse(playerPokemon2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3149,12 +3149,10 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
const randItemIndex = pokemon.randSeedInt(itemModifiers.length);
|
||||
const randItem = itemModifiers[randItemIndex];
|
||||
heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => {
|
||||
if (success) {
|
||||
transferredModifierTypes.push(randItem.type);
|
||||
itemModifiers.splice(randItemIndex, 1);
|
||||
}
|
||||
}));
|
||||
if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) {
|
||||
transferredModifierTypes.push(randItem.type);
|
||||
itemModifiers.splice(randItemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(heldItemTransferPromises).then(() => {
|
||||
|
20
src/phases/load-move-anim-phase.ts
Normal file
20
src/phases/load-move-anim-phase.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
|
||||
import type { Moves } from "#enums/moves";
|
||||
import { Phase } from "#app/phase";
|
||||
|
||||
/**
|
||||
* Phase for synchronous move animation loading.
|
||||
* Should be used when a move invokes another move that
|
||||
* isn't already loaded (e.g. for Metronome)
|
||||
*/
|
||||
export class LoadMoveAnimPhase extends Phase {
|
||||
constructor(protected moveId: Moves) {
|
||||
super();
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
initMoveAnim(this.moveId)
|
||||
.then(() => loadMoveAnimAssets([ this.moveId ], true))
|
||||
.then(() => this.end());
|
||||
}
|
||||
}
|
20
src/phases/move-anim-phase.ts
Normal file
20
src/phases/move-anim-phase.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { MoveAnim } from "#app/data/battle-anims";
|
||||
import { Phase } from "#app/phase";
|
||||
|
||||
/**
|
||||
* Plays the given {@linkcode MoveAnim} sequentially.
|
||||
*/
|
||||
export class MoveAnimPhase<Anim extends MoveAnim> extends Phase {
|
||||
constructor(
|
||||
protected anim: Anim,
|
||||
protected onSubstitute: boolean = false,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
super.start();
|
||||
|
||||
this.anim.play(this.onSubstitute, () => this.end());
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ import {
|
||||
PokemonMultiHitModifier,
|
||||
} from "#app/modifier/modifier";
|
||||
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||
import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils";
|
||||
import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils";
|
||||
import { type nil } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { Moves } from "#enums/moves";
|
||||
@ -143,86 +143,86 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const move = this.move.getMove();
|
||||
|
||||
// Assume single target for override
|
||||
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual).then(() => {
|
||||
// If other effects were overriden, stop this phase before they can be applied
|
||||
if (overridden.value) {
|
||||
return this.end();
|
||||
}
|
||||
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual);
|
||||
|
||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||
// If other effects were overriden, stop this phase before they can be applied
|
||||
if (overridden.value) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
// If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that
|
||||
// the move executes correctly (ensures all hits of a multi-hit are properly calculated)
|
||||
if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) {
|
||||
user.turnData.hitsLeft = -1;
|
||||
user.turnData.hitCount = 0;
|
||||
user.turnData.extraTurns--;
|
||||
}
|
||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||
|
||||
/**
|
||||
* If this phase is for the first hit of the invoked move,
|
||||
* resolve the move's total hit count. This block combines the
|
||||
* effects of the move itself, Parental Bond, and Multi-Lens to do so.
|
||||
*/
|
||||
if (user.turnData.hitsLeft === -1) {
|
||||
const hitCount = new NumberHolder(1);
|
||||
// Assume single target for multi hit
|
||||
applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount);
|
||||
// If Parental Bond is applicable, add another hit
|
||||
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null);
|
||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||
// Set the user's relevant turnData fields to reflect the final hit count
|
||||
user.turnData.hitCount = hitCount.value;
|
||||
user.turnData.hitsLeft = hitCount.value;
|
||||
}
|
||||
// If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that
|
||||
// the move executes correctly (ensures all hits of a multi-hit are properly calculated)
|
||||
if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) {
|
||||
user.turnData.hitsLeft = -1;
|
||||
user.turnData.hitCount = 0;
|
||||
user.turnData.extraTurns--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log to be entered into the user's move history once the move result is resolved.
|
||||
* Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully
|
||||
* used in the sense of "Does it have an effect on the user?".
|
||||
*/
|
||||
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
||||
/**
|
||||
* If this phase is for the first hit of the invoked move,
|
||||
* resolve the move's total hit count. This block combines the
|
||||
* effects of the move itself, Parental Bond, and Multi-Lens to do so.
|
||||
*/
|
||||
if (user.turnData.hitsLeft === -1) {
|
||||
const hitCount = new NumberHolder(1);
|
||||
// Assume single target for multi hit
|
||||
applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount);
|
||||
// If Parental Bond is applicable, add another hit
|
||||
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null);
|
||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||
// Set the user's relevant turnData fields to reflect the final hit count
|
||||
user.turnData.hitCount = hitCount.value;
|
||||
user.turnData.hitsLeft = hitCount.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores results of hit checks of the invoked move against all targets, organized by battler index.
|
||||
* @see {@linkcode hitCheck}
|
||||
*/
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
|
||||
const hasActiveTargets = targets.some(t => t.isActive(true));
|
||||
/**
|
||||
* Log to be entered into the user's move history once the move result is resolved.
|
||||
* Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully
|
||||
* used in the sense of "Does it have an effect on the user?".
|
||||
*/
|
||||
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
||||
|
||||
/** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */
|
||||
const isImmune = targets[0]?.hasAbilityWithAttr(TypeImmunityAbAttr)
|
||||
&& (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
|
||||
&& !targets[0]?.getTag(SemiInvulnerableTag);
|
||||
/**
|
||||
* Stores results of hit checks of the invoked move against all targets, organized by battler index.
|
||||
* @see {@linkcode hitCheck}
|
||||
*/
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
|
||||
const hasActiveTargets = targets.some(t => t.isActive(true));
|
||||
|
||||
/** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */
|
||||
const isImmune = targets[0]?.hasAbilityWithAttr(TypeImmunityAbAttr)
|
||||
&& (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
|
||||
&& !targets[0]?.getTag(SemiInvulnerableTag);
|
||||
|
||||
const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected && targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
||||
|
||||
/**
|
||||
* If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target
|
||||
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||
*/
|
||||
if (!hasActiveTargets || (!mayBounce && !move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) {
|
||||
this.stopMultiHit();
|
||||
if (hasActiveTargets) {
|
||||
globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" }));
|
||||
moveHistoryEntry.result = MoveResult.MISS;
|
||||
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("battle:attackFailed"));
|
||||
moveHistoryEntry.result = MoveResult.FAIL;
|
||||
}
|
||||
user.pushMoveHistory(moveHistoryEntry);
|
||||
return this.end();
|
||||
/**
|
||||
* If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target
|
||||
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||
*/
|
||||
if (!hasActiveTargets || (!mayBounce && !move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) {
|
||||
this.stopMultiHit();
|
||||
if (hasActiveTargets) {
|
||||
globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" }));
|
||||
moveHistoryEntry.result = MoveResult.MISS;
|
||||
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("battle:attackFailed"));
|
||||
moveHistoryEntry.result = MoveResult.FAIL;
|
||||
}
|
||||
user.pushMoveHistory(moveHistoryEntry);
|
||||
return this.end();
|
||||
}
|
||||
|
||||
/** All move effect attributes are chained together in this array to be applied asynchronously. */
|
||||
const applyAttrs: Promise<void>[] = [];
|
||||
|
||||
const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
||||
// Move animation only needs one target
|
||||
new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => {
|
||||
const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
||||
// Move animation only needs one target
|
||||
new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(
|
||||
move.hitsSubstitute(user, this.getFirstTarget()!),
|
||||
() => {
|
||||
/** Has the move successfully hit a target (for damage) yet? */
|
||||
let hasHit: boolean = false;
|
||||
|
||||
@ -313,7 +313,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
/** Does this phase represent the invoked move's first strike? */
|
||||
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
|
||||
const firstHit = user.turnData.hitsLeft === user.turnData.hitCount;
|
||||
|
||||
// Only log the move's result on the first strike
|
||||
if (firstHit) {
|
||||
@ -363,7 +363,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
/** Does this phase represent the invoked move's last strike? */
|
||||
const lastHit = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive());
|
||||
const lastHit = user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive();
|
||||
|
||||
/**
|
||||
* If the user can change forms by using the invoked move,
|
||||
@ -381,43 +381,29 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Promise that applies *all* effects from the invoked move's MoveEffectAttrs.
|
||||
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
|
||||
* type requires different conditions to be met with respect to the move's hit result.
|
||||
*/
|
||||
const k = new Promise<void>((resolve) => {
|
||||
//Start promise chain and apply PRE_APPLY move attributes
|
||||
let promiseChain: Promise<void | null> = applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||
attr instanceof MoveEffectAttr
|
||||
&& attr.trigger === MoveEffectTrigger.PRE_APPLY
|
||||
&& (!attr.firstHitOnly || firstHit)
|
||||
&& (!attr.lastHitOnly || lastHit)
|
||||
&& hitResult !== HitResult.NO_EFFECT, user, target, move);
|
||||
applyFilteredMoveAttrs(
|
||||
(attr: MoveAttr) =>
|
||||
attr instanceof MoveEffectAttr &&
|
||||
attr.trigger === MoveEffectTrigger.PRE_APPLY &&
|
||||
(!attr.firstHitOnly || firstHit) &&
|
||||
(!attr.lastHitOnly || lastHit) &&
|
||||
hitResult !== HitResult.NO_EFFECT,
|
||||
user,
|
||||
target,
|
||||
move,
|
||||
);
|
||||
|
||||
/** Don't complete if the move failed */
|
||||
if (hitResult === HitResult.FAIL) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
/** Apply Move/Ability Effects in correct order */
|
||||
promiseChain = promiseChain
|
||||
.then(this.applySelfTargetEffects(user, target, firstHit, lastHit));
|
||||
if (hitResult !== HitResult.FAIL) {
|
||||
this.applySelfTargetEffects(user, target, firstHit, lastHit);
|
||||
|
||||
if (hitResult !== HitResult.NO_EFFECT) {
|
||||
promiseChain
|
||||
.then(this.applyPostApplyEffects(user, target, firstHit, lastHit))
|
||||
.then(this.applyHeldItemFlinchCheck(user, target, dealsDamage))
|
||||
.then(this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget))
|
||||
.then(() => resolve());
|
||||
this.applyPostApplyEffects(user, target, firstHit, lastHit);
|
||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||
this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget);
|
||||
} else {
|
||||
promiseChain
|
||||
.then(() => applyMoveAttrs(NoEffectAttr, user, null, move))
|
||||
.then(resolve);
|
||||
applyMoveAttrs(NoEffectAttr, user, null, move);
|
||||
}
|
||||
});
|
||||
|
||||
applyAttrs.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply queued phases
|
||||
@ -425,41 +411,35 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
globalScene.appendToPhase(queuedPhases, MoveEndPhase);
|
||||
}
|
||||
// Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved
|
||||
const postTarget = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) ?
|
||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) :
|
||||
null;
|
||||
|
||||
if (postTarget) {
|
||||
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
|
||||
applyAttrs[applyAttrs.length - 1].then(() => postTarget);
|
||||
} else { // Otherwise, push a new asynchronous move effect
|
||||
applyAttrs.push(postTarget);
|
||||
}
|
||||
if (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) {
|
||||
applyFilteredMoveAttrs(
|
||||
(attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET,
|
||||
user,
|
||||
null,
|
||||
move,
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for all move effects to finish applying, then end this phase
|
||||
Promise.allSettled(applyAttrs).then(() => {
|
||||
/**
|
||||
* Remove the target's substitute (if it exists and has expired)
|
||||
* after all targeted effects have applied.
|
||||
* This prevents blocked effects from applying until after this hit resolves.
|
||||
*/
|
||||
targets.forEach(target => {
|
||||
const substitute = target.getTag(SubstituteTag);
|
||||
if (substitute && substitute.hp <= 0) {
|
||||
target.lapseTag(BattlerTagType.SUBSTITUTE);
|
||||
}
|
||||
});
|
||||
|
||||
const moveType = user.getMoveType(move, true);
|
||||
if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) {
|
||||
user.stellarTypesBoosted.push(moveType);
|
||||
/**
|
||||
* Remove the target's substitute (if it exists and has expired)
|
||||
* after all targeted effects have applied.
|
||||
* This prevents blocked effects from applying until after this hit resolves.
|
||||
*/
|
||||
targets.forEach((target) => {
|
||||
const substitute = target.getTag(SubstituteTag);
|
||||
if (substitute && substitute.hp <= 0) {
|
||||
target.lapseTag(BattlerTagType.SUBSTITUTE);
|
||||
}
|
||||
|
||||
this.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const moveType = user.getMoveType(move, true);
|
||||
if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) {
|
||||
user.stellarTypesBoosted.push(moveType);
|
||||
}
|
||||
|
||||
this.end();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public override end(): void {
|
||||
@ -500,7 +480,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param lastHit - `true` if this is the last hit in a multi-hit attack
|
||||
* @returns a function intended to be passed into a `then()` call.
|
||||
*/
|
||||
protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise<void | null> {
|
||||
protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => void {
|
||||
return () => applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||
attr instanceof MoveEffectAttr
|
||||
&& attr.trigger === MoveEffectTrigger.POST_APPLY
|
||||
@ -518,7 +498,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param lastHit - `true` if this is the last hit in a multi-hit attack
|
||||
* @returns a function intended to be passed into a `then()` call.
|
||||
*/
|
||||
protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise<void | null> {
|
||||
protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => void {
|
||||
return () => applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||
attr instanceof MoveEffectAttr
|
||||
&& attr.trigger === MoveEffectTrigger.POST_APPLY
|
||||
@ -537,8 +517,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move}
|
||||
* @returns a function intended to be passed into a `then()` call.
|
||||
*/
|
||||
protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): Promise<void> {
|
||||
return applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||
protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): void {
|
||||
applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||
attr instanceof MoveEffectAttr
|
||||
&& attr.trigger === MoveEffectTrigger.HIT
|
||||
&& (!attr.firstHitOnly || firstHit)
|
||||
@ -554,21 +534,18 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||
* @returns a `Promise` intended to be passed into a `then()` call.
|
||||
*/
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): Promise<void | null> {
|
||||
return executeIf(!target.isFainted() || target.canApplyAbility(), () =>
|
||||
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult)
|
||||
.then(() => {
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||
if (!target.isFainted() || target.canApplyAbility()) {
|
||||
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult);
|
||||
|
||||
if (!this.move.getMove().hitsSubstitute(user, target)) {
|
||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
|
||||
}
|
||||
if (!this.move.getMove().hitsSubstitute(user, target)) {
|
||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
|
||||
}
|
||||
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
|
||||
})
|
||||
);
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -583,17 +560,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move}
|
||||
* @returns a function intended to be passed into a `then()` call.
|
||||
*/
|
||||
protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, isProtected : boolean, hitResult: HitResult, firstTarget: boolean) : () => Promise<void | null> {
|
||||
return () => executeIf(!isProtected, () =>
|
||||
this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget).then(() =>
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult)).then(() =>
|
||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult)).then(() => { // Item Stealing Effects
|
||||
|
||||
if (this.move.getMove() instanceof AttackMove) {
|
||||
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
||||
}
|
||||
})
|
||||
);
|
||||
protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean, isProtected: boolean, hitResult: HitResult, firstTarget: boolean): void {
|
||||
if (!isProtected) {
|
||||
this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget);
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult);
|
||||
if (this.move.getMove() instanceof AttackMove) {
|
||||
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
71
src/phases/pokemon-transform-phase.ts
Normal file
71
src/phases/pokemon-transform-phase.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
|
||||
/**
|
||||
* Transforms a Pokemon into another Pokemon on the field.
|
||||
* Used for Transform (move) and Imposter (ability)
|
||||
*/
|
||||
export class PokemonTransformPhase extends PokemonPhase {
|
||||
protected targetIndex: BattlerIndex;
|
||||
private playSound: boolean;
|
||||
|
||||
constructor(userIndex: BattlerIndex, targetIndex: BattlerIndex, playSound: boolean = false) {
|
||||
super(userIndex);
|
||||
|
||||
this.targetIndex = targetIndex;
|
||||
this.playSound = playSound;
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
const user = this.getPokemon();
|
||||
const target = globalScene.getField(true).find((p) => p.getBattlerIndex() === this.targetIndex);
|
||||
|
||||
if (!target) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
// Power Trick's effect is removed after using Transform
|
||||
user.removeTag(BattlerTagType.POWER_TRICK);
|
||||
|
||||
// Copy all stats (except HP)
|
||||
for (const s of EFFECTIVE_STATS) {
|
||||
user.setStat(s, target.getStat(s, false), false);
|
||||
}
|
||||
|
||||
// Copy all stat stages
|
||||
for (const s of BATTLE_STATS) {
|
||||
user.setStatStage(s, target.getStatStage(s));
|
||||
}
|
||||
|
||||
user.summonData.moveset = target.getMoveset().map((m) => {
|
||||
if (m) {
|
||||
// If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5.
|
||||
return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5));
|
||||
} else {
|
||||
console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`);
|
||||
return new PokemonMove(Moves.NONE);
|
||||
}
|
||||
});
|
||||
user.summonData.types = target.getTypes();
|
||||
|
||||
const promises = [ user.updateInfo() ];
|
||||
|
||||
if (this.playSound) {
|
||||
globalScene.playSound("battle_anims/PRSFX- Transform");
|
||||
}
|
||||
|
||||
promises.push(
|
||||
user.loadAssets(false).then(() => {
|
||||
user.playAnim();
|
||||
user.updateInfo();
|
||||
}),
|
||||
);
|
||||
|
||||
Promise.allSettled(promises).then(() => this.end());
|
||||
}
|
||||
}
|
61
src/phases/revival-blessing-phase.ts
Normal file
61
src/phases/revival-blessing-phase.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { PartyOption } from "#app/ui/party-ui-handler";
|
||||
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import * as Utils from "#app/utils";
|
||||
import { BattlePhase } from "#app/phases/battle-phase";
|
||||
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
||||
/**
|
||||
* Sets the Party UI and handles the effect of Revival Blessing
|
||||
* when used by one of the player's Pokemon.
|
||||
*/
|
||||
export class RevivalBlessingPhase extends BattlePhase {
|
||||
constructor(protected user: PlayerPokemon) {
|
||||
super();
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
globalScene.ui.setMode(
|
||||
Mode.PARTY,
|
||||
PartyUiMode.REVIVAL_BLESSING,
|
||||
this.user.getFieldIndex(),
|
||||
(slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex >= 0 && slotIndex < 6) {
|
||||
const pokemon = globalScene.getPlayerParty()[slotIndex];
|
||||
if (!pokemon || !pokemon.isFainted()) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
pokemon.resetTurnData();
|
||||
pokemon.resetStatus();
|
||||
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true);
|
||||
|
||||
if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) {
|
||||
const allyPokemon = this.user.getAlly();
|
||||
if (slotIndex <= 1) {
|
||||
// Revived ally pokemon
|
||||
globalScene.unshiftPhase(
|
||||
new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true),
|
||||
);
|
||||
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
// Revived party pokemon, and ally pokemon is fainted
|
||||
globalScene.unshiftPhase(
|
||||
new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true),
|
||||
);
|
||||
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => this.end());
|
||||
},
|
||||
PartyUiHandler.FilterFainted,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user