mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 07:22:19 +02:00
Add new priority queues
This commit is contained in:
parent
7c6189e812
commit
5f098545f5
@ -79,6 +79,7 @@ import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
|||||||
import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves";
|
import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves";
|
||||||
import type { Localizable } from "#app/@types/locales";
|
import type { Localizable } from "#app/@types/locales";
|
||||||
import { applyAbAttrs } from "./apply-ab-attrs";
|
import { applyAbAttrs } from "./apply-ab-attrs";
|
||||||
|
import { MovePriorityModifier } from "#enums/move-priority-modifier";
|
||||||
|
|
||||||
export class Ability implements Localizable {
|
export class Ability implements Localizable {
|
||||||
public id: AbilityId;
|
public id: AbilityId;
|
||||||
@ -4978,6 +4979,31 @@ export class ChangeMovePriorityAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ChangeMovePriorityModifierAbAttr extends AbAttr {
|
||||||
|
private newModifier: MovePriorityModifier;
|
||||||
|
private moveFunc: (pokemon: Pokemon, move: Move) => boolean;
|
||||||
|
|
||||||
|
constructor(moveFunc: (pokemon: Pokemon, move: Move) => boolean, newModifier: MovePriorityModifier) {
|
||||||
|
super(false);
|
||||||
|
this.newModifier = newModifier;
|
||||||
|
this.moveFunc = moveFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
||||||
|
return this.moveFunc(pokemon, args[0] as Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
override apply(
|
||||||
|
_pokemon: Pokemon,
|
||||||
|
_passive: boolean,
|
||||||
|
_simulated: boolean,
|
||||||
|
_cancelled: BooleanHolder | null,
|
||||||
|
args: any[],
|
||||||
|
): void {
|
||||||
|
(args[1] as NumberHolder).value = this.newModifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class IgnoreContactAbAttr extends AbAttr {}
|
export class IgnoreContactAbAttr extends AbAttr {}
|
||||||
|
|
||||||
export class PreWeatherEffectAbAttr extends AbAttr {
|
export class PreWeatherEffectAbAttr extends AbAttr {
|
||||||
@ -7219,7 +7245,12 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
|
|||||||
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
||||||
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
|
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
|
||||||
return (
|
return (
|
||||||
!simulated && !bypassSpeed.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove
|
!simulated &&
|
||||||
|
!bypassSpeed.value &&
|
||||||
|
pokemon.randBattleSeedInt(100) < this.chance &&
|
||||||
|
isCommandFight &&
|
||||||
|
isDamageMove &&
|
||||||
|
pokemon.canAddTag(BattlerTagType.BYPASS_SPEED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7228,17 +7259,16 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
|
|||||||
* @param {Pokemon} _pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
* @param {Pokemon} _pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
||||||
* @param {boolean} _passive N/A
|
* @param {boolean} _passive N/A
|
||||||
* @param {BooleanHolder} _cancelled N/A
|
* @param {BooleanHolder} _cancelled N/A
|
||||||
* @param {any[]} args [0] {@linkcode BooleanHolder} set to true when the ability activated
|
* @param {any[]} _args N/A
|
||||||
*/
|
*/
|
||||||
override apply(
|
override apply(
|
||||||
_pokemon: Pokemon,
|
pokemon: Pokemon,
|
||||||
_passive: boolean,
|
_passive: boolean,
|
||||||
_simulated: boolean,
|
_simulated: boolean,
|
||||||
_cancelled: BooleanHolder,
|
_cancelled: BooleanHolder,
|
||||||
args: any[],
|
_args: any[],
|
||||||
): void {
|
): void {
|
||||||
const bypassSpeed = args[0] as BooleanHolder;
|
pokemon.addTag(BattlerTagType.BYPASS_SPEED);
|
||||||
bypassSpeed.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
||||||
@ -7270,7 +7300,6 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment
|
* @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment
|
||||||
* @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not
|
|
||||||
*/
|
*/
|
||||||
override apply(
|
override apply(
|
||||||
_pokemon: Pokemon,
|
_pokemon: Pokemon,
|
||||||
@ -7280,9 +7309,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
|||||||
args: any[],
|
args: any[],
|
||||||
): void {
|
): void {
|
||||||
const bypassSpeed = args[0] as BooleanHolder;
|
const bypassSpeed = args[0] as BooleanHolder;
|
||||||
const canCheckHeldItems = args[1] as BooleanHolder;
|
|
||||||
bypassSpeed.value = false;
|
bypassSpeed.value = false;
|
||||||
canCheckHeldItems.value = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7814,6 +7841,7 @@ const AbilityAttrs = Object.freeze({
|
|||||||
BlockStatusDamageAbAttr,
|
BlockStatusDamageAbAttr,
|
||||||
BlockOneHitKOAbAttr,
|
BlockOneHitKOAbAttr,
|
||||||
ChangeMovePriorityAbAttr,
|
ChangeMovePriorityAbAttr,
|
||||||
|
ChangeMovePriorityModifierAbAttr,
|
||||||
IgnoreContactAbAttr,
|
IgnoreContactAbAttr,
|
||||||
PreWeatherEffectAbAttr,
|
PreWeatherEffectAbAttr,
|
||||||
PreWeatherDamageAbAttr,
|
PreWeatherDamageAbAttr,
|
||||||
@ -8230,7 +8258,7 @@ export function initAbilities() {
|
|||||||
.attr(AlwaysHitAbAttr)
|
.attr(AlwaysHitAbAttr)
|
||||||
.attr(DoubleBattleChanceAbAttr),
|
.attr(DoubleBattleChanceAbAttr),
|
||||||
new Ability(AbilityId.STALL, 4)
|
new Ability(AbilityId.STALL, 4)
|
||||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, _move: Move) => true, -0.2),
|
.attr(ChangeMovePriorityModifierAbAttr, (_pokemon, _move: Move) => true, MovePriorityModifier.LAST_IN_BRACKET),
|
||||||
new Ability(AbilityId.TECHNICIAN, 4)
|
new Ability(AbilityId.TECHNICIAN, 4)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => {
|
.attr(MovePowerBoostAbAttr, (user, target, move) => {
|
||||||
const power = new NumberHolder(move.power);
|
const power = new NumberHolder(move.power);
|
||||||
@ -8957,7 +8985,7 @@ export function initAbilities() {
|
|||||||
.attr(TypeImmunityHealAbAttr, PokemonType.GROUND)
|
.attr(TypeImmunityHealAbAttr, PokemonType.GROUND)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(AbilityId.MYCELIUM_MIGHT, 9)
|
new Ability(AbilityId.MYCELIUM_MIGHT, 9)
|
||||||
.attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
.attr(ChangeMovePriorityModifierAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS, MovePriorityModifier.LAST_IN_BRACKET)
|
||||||
.attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS)
|
.attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS)
|
||||||
.attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
.attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
||||||
new Ability(AbilityId.MINDS_EYE, 9)
|
new Ability(AbilityId.MINDS_EYE, 9)
|
||||||
|
@ -3421,6 +3421,23 @@ export class GrudgeTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag to allow the affected Pokemon's move to go first in its priority bracket.
|
||||||
|
* Used for {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(Ability) Quick Draw}
|
||||||
|
* and {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Claw Quick Claw}.
|
||||||
|
*/
|
||||||
|
export class BypassSpeedTag extends BattlerTag {
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.BYPASS_SPEED, BattlerTagLapseType.TURN_END, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
override canAdd(pokemon: Pokemon): boolean {
|
||||||
|
const cancelled = new BooleanHolder(false);
|
||||||
|
applyAbAttrs("PreventBypassSpeedChanceAbAttr", pokemon, null, false, cancelled);
|
||||||
|
return !cancelled.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon
|
* Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon
|
||||||
*/
|
*/
|
||||||
@ -3668,6 +3685,8 @@ export function getBattlerTag(
|
|||||||
return new PsychoShiftTag();
|
return new PsychoShiftTag();
|
||||||
case BattlerTagType.MAGIC_COAT:
|
case BattlerTagType.MAGIC_COAT:
|
||||||
return new MagicCoatTag();
|
return new MagicCoatTag();
|
||||||
|
case BattlerTagType.BYPASS_SPEED:
|
||||||
|
return new BypassSpeedTag();
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
@ -100,6 +100,7 @@ import { SelectBiomePhase } from "#app/phases/select-biome-phase";
|
|||||||
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
||||||
import { applyMoveAttrs } from "./apply-attrs";
|
import { applyMoveAttrs } from "./apply-attrs";
|
||||||
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
||||||
|
import { MovePriorityModifier } from "#enums/move-priority-modifier";
|
||||||
|
|
||||||
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||||
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
||||||
@ -867,6 +868,13 @@ export default abstract class Move implements Localizable {
|
|||||||
return priority.value;
|
return priority.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPriorityModifier(user: Pokemon, simulated = true): MovePriorityModifier {
|
||||||
|
const modifierHolder = new NumberHolder(MovePriorityModifier.NORMAL);
|
||||||
|
applyAbAttrs("ChangeMovePriorityModifierAbAttr", user, null, simulated, this, modifierHolder);
|
||||||
|
modifierHolder.value = user.getTag(BattlerTagType.BYPASS_SPEED) ? MovePriorityModifier.FIRST_IN_BRACKET : modifierHolder.value;
|
||||||
|
return modifierHolder.value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the [Expected Power](https://en.wikipedia.org/wiki/Expected_value) per turn
|
* Calculate the [Expected Power](https://en.wikipedia.org/wiki/Expected_value) per turn
|
||||||
* of this move, taking into account multi hit moves, accuracy, and the number of turns it
|
* of this move, taking into account multi hit moves, accuracy, and the number of turns it
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import type { Phase } from "#app/phase";
|
|
||||||
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
|
||||||
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
|
|
||||||
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
|
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import { BooleanHolder } from "#app/utils/common";
|
|
||||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
|
||||||
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a list of {@linkcode Phase}s
|
|
||||||
*
|
|
||||||
* Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}
|
|
||||||
*/
|
|
||||||
export abstract class PhasePriorityQueue {
|
|
||||||
protected abstract queue: Phase[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the elements in the queue
|
|
||||||
*/
|
|
||||||
public abstract reorder(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls {@linkcode reorder} and shifts the queue
|
|
||||||
* @returns The front element of the queue after sorting
|
|
||||||
*/
|
|
||||||
public pop(): Phase | undefined {
|
|
||||||
this.reorder();
|
|
||||||
return this.queue.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a phase to the queue
|
|
||||||
* @param phase The phase to add
|
|
||||||
*/
|
|
||||||
public push(phase: Phase): void {
|
|
||||||
this.queue.push(phase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all phases from the queue
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
this.queue.splice(0, this.queue.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase}
|
|
||||||
*
|
|
||||||
* Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed
|
|
||||||
*/
|
|
||||||
export class PostSummonPhasePriorityQueue extends PhasePriorityQueue {
|
|
||||||
protected override queue: PostSummonPhase[] = [];
|
|
||||||
|
|
||||||
public override reorder(): void {
|
|
||||||
this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => {
|
|
||||||
if (phaseA.getPriority() === phaseB.getPriority()) {
|
|
||||||
return (
|
|
||||||
(phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD)) *
|
|
||||||
(isTrickRoom() ? -1 : 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return phaseB.getPriority() - phaseA.getPriority();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override push(phase: PostSummonPhase): void {
|
|
||||||
super.push(phase);
|
|
||||||
this.queueAbilityPhase(phase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase}
|
|
||||||
* @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue
|
|
||||||
*/
|
|
||||||
private queueAbilityPhase(phase: PostSummonPhase): void {
|
|
||||||
const phasePokemon = phase.getPokemon();
|
|
||||||
|
|
||||||
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
|
||||||
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
|
||||||
globalScene.phaseManager.appendToPhase(
|
|
||||||
new ActivatePriorityQueuePhase(DynamicPhaseType.POST_SUMMON),
|
|
||||||
"ActivatePriorityQueuePhase",
|
|
||||||
(p: ActivatePriorityQueuePhase) => p.getType() === DynamicPhaseType.POST_SUMMON,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTrickRoom(): boolean {
|
|
||||||
const speedReversed = new BooleanHolder(false);
|
|
||||||
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
|
|
||||||
return speedReversed.value;
|
|
||||||
}
|
|
@ -95,4 +95,5 @@ export enum BattlerTagType {
|
|||||||
ENDURE_TOKEN = "ENDURE_TOKEN",
|
ENDURE_TOKEN = "ENDURE_TOKEN",
|
||||||
POWDER = "POWDER",
|
POWDER = "POWDER",
|
||||||
MAGIC_COAT = "MAGIC_COAT",
|
MAGIC_COAT = "MAGIC_COAT",
|
||||||
|
BYPASS_SPEED = "BYPASS_SPEED"
|
||||||
}
|
}
|
||||||
|
5
src/enums/move-priority-modifier.ts
Normal file
5
src/enums/move-priority-modifier.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum MovePriorityModifier {
|
||||||
|
LAST_IN_BRACKET = 0,
|
||||||
|
NORMAL,
|
||||||
|
FIRST_IN_BRACKET,
|
||||||
|
}
|
@ -12,7 +12,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { LearnMoveType } from "#enums/learn-move-type";
|
import { LearnMoveType } from "#enums/learn-move-type";
|
||||||
import type { VoucherType } from "#app/system/voucher";
|
import type { VoucherType } from "#app/system/voucher";
|
||||||
import { Command } from "#enums/command";
|
|
||||||
import { addTextObject, TextStyle } from "#app/ui/text";
|
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||||
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common";
|
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
@ -1588,30 +1587,16 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
|
|||||||
return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount);
|
return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if {@linkcode BypassSpeedChanceModifier} should be applied
|
|
||||||
* @param pokemon the {@linkcode Pokemon} that holds the item
|
|
||||||
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
|
|
||||||
* @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied
|
|
||||||
*/
|
|
||||||
override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean {
|
|
||||||
return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies {@linkcode BypassSpeedChanceModifier}
|
* Applies {@linkcode BypassSpeedChanceModifier}
|
||||||
* @param pokemon the {@linkcode Pokemon} that holds the item
|
* @param pokemon the {@linkcode Pokemon} that holds the item
|
||||||
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
|
|
||||||
* @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied
|
* @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied
|
||||||
*/
|
*/
|
||||||
override apply(pokemon: Pokemon, doBypassSpeed: BooleanHolder): boolean {
|
override apply(pokemon: Pokemon): boolean {
|
||||||
if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) {
|
if (pokemon.randBattleSeedInt(10) < this.getStackCount() && pokemon.addTag(BattlerTagType.BYPASS_SPEED)) {
|
||||||
doBypassSpeed.value = true;
|
|
||||||
const isCommandFight =
|
|
||||||
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
|
|
||||||
const hasQuickClaw = this.type.is("PokemonHeldItemModifierType") && this.type.id === "QUICK_CLAW";
|
const hasQuickClaw = this.type.is("PokemonHeldItemModifierType") && this.type.id === "QUICK_CLAW";
|
||||||
|
|
||||||
if (isCommandFight && hasQuickClaw) {
|
if (hasQuickClaw) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("modifier:bypassSpeedChanceApply", {
|
i18next.t("modifier:bypassSpeedChanceApply", {
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
|
28
src/queues/move-phase-priority-queue.ts
Normal file
28
src/queues/move-phase-priority-queue.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { MovePhase } from "#app/phases/move-phase";
|
||||||
|
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
||||||
|
|
||||||
|
export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase> {
|
||||||
|
public override reorder(): void {
|
||||||
|
super.reorder();
|
||||||
|
this.sortPostSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortPostSpeed(): void {
|
||||||
|
this.queue.sort((a: MovePhase, b: MovePhase) => {
|
||||||
|
const priority = [a, b].map(movePhase => {
|
||||||
|
const move = movePhase.move.getMove();
|
||||||
|
return move.getPriority(movePhase.pokemon, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const priorityModifiers = [a, b].map(movePhase =>
|
||||||
|
movePhase.move.getMove().getPriorityModifier(movePhase.pokemon),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (priority[0] === priority[1] && priorityModifiers[0] !== priorityModifiers[1]) {
|
||||||
|
return priorityModifiers[0] - priorityModifiers[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return priority[0] - priority[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
43
src/queues/phase-priority-queue.ts
Normal file
43
src/queues/phase-priority-queue.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import type { Phase } from "#app/phase";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a list of {@linkcode Phase}s
|
||||||
|
*
|
||||||
|
* Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}
|
||||||
|
*/
|
||||||
|
export abstract class PhasePriorityQueue<T extends Phase> {
|
||||||
|
protected queue: T[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the elements in the queue
|
||||||
|
*/
|
||||||
|
public abstract reorder(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@linkcode reorder} and shifts the queue
|
||||||
|
* @returns The front element of the queue after sorting
|
||||||
|
*/
|
||||||
|
public pop(): T | undefined {
|
||||||
|
this.reorder();
|
||||||
|
return this.queue.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a phase to the queue
|
||||||
|
* @param phase The phase to add
|
||||||
|
*/
|
||||||
|
public push(phase: T): void {
|
||||||
|
this.queue.push(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all phases from the queue
|
||||||
|
*/
|
||||||
|
public clear(): void {
|
||||||
|
this.queue.splice(0, this.queue.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isEmpty(): boolean {
|
||||||
|
return !this.queue.length;
|
||||||
|
}
|
||||||
|
}
|
10
src/queues/pokemon-phase-priority-queue.ts
Normal file
10
src/queues/pokemon-phase-priority-queue.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { PartyMemberPokemonPhase } from "#app/phases/party-member-pokemon-phase";
|
||||||
|
import type { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||||
|
import { PhasePriorityQueue } from "#app/queues/phase-priority-queue";
|
||||||
|
import { sortInSpeedOrder } from "#app/utils/speed-order";
|
||||||
|
|
||||||
|
export class PokemonPhasePriorityQueue<T extends PokemonPhase | PartyMemberPokemonPhase> extends PhasePriorityQueue<T> {
|
||||||
|
public override reorder(): void {
|
||||||
|
this.queue = sortInSpeedOrder(this.queue);
|
||||||
|
}
|
||||||
|
}
|
42
src/queues/post-summon-phase-priority-queue.ts
Normal file
42
src/queues/post-summon-phase-priority-queue.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
||||||
|
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
|
||||||
|
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||||
|
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase}
|
||||||
|
*
|
||||||
|
* Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue<PostSummonPhase> {
|
||||||
|
public override reorder(): void {
|
||||||
|
super.reorder();
|
||||||
|
this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => {
|
||||||
|
return phaseB.getPriority() - phaseA.getPriority();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override push(phase: PostSummonPhase): void {
|
||||||
|
super.push(phase);
|
||||||
|
this.queueAbilityPhase(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase}
|
||||||
|
* @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue
|
||||||
|
*/
|
||||||
|
private queueAbilityPhase(phase: PostSummonPhase): void {
|
||||||
|
const phasePokemon = phase.getPokemon();
|
||||||
|
|
||||||
|
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
||||||
|
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
||||||
|
globalScene.phaseManager.appendToPhase(
|
||||||
|
new ActivatePriorityQueuePhase("PostSummonPhase"),
|
||||||
|
"ActivatePriorityQueuePhase",
|
||||||
|
(p: ActivatePriorityQueuePhase) => p.getType() === "PostSummonPhase",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
46
src/utils/speed-order.ts
Normal file
46
src/utils/speed-order.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 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";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
|
||||||
|
export interface hasPokemon {
|
||||||
|
getPokemon(): Pokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(pokemonList: T[]): T[] {
|
||||||
|
pokemonList = shuffle(pokemonList);
|
||||||
|
sortBySpeed(pokemonList);
|
||||||
|
return pokemonList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Randomly shuffles the queue. */
|
||||||
|
function shuffle<T extends Pokemon | hasPokemon>(pokemonList: T[]): T[] {
|
||||||
|
// This is seeded with the current turn to prevent an inconsistency where it
|
||||||
|
// was varying based on how long since you last reloaded
|
||||||
|
globalScene.executeWithSeedOffset(
|
||||||
|
() => {
|
||||||
|
pokemonList = randSeedShuffle(pokemonList);
|
||||||
|
},
|
||||||
|
globalScene.currentBattle.turn * 1000 + pokemonList.length,
|
||||||
|
globalScene.waveSeed,
|
||||||
|
);
|
||||||
|
return pokemonList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortBySpeed<T extends Pokemon | hasPokemon>(pokemonList: T[]): void {
|
||||||
|
pokemonList.sort((a, b) => {
|
||||||
|
const [aSpeed, bSpeed] = [a, b].map(pkmn =>
|
||||||
|
pkmn instanceof Pokemon ? pkmn.getEffectiveStat(Stat.SPD) : pkmn.getPokemon().getEffectiveStat(Stat.SPD),
|
||||||
|
);
|
||||||
|
return bSpeed - aSpeed;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 'true' if Trick Room is on the field. */
|
||||||
|
const speedReversed = new BooleanHolder(false);
|
||||||
|
globalScene.arena.applyTags(ArenaTagType.TRICK_ROOM, false, speedReversed);
|
||||||
|
|
||||||
|
if (speedReversed.value) {
|
||||||
|
pokemonList.reverse();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user