Update interactions to use generator

This commit is contained in:
Dean 2025-09-20 17:39:24 -07:00
parent 87e6095a00
commit 6a47cb01da
17 changed files with 146 additions and 131 deletions

View File

@ -802,6 +802,10 @@ export class BattleScene extends SceneBase {
* @param activeOnly - Whether to consider only active pokemon (as described by {@linkcode Pokemon.isActive()}); default `false`.
* If `true`, will also remove all `null` values from the array.
* @returns An array of {@linkcode Pokemon}, as described above.
*
* @remarks
* This should *only* be used in instances where speed order is not relevant.
* If speed order matters, use {@linkcode inSpeedOrder}.
*/
public getField(activeOnly = false): Pokemon[] {
const ret: Pokemon[] = new Array(4).fill(null);

View File

@ -74,6 +74,7 @@ import {
randSeedItem,
toDmgValue,
} from "#utils/common";
import { inSpeedOrder } from "#utils/speed-order-generator";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
@ -2769,7 +2770,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr {
return;
}
for (const opponent of pokemon.getOpponents()) {
for (const opponent of pokemon.getOpponentsGenerator()) {
const cancelled = new BooleanHolder(false);
if (this.intimidate) {
const params: AbAttrParamsWithCancel = { pokemon: opponent, cancelled, simulated };
@ -3079,16 +3080,12 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
if (simulated) {
return;
}
const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
const allowedParty = party.filter(p => p.isAllowedInBattle());
for (const pokemon of allowedParty) {
if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) {
globalScene.phaseManager.queueMessage(
getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)),
);
pokemon.resetStatus(false);
pokemon.updateInfo();
for (const p of pokemon.getAlliedField()) {
if (p.status && this.statusEffect.includes(p.status.effect)) {
globalScene.phaseManager.queueMessage(getStatusEffectHealText(p.status.effect, getPokemonNameWithAffix(p)));
p.resetStatus(false);
p.updateInfo();
}
}
}
@ -4302,7 +4299,7 @@ export class FriskAbAttr extends PostSummonAbAttr {
override apply({ simulated, pokemon }: AbAttrBaseParams): void {
if (!simulated) {
for (const opponent of pokemon.getOpponents()) {
for (const opponent of pokemon.getOpponentsGenerator()) {
globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:frisk", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
@ -4869,7 +4866,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
return;
}
for (const opp of pokemon.getOpponents()) {
for (const opp of pokemon.getOpponentsGenerator()) {
if ((opp.status?.effect !== StatusEffect.SLEEP && !opp.hasAbility(AbilityId.COMATOSE)) || opp.switchOutStatus) {
continue;
}
@ -5420,10 +5417,9 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
}
const cancelled = new BooleanHolder(false);
// TODO: This should be in speed order
globalScene
.getField(true)
.forEach(p => applyAbAttrs("FieldPreventExplosiveMovesAbAttr", { pokemon: p, cancelled, simulated }));
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
applyAbAttrs("FieldPreventExplosiveMovesAbAttr", { pokemon: p, cancelled, simulated });
}
if (cancelled.value) {
return false;

View File

@ -77,6 +77,7 @@ import type {
} from "#types/arena-tags";
import type { Mutable } from "#types/type-helpers";
import { BooleanHolder, type NumberHolder, toDmgValue } from "#utils/common";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
/** Interface containing the serializable fields of ArenaTagData. */
@ -187,7 +188,7 @@ export abstract class ArenaTag implements BaseArenaTag {
/**
* Helper function that retrieves the Pokemon affected
* @returns list of PlayerPokemon or EnemyPokemon on the field
* @returns A list of PlayerPokemon or EnemyPokemon on the field, not in speed order
*/
public getAffectedPokemon(): Pokemon[] {
switch (this.side) {
@ -1236,7 +1237,7 @@ export class GravityTag extends SerializableArenaTag {
onAdd(_arena: Arena): void {
globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
globalScene.getField(true).forEach(pokemon => {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING);
pokemon.removeTag(BattlerTagType.TELEKINESIS);
@ -1244,7 +1245,7 @@ export class GravityTag extends SerializableArenaTag {
pokemon.addTag(BattlerTagType.INTERRUPTED);
}
}
});
}
}
onRemove(_arena: Arena): void {
@ -1279,9 +1280,7 @@ class TailwindTag extends SerializableArenaTag {
);
}
const field = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const pokemon of field) {
for (const pokemon of source.getAlliedField()) {
// Apply the CHARGED tag to party members with the WIND_POWER ability
// TODO: This should not be handled here
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
@ -1394,27 +1393,26 @@ class FireGrassPledgeTag extends SerializableArenaTag {
}
override lapse(arena: Arena): boolean {
const field: Pokemon[] =
this.side === ArenaTagSide.PLAYER ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const pokemon of inSpeedOrder(this.side)) {
if (pokemon.isOfType(PokemonType.FIRE) || pokemon.switchOutStatus) {
continue;
}
field
.filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus)
.forEach(pokemon => {
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:fireGrassPledgeLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}),
);
// TODO: Replace this with a proper animation
globalScene.phaseManager.unshiftNew(
"CommonAnimPhase",
pokemon.getBattlerIndex(),
pokemon.getBattlerIndex(),
CommonAnim.MAGMA_STORM,
);
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
});
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:fireGrassPledgeLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}),
);
// TODO: Replace this with a proper animation
globalScene.phaseManager.unshiftNew(
"CommonAnimPhase",
pokemon.getBattlerIndex(),
pokemon.getBattlerIndex(),
CommonAnim.MAGMA_STORM,
);
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
}
return super.lapse(arena);
}
@ -1532,7 +1530,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag {
if (pokemon) {
this.playActivationMessage(pokemon);
for (const fieldPokemon of globalScene.getField(true)) {
for (const fieldPokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
// the appropriate attributes (preLEaveField and IllusionBreak)
@ -1573,7 +1571,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag {
globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
}
for (const pokemon of globalScene.getField(true)) {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
[true, false].forEach(passive => {

View File

@ -94,6 +94,7 @@ import i18next from "i18next";
import { applyChallenges } from "#utils/challenge-utils";
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
import type { AbstractConstructor } from "#types/type-helpers";
import { inSpeedOrder } from "#utils/speed-order-generator";
/**
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
@ -859,8 +860,9 @@ export abstract class Move implements Localizable {
aura.apply({pokemon: source, simulated, opponent: target, move: this, power});
}
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
alliedField.forEach(p => applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power}));
for (const p of source.getAlliedField()) {
applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power});
}
power.value *= typeChangeMovePowerMultiplier.value;
@ -5948,8 +5950,10 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr {
return false;
}
globalScene.getField(true).forEach(pokemon =>
pokemon.findAndRemoveTags(tag => tag.tagType === BattlerTagType.SUBSTITUTE));
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
pokemon.findAndRemoveTags(tag => tag.tagType === BattlerTagType.SUBSTITUTE);
}
return true;
}
}
@ -7955,7 +7959,9 @@ const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
// temporary workaround to prevent displaying the message during enemy command phase
// TODO: either move this, or make the move condition func have a `simulated` param
const simulated = globalScene.phaseManager.getCurrentPhase()?.is('EnemyCommandPhase');
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled, simulated}));
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled, simulated});
}
// Queue a message if an ability prevented usage of the move
if (!simulated && cancelled.value) {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));

View File

@ -38,6 +38,7 @@ import type { Move } from "#moves/move";
import type { AbstractConstructor } from "#types/type-helpers";
import { type Constructor, NumberHolder, randSeedInt } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { inSpeedOrder } from "#utils/speed-order-generator";
export class Arena {
public biomeType: BiomeId;
@ -356,15 +357,12 @@ export class Arena {
globalScene.phaseManager.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
}
globalScene
.getField(true)
.filter(p => p.isOnField())
.map(pokemon => {
pokemon.findAndRemoveTags(
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
);
applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather });
});
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
pokemon.findAndRemoveTags(
tag => "weatherTypes" in tag && !(tag.weatherTypes as WeatherType[]).find(t => t === weather),
);
applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather });
}
return true;
}
@ -374,7 +372,7 @@ export class Arena {
* @param source - The Pokemon causing the changes by removing itself from the field
*/
triggerWeatherBasedFormChanges(source?: Pokemon): void {
globalScene.getField(true).forEach(p => {
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
// TODO - This is a bandaid. Abilities leaving the field needs a better approach than
// calling this method for every switch out that happens
if (p === source) {
@ -386,23 +384,23 @@ export class Arena {
if (isCastformWithForecast || isCherrimWithFlowerGift) {
globalScene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
}
});
}
}
/**
* Function to trigger all weather based form changes back into their normal forms
*/
triggerWeatherBasedFormChangesToNormal(): void {
globalScene.getField(true).forEach(p => {
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
const isCastformWithForecast =
p.hasAbility(AbilityId.FORECAST, false, true) && p.species.speciesId === SpeciesId.CASTFORM;
const isCherrimWithFlowerGift =
p.hasAbility(AbilityId.FLOWER_GIFT, false, true) && p.species.speciesId === SpeciesId.CHERRIM;
if (isCastformWithForecast || isCherrimWithFlowerGift) {
return globalScene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
globalScene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
}
});
}
}
/** Returns whether or not the terrain can be set to {@linkcode terrain} */
@ -451,16 +449,13 @@ export class Arena {
globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType));
}
globalScene
.getField(true)
.filter(p => p.isOnField())
.map(pokemon => {
pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
);
applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
});
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
);
applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
}
return true;
}

View File

@ -172,6 +172,7 @@ import {
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
import { inSpeedOrder } from "#utils/speed-order-generator";
import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities";
import i18next from "i18next";
import Phaser from "phaser";
@ -2347,15 +2348,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
/** Holds whether the pokemon is trapped due to an ability */
const trapped = new BooleanHolder(false);
/**
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
*/
const opposingFieldUnfiltered = this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField();
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
for (const opponent of opposingField) {
applyAbAttrs("CheckTrappedAbAttr", { pokemon: opponent, trapped, opponent: this, simulated }, trappedAbMessages);
for (const opponent of inSpeedOrder(this.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER)) {
if (opponent.switchOutStatus === false) {
applyAbAttrs(
"CheckTrappedAbAttr",
{ pokemon: opponent, trapped, opponent: this, simulated },
trappedAbMessages,
);
}
}
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -2465,15 +2465,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (!cancelledHolder.value) {
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
defendingSidePlayField.forEach(p =>
for (const p of this.getAlliedField()) {
applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
pokemon: p,
opponent: source,
move,
cancelled: cancelledHolder,
}),
);
});
}
}
}
@ -3244,6 +3243,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
);
}
public getOpponentsGenerator(): Generator<Pokemon, number> {
return inSpeedOrder(this.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
}
getOpponentDescriptor(): string {
return this.isPlayer() ? i18next.t("arenaTag:opposingTeam") : i18next.t("arenaTag:yourTeam");
}
@ -3255,10 +3258,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Gets the Pokémon on the allied field.
*
* @returns An array of Pokémon on the allied field.
* @returns An generator of Pokémon on the allied field in speed order.
*/
getAlliedField(): Pokemon[] {
return this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
getAlliedField(): Generator<Pokemon, number> {
return inSpeedOrder(this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
}
/**
@ -4022,16 +4025,15 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: stubTag, cancelled, simulated: true });
const userField = this.getAlliedField();
userField.forEach(pokemon =>
for (const pokemon of this.getAlliedField()) {
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
pokemon,
tag: stubTag,
cancelled,
simulated: true,
target: this,
}),
);
});
}
return !cancelled.value;
}
@ -5576,10 +5578,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
leaveField(clearEffects = true, hideInfo = true, destroy = false) {
this.resetSprite();
this.resetTurnData();
globalScene
.getField(true)
.filter(p => p !== this)
.forEach(p => p.removeTagsBySourceId(this.id));
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
if (p !== this) {
p.removeTagsBySourceId(this.id);
}
}
if (clearEffects) {
this.destroySubstitute();

View File

@ -1915,9 +1915,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
// Remove the Pokemon's FAINT status
pokemon.resetStatus(true, false, true, false);
// Reapply Commander on the Pokemon's side of the field, if applicable
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) {
for (const p of pokemon.getAlliedField()) {
applyAbAttrs("CommanderAbAttr", { pokemon: p });
}
return true;

View File

@ -1,10 +1,12 @@
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { FieldPhase } from "#phases/field-phase";
import { NumberHolder } from "#utils/common";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
export class AttemptRunPhase extends FieldPhase {
@ -15,16 +17,14 @@ export class AttemptRunPhase extends FieldPhase {
// Increment escape attempts count on entry
const currentAttempts = globalScene.currentBattle.escapeAttempts++;
const activePlayerField = globalScene.getPlayerField(true);
const enemyField = globalScene.getEnemyField();
const escapeRoll = globalScene.randBattleSeedInt(100);
const escapeChance = new NumberHolder(this.calculateEscapeChance(currentAttempts));
activePlayerField.forEach(pokemon => {
for (const pokemon of inSpeedOrder(ArenaTagSide.PLAYER)) {
applyAbAttrs("RunSuccessAbAttr", { pokemon, chance: escapeChance });
});
}
if (escapeRoll < escapeChance.value) {
enemyField.forEach(pokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon }));
@ -56,7 +56,7 @@ export class AttemptRunPhase extends FieldPhase {
globalScene.phaseManager.pushNew("NewBattlePhase");
} else {
activePlayerField.forEach(p => {
globalScene.getPlayerField(true).forEach(p => {
p.turnData.failedRunAway = true;
});

View File

@ -1,13 +1,14 @@
import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { inSpeedOrder } from "#utils/speed-order-generator";
export class CheckStatusEffectPhase extends Phase {
public readonly phaseName = "CheckStatusEffectPhase";
start() {
const field = globalScene.getField();
for (const p of field) {
if (p?.status?.isPostTurn()) {
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
if (p.status?.isPostTurn()) {
globalScene.phaseManager.unshiftNew("PostTurnStatusEffectPhase", p.getBattlerIndex());
}
}

View File

@ -5,6 +5,7 @@ import { FRIENDSHIP_LOSS_FROM_FAINT } from "#balance/starters";
import { allMoves } from "#data/data-lists";
import { battleSpecDialogue } from "#data/dialogue";
import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { BattleSpec } from "#enums/battle-spec";
import { BattleType } from "#enums/battle-type";
import type { BattlerIndex } from "#enums/battler-index";
@ -17,6 +18,7 @@ import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
import { PokemonInstantReviveModifier } from "#modifiers/modifier";
import { PokemonMove } from "#moves/pokemon-move";
import { PokemonPhase } from "#phases/pokemon-phase";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
export class FaintPhase extends PokemonPhase {
@ -126,8 +128,7 @@ export class FaintPhase extends PokemonPhase {
applyAbAttrs("PostFaintAbAttr", { pokemon });
}
const alivePlayField = globalScene.getField(true);
for (const p of alivePlayField) {
for (const p of inSpeedOrder(ArenaTagSide.BOTH)) {
applyAbAttrs("PostKnockOutAbAttr", { pokemon: p, victim: pokemon });
}
if (pokemon.turnData.attacksReceived?.length > 0) {

View File

@ -1,12 +1,13 @@
import { globalScene } from "#app/global-scene";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type { Pokemon } from "#field/pokemon";
import { BattlePhase } from "#phases/battle-phase";
import { inSpeedOrder } from "#utils/speed-order-generator";
type PokemonFunc = (pokemon: Pokemon) => void;
export abstract class FieldPhase extends BattlePhase {
executeForAll(func: PokemonFunc): void {
for (const pokemon of globalScene.getField(true)) {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
func(pokemon);
}
}

View File

@ -10,6 +10,7 @@ import { getStatusEffectActivationText, getStatusEffectHealText } from "#data/st
import { getTerrainBlockMessage } from "#data/terrain";
import { getWeatherBlockMessage } from "#data/weather";
import { AbilityId } from "#enums/ability-id";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { BattlerTagType } from "#enums/battler-tag-type";
@ -29,6 +30,7 @@ import type { PokemonMove } from "#moves/pokemon-move";
import type { TurnMove } from "#types/turn-move";
import { NumberHolder } from "#utils/common";
import { enumValueToKey } from "#utils/enums";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
export class MovePhase extends PokemonPhase {
@ -380,9 +382,9 @@ export class MovePhase extends PokemonPhase {
// TODO: This needs to go at the end of `MoveEffectPhase` to check move results
const dancerModes: MoveUseMode[] = [MoveUseMode.INDIRECT, MoveUseMode.REFLECTED] as const;
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
globalScene.getField(true).forEach(pokemon => {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
applyAbAttrs("PostMoveUsedAbAttr", { pokemon, move: this.move, source: this.pokemon, targets: this.targets });
});
}
}
}
@ -510,22 +512,22 @@ export class MovePhase extends PokemonPhase {
const redirectTarget = new NumberHolder(currentTarget);
// check move redirection abilities of every pokemon *except* the user.
globalScene
.getField(true)
.filter(p => p !== this.pokemon)
.forEach(pokemon => {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
if (pokemon !== this.pokemon) {
applyAbAttrs("RedirectMoveAbAttr", {
pokemon,
moveId: this.move.moveId,
targetIndex: redirectTarget,
sourcePokemon: this.pokemon,
});
});
}
}
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
let redirectedByAbility = currentTarget !== redirectTarget.value;
// check for center-of-attention tags (note that this will override redirect abilities)
// TODO The target of redirection should be the first viable pokemon that used a redirection move in the turn
this.pokemon.getOpponents(true).forEach(p => {
const redirectTag = p.getTag(CenterOfAttentionTag);

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase";
import { getCharVariantFromDialogue } from "#data/dialogue";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { BattleSpec } from "#enums/battle-spec";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { BattlerTagType } from "#enums/battler-tag-type";
@ -15,6 +16,7 @@ import { transitionMysteryEncounterIntroVisuals } from "#mystery-encounters/enco
import type { MysteryEncounterOption, OptionPhaseCallback } from "#mystery-encounters/mystery-encounter-option";
import { SeenEncounterData } from "#mystery-encounters/mystery-encounter-save-data";
import { randSeedItem } from "#utils/common";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
/**
@ -216,7 +218,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
// Lapse any residual flinches/endures but ignore all other turn-end battle tags
const includedLapseTags = [BattlerTagType.FLINCHED, BattlerTagType.ENDURING];
globalScene.getField(true).forEach(pokemon => {
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
const tags = pokemon.summonData.tags;
tags
.filter(
@ -229,7 +231,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
t.onRemove(pokemon);
tags.splice(tags.indexOf(t), 1);
});
});
}
// Remove any status tick phases
globalScene.phaseManager.removeAllPhasesOfType("PostTurnStatusEffectPhase");
@ -428,7 +430,9 @@ export class MysteryEncounterBattlePhase extends Phase {
}
} else {
if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) {
globalScene.getPlayerField().forEach(pokemon => pokemon.lapseTag(BattlerTagType.COMMANDED));
for (const pokemon of inSpeedOrder(ArenaTagSide.PLAYER)) {
pokemon.lapseTag(BattlerTagType.COMMANDED);
}
globalScene.phaseManager.pushNew("ReturnPhase", 1);
}
globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false);

View File

@ -39,8 +39,7 @@ export class PostSummonPhase extends PokemonPhase {
) {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
}
const field = pokemon.isPlayer() ? globalScene.getPlayerField(true) : globalScene.getEnemyField(true);
for (const p of field) {
for (const p of pokemon.getAlliedField()) {
applyAbAttrs("CommanderAbAttr", { pokemon: p });
}

View File

@ -210,7 +210,7 @@ export class StatStageChangePhase extends PokemonPhase {
}
if (stages.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) {
for (const opponent of pokemon.getOpponentsGenerator()) {
applyAbAttrs("StatStageChangeCopyAbAttr", { pokemon: opponent, stats: this.stats, numStages: stages.value });
}
}

View File

@ -5,12 +5,14 @@ import { SubstituteTag } from "#data/battler-tags";
import { allMoves } from "#data/data-lists";
import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers";
import { getPokeballTintColor } from "#data/pokeball";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { Command } from "#enums/command";
import { SwitchType } from "#enums/switch-type";
import { TrainerSlot } from "#enums/trainer-slot";
import type { Pokemon } from "#field/pokemon";
import { SwitchEffectTransferModifier } from "#modifiers/modifier";
import { SummonPhase } from "#phases/summon-phase";
import { inSpeedOrder } from "#utils/speed-order-generator";
import i18next from "i18next";
export class SwitchSummonPhase extends SummonPhase {
@ -69,9 +71,9 @@ export class SwitchSummonPhase extends SummonPhase {
}
const pokemon = this.getPokemon();
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon =>
enemyPokemon.removeTagsBySourceId(pokemon.id),
);
for (const enemyPokemon of inSpeedOrder(this.player ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER)) {
enemyPokemon.removeTagsBySourceId(pokemon.id);
}
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
const substitute = pokemon.getTag(SubstituteTag);

View File

@ -1,4 +1,4 @@
import { Pokemon } from "#app/field/pokemon";
import type { Pokemon } from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import { BooleanHolder, randSeedShuffle } from "#app/utils/common";
import { ArenaTagType } from "#enums/arena-tag-type";
@ -38,11 +38,16 @@ function shufflePokemonList<T extends Pokemon | hasPokemon>(pokemonList: T[]): T
return pokemonList;
}
/** Type guard for {@linkcode sortBySpeed} to avoid importing {@linkcode Pokemon} */
function isPokemon(p: Pokemon | hasPokemon): p is Pokemon {
return typeof (p as hasPokemon).getPokemon !== "function";
}
/** Sorts an array of {@linkcode Pokemon} by speed (without shuffling) */
function sortBySpeed<T extends Pokemon | hasPokemon>(pokemonList: T[]): void {
pokemonList.sort((a, b) => {
const aSpeed = (a instanceof Pokemon ? a : a.getPokemon()).getEffectiveStat(Stat.SPD);
const bSpeed = (b instanceof Pokemon ? b : b.getPokemon()).getEffectiveStat(Stat.SPD);
const aSpeed = (isPokemon(a) ? a : a.getPokemon()).getEffectiveStat(Stat.SPD);
const bSpeed = (isPokemon(b) ? b : b.getPokemon()).getEffectiveStat(Stat.SPD);
return bSpeed - aSpeed;
});