Support priority ability activations

This commit is contained in:
Dean 2025-03-11 23:59:43 -07:00
parent 5fd41344a5
commit 4ee5e6c9a0
4 changed files with 72 additions and 14 deletions

View File

@ -58,14 +58,16 @@ export class Ability implements Localizable {
public generation: number; public generation: number;
public isBypassFaint: boolean; public isBypassFaint: boolean;
public isIgnorable: boolean; public isIgnorable: boolean;
public isPriority: boolean;
public attrs: AbAttr[]; public attrs: AbAttr[];
public conditions: AbAttrCondition[]; public conditions: AbAttrCondition[];
constructor(id: Abilities, generation: number) { constructor(id: Abilities, generation: number, isPriority: boolean = false) {
this.id = id; this.id = id;
this.nameAppend = ""; this.nameAppend = "";
this.generation = generation; this.generation = generation;
this.isPriority = isPriority;
this.attrs = []; this.attrs = [];
this.conditions = []; this.conditions = [];
@ -5999,6 +6001,21 @@ export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
} }
/**
* Applies only abilities that are priority or are not, based on parameters
*
* @param priority If true, apply only priority abilities. If false, apply only non-priority abilities
*/
export function applyPriorityBasedAbAttrs(pokemon: Pokemon, priority: boolean, simulated: boolean = false, ...args: any[]) {
for (const passive of [ false, true ]) {
const ability: Ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
if (ability.isPriority == priority) {
applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, false, simulated)
}
}
}
function queueShowAbility(pokemon: Pokemon, passive: boolean): void { function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive));
globalScene.clearPhaseQueueSplice(); globalScene.clearPhaseQueueSplice();
@ -6887,7 +6904,7 @@ export function initAbilities() {
.edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil
new Ability(Abilities.GORILLA_TACTICS, 8) new Ability(Abilities.GORILLA_TACTICS, 8)
.attr(GorillaTacticsAbAttr), .attr(GorillaTacticsAbAttr),
new Ability(Abilities.NEUTRALIZING_GAS, 8) new Ability(Abilities.NEUTRALIZING_GAS, 8, true)
.attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0) .attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0)
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
@ -6920,14 +6937,14 @@ export function initAbilities() {
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
new Ability(Abilities.GRIM_NEIGH, 8) new Ability(Abilities.GRIM_NEIGH, 8)
.attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1), .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.AS_ONE_GLASTRIER, 8) new Ability(Abilities.AS_ONE_GLASTRIER, 8, true)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneGlastrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneGlastrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PreventBerryUseAbAttr) .attr(PreventBerryUseAbAttr)
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1) .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr), .attr(UnsuppressableAbilityAbAttr),
new Ability(Abilities.AS_ONE_SPECTRIER, 8) new Ability(Abilities.AS_ONE_SPECTRIER, 8, true)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneSpectrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneSpectrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PreventBerryUseAbAttr) .attr(PreventBerryUseAbAttr)
.attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1) .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1)

View File

@ -2265,6 +2265,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
/**
* @returns If either of the Pokemon's abilities have priority activation
*/
public hasPriorityAbility() {
return [this.getAbility(), this.getPassiveAbility()].some(ability => ability.isPriority);
}
/** /**
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first * Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal) * and then multiplicative modifiers happening after (Heavy Metal and Light Metal)

View File

@ -1,31 +1,46 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#app/battle";
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability"; import { applyAbAttrs, applyPriorityBasedAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability";
import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { PriorityAbilityActivationPhase } from "#app/phases/priority-ability-activation-phase";
export class PostSummonPhase extends PokemonPhase { export class PostSummonPhase extends PokemonPhase {
/** Represents whether or not this phase has already been placed in the correct (speed) order */
private ordered: boolean;
constructor(battlerIndex?: BattlerIndex, ordered = false) {
super(battlerIndex);
this.ordered = ordered;
}
start() { start() {
super.start(); super.start();
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
globalScene.phaseQueue; if (
const fasterPhase = globalScene.findPhase( !this.ordered &&
phase => globalScene.findPhase(phase => phase instanceof PostSummonPhase && phase.getPokemon() !== pokemon)
phase instanceof PostSummonPhase && ) {
phase.getPokemon().getEffectiveStat(Stat.SPD) > pokemon.getEffectiveStat(Stat.SPD), globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex(), true));
);
if (fasterPhase) {
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex()));
globalScene.phaseQueue.sort( globalScene.phaseQueue.sort(
(phaseA: PostSummonPhase, phaseB: PostSummonPhase) => (phaseA: PostSummonPhase, phaseB: PostSummonPhase) =>
phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD), phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD),
); );
globalScene.phaseQueue.forEach((phase: PostSummonPhase) => {
phase.ordered = true;
const phasePokemon = phase.getPokemon();
if (phasePokemon.hasPriorityAbility()) {
globalScene.unshiftPhase(new PriorityAbilityActivationPhase(this.getPokemon().getBattlerIndex()));
}
});
this.end(); this.end();
return; return;
} }
@ -43,7 +58,7 @@ export class PostSummonPhase extends PokemonPhase {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
} }
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon); applyPriorityBasedAbAttrs(pokemon, false);
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) { for (const p of field) {
applyAbAttrs(CommanderAbAttr, p, null, false); applyAbAttrs(CommanderAbAttr, p, null, false);

View File

@ -0,0 +1,19 @@
import { applyPriorityBasedAbAttrs } from "#app/data/ability";
import { PokemonPhase } from "#app/phases/pokemon-phase";
/**
* Phase to apply (post-summon) ability attributes for "priority" abilities
*
* Priority abilities activate before others and before hazards
*
* @see Example - {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) | Neutralizing Gas}
*/
export class PriorityAbilityActivationPhase extends PokemonPhase {
start() {
super.start();
applyPriorityBasedAbAttrs(this.getPokemon(), true);
this.end();
}
}