mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 06:53:27 +02:00
Merge ab58381c50
into 2fe99cc3bf
This commit is contained in:
commit
3a667c1f00
@ -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);
|
||||
|
@ -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.getAlliesGenerator()) {
|
||||
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;
|
||||
|
@ -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.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)) {
|
||||
@ -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 => {
|
||||
|
@ -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.getAlliesGenerator()) {
|
||||
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 }));
|
||||
|
@ -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,11 +372,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;
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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.getAlliesGenerator()) {
|
||||
applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
|
||||
pokemon: p,
|
||||
opponent: source,
|
||||
move,
|
||||
cancelled: cancelledHolder,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3234,7 +3233,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)
|
||||
*/
|
||||
@ -3244,6 +3243,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");
|
||||
}
|
||||
@ -3253,12 +3259,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();
|
||||
getAlliesGenerator(): Generator<Pokemon, number> {
|
||||
return inSpeedOrder(this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4017,16 +4021,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;
|
||||
}
|
||||
@ -4060,7 +4063,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;
|
||||
@ -4778,7 +4781,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,
|
||||
@ -5571,10 +5574,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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.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";
|
||||
@ -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;
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user