mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-15 06:15:20 +01:00
[Misc] Add dynamic ordering to non-phase interactions (#6581)
* Update interactions to use generator * Fix triggerWeatherBasedFormChanges * Update documentation * Fix arena tag file * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
This commit is contained in:
parent
b88cde162a
commit
c048b34425
@ -803,6 +803,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);
|
||||
|
||||
@ -66,6 +66,7 @@ import type { Constructor } from "#types/common";
|
||||
import type { Closed, Exact } from "#types/type-helpers";
|
||||
import { coerceArray } from "#utils/array";
|
||||
import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||
import { inSpeedOrder } from "#utils/speed-order-generator";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -2857,7 +2858,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 };
|
||||
@ -3168,10 +3169,8 @@ 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 partyPokemon of allowedParty) {
|
||||
for (const partyPokemon of pokemon.getAlliesGenerator()) {
|
||||
if (partyPokemon.status && this.statusEffect.includes(partyPokemon.status.effect)) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
getStatusEffectHealText(partyPokemon.status.effect, getPokemonNameWithAffix(partyPokemon)),
|
||||
@ -4423,7 +4422,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),
|
||||
@ -4992,7 +4991,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;
|
||||
}
|
||||
@ -5545,10 +5544,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;
|
||||
|
||||
@ -78,6 +78,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. */
|
||||
@ -247,7 +248,7 @@ export abstract class ArenaTag implements BaseArenaTag {
|
||||
|
||||
/**
|
||||
* Helper function that retrieves the Pokemon affected.
|
||||
* @returns An array containing all {@linkcode Pokemon} affected by this Tag.
|
||||
* @returns An array containing all {@linkcode Pokemon} affected by this Tag, not in speed order.
|
||||
*/
|
||||
protected getAffectedPokemon(): Pokemon[] {
|
||||
switch (this.side) {
|
||||
@ -1204,7 +1205,7 @@ export class GravityTag extends SerializableArenaTag {
|
||||
|
||||
onAdd(quiet = false): void {
|
||||
super.onAdd(quiet);
|
||||
globalScene.getField(true).forEach(pokemon => {
|
||||
for (const pokemon of inSpeedOrder(ArenaTagSide.BOTH)) {
|
||||
if (pokemon !== null) {
|
||||
pokemon.removeTag(BattlerTagType.FLOATING);
|
||||
pokemon.removeTag(BattlerTagType.TELEKINESIS);
|
||||
@ -1212,7 +1213,7 @@ export class GravityTag extends SerializableArenaTag {
|
||||
pokemon.addTag(BattlerTagType.INTERRUPTED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1237,10 +1238,13 @@ class TailwindTag extends SerializableArenaTag {
|
||||
|
||||
onAdd(quiet = false): void {
|
||||
super.onAdd(quiet);
|
||||
const source = this.getSourcePokemon();
|
||||
|
||||
const field = this.getAffectedPokemon();
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pokemon of field) {
|
||||
for (const pokemon of source.getAlliesGenerator()) {
|
||||
// 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)) {
|
||||
@ -1346,11 +1350,11 @@ class FireGrassPledgeTag extends SerializableArenaTag {
|
||||
}
|
||||
|
||||
override lapse(): boolean {
|
||||
const field = this.getAffectedPokemon().filter(
|
||||
pokemon => !pokemon.isOfType(PokemonType.FIRE, true, true) && !pokemon.switchOutStatus,
|
||||
);
|
||||
for (const pokemon of inSpeedOrder(this.side)) {
|
||||
if (pokemon.isOfType(PokemonType.FIRE) || pokemon.switchOutStatus) {
|
||||
continue;
|
||||
}
|
||||
|
||||
field.forEach(pokemon => {
|
||||
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("arenaTag:fireGrassPledgeLapse", {
|
||||
@ -1365,7 +1369,7 @@ class FireGrassPledgeTag extends SerializableArenaTag {
|
||||
CommonAnim.MAGMA_STORM,
|
||||
);
|
||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||
});
|
||||
}
|
||||
|
||||
return super.lapse();
|
||||
}
|
||||
@ -1497,7 +1501,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.id !== pokemon.id) {
|
||||
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
|
||||
// the appropriate attributes (preLEaveField and IllusionBreak)
|
||||
@ -1539,7 +1543,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag {
|
||||
this.#beingRemoved = true;
|
||||
super.onRemove(quiet);
|
||||
|
||||
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.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
||||
[true, false].forEach(passive => {
|
||||
|
||||
@ -98,6 +98,7 @@ import { areAllies } from "#utils/pokemon-utils";
|
||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
import { inSpeedOrder } from "#utils/speed-order-generator";
|
||||
import { canSpeciesTera, willTerastallize } from "#utils/pokemon-utils";
|
||||
import type { ReadonlyGenericUint8Array } from "#types/typed-arrays";
|
||||
|
||||
@ -1031,8 +1032,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.getAlliesGenerator()) {
|
||||
applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power});
|
||||
}
|
||||
|
||||
power.value *= typeChangeMovePowerMultiplier.value;
|
||||
|
||||
@ -6218,8 +6220,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;
|
||||
}
|
||||
}
|
||||
@ -8156,7 +8160,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 }));
|
||||
|
||||
@ -40,6 +40,7 @@ import type { Constructor } from "#types/common";
|
||||
import type { AbstractConstructor } from "#types/type-helpers";
|
||||
import { NumberHolder, randSeedInt } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import { inSpeedOrder } from "#utils/speed-order-generator";
|
||||
|
||||
export class Arena {
|
||||
public biomeType: BiomeId;
|
||||
@ -358,15 +359,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;
|
||||
}
|
||||
@ -376,11 +374,11 @@ 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) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const isCastformWithForecast = p.hasAbility(AbilityId.FORECAST) && p.species.speciesId === SpeciesId.CASTFORM;
|
||||
const isCherrimWithFlowerGift = p.hasAbility(AbilityId.FLOWER_GIFT) && p.species.speciesId === SpeciesId.CHERRIM;
|
||||
@ -388,23 +386,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} */
|
||||
@ -453,16 +451,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;
|
||||
}
|
||||
|
||||
@ -173,6 +173,7 @@ import {
|
||||
import { calculateBossSegmentDamage } from "#utils/damage";
|
||||
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;
|
||||
@ -2468,8 +2468,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Do not check queenly majesty unless this is being simulated
|
||||
// This is because the move effect phase should not check queenly majesty, as that is handled by the move phase
|
||||
if (simulated && !cancelledHolder.value) {
|
||||
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||
defendingSidePlayField.forEach((p: (typeof defendingSidePlayField)[0]) => {
|
||||
for (const p of this.getAlliesGenerator()) {
|
||||
applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
|
||||
pokemon: p,
|
||||
opponent: source,
|
||||
@ -2477,7 +2476,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
cancelled: cancelledHolder,
|
||||
simulated,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3243,7 +3242,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pokemon that oppose this one and are active
|
||||
* Returns the pokemon that oppose this one and are active in non-speed order
|
||||
*
|
||||
* @param onField - whether to also check if the pokemon is currently on the field (defaults to true)
|
||||
*/
|
||||
@ -3253,6 +3252,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A generator of pokemon that oppose this one in speed order
|
||||
*/
|
||||
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");
|
||||
}
|
||||
@ -3262,12 +3268,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 A generator of Pokémon on the allied field in speed order.
|
||||
*/
|
||||
getAlliedField(): Pokemon[] {
|
||||
return this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||
getAlliesGenerator(): Generator<Pokemon, number> {
|
||||
return inSpeedOrder(this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4020,16 +4024,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.getAlliesGenerator()) {
|
||||
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
|
||||
pokemon,
|
||||
tag: stubTag,
|
||||
cancelled,
|
||||
simulated: true,
|
||||
target: this,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return !cancelled.value;
|
||||
}
|
||||
@ -4063,7 +4066,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const pokemon of this.getAlliedField()) {
|
||||
for (const pokemon of this.getAlliesGenerator()) {
|
||||
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", { pokemon, tag: newTag, cancelled, target: this });
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
@ -4811,7 +4814,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const pokemon of this.getAlliedField()) {
|
||||
for (const pokemon of this.getAlliesGenerator()) {
|
||||
applyAbAttrs("UserFieldStatusEffectImmunityAbAttr", {
|
||||
pokemon,
|
||||
effect,
|
||||
@ -5619,10 +5622,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();
|
||||
|
||||
@ -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.getAlliesGenerator()) {
|
||||
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import { getStatusEffectActivationText } from "#data/status-effect";
|
||||
import { getTerrainBlockMessage } from "#data/terrain";
|
||||
import { getWeatherBlockMessage } from "#data/weather";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
@ -35,6 +36,7 @@ import type { TurnMove } from "#types/turn-move";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
import { BooleanHolder, 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 {
|
||||
@ -321,17 +323,16 @@ export class MovePhase extends PokemonPhase {
|
||||
|
||||
// check move redirection abilities of every pokemon *except* the user.
|
||||
// TODO: Make storm drain, lightning rod, etc, redirect at this point for type changing moves
|
||||
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;
|
||||
@ -848,9 +849,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: user, targets });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
@ -427,7 +429,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);
|
||||
|
||||
@ -30,8 +30,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.getAlliesGenerator()) {
|
||||
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||
}
|
||||
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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";
|
||||
@ -39,11 +39,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;
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user