217 lines
7.7 KiB
TypeScript
217 lines
7.7 KiB
TypeScript
import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog";
|
|
import type { ActiveAura } from "./combatAuras";
|
|
|
|
const ELECTROSTATIC_CHARGE_EXPERTISE_SPELL_ID = 498_871;
|
|
const ELECTROSTATIC_CHARGE_SPELL_ID = 491_156;
|
|
|
|
export const ROM_MAGE_MEDITATIVE_CURRENT = Object.freeze({
|
|
passiveSpellId: 493_262,
|
|
triggerSpellIds: [491_150, 491_169] as const,
|
|
durationMs: 10_000,
|
|
cooldownMs: 30_000,
|
|
spellHaste: 10,
|
|
cooldownAuraTag: "rom-mage-meditative-current-cooldown",
|
|
});
|
|
|
|
export const ROM_MAGE_MEDITATIVE_CURRENT_COOLDOWN_EFFECT: Extract<AbilityEffect, { kind: "apply-aura" }> = {
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id: "rom-mage-meditative-current-cooldown",
|
|
name: "Meditative Current cooldown",
|
|
disposition: "buff",
|
|
durationMs: ROM_MAGE_MEDITATIVE_CURRENT.cooldownMs,
|
|
hideFromAuraStrip: true,
|
|
utilityTags: [ROM_MAGE_MEDITATIVE_CURRENT.cooldownAuraTag],
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: -1,
|
|
miscValue: 0,
|
|
triggerSpellId: ROM_MAGE_MEDITATIVE_CURRENT.passiveSpellId,
|
|
};
|
|
|
|
export const ROM_MAGE_KNIGHT_LIGHT_OF_DAY = Object.freeze({
|
|
passiveSpellId: 493_034,
|
|
triggerSpellIds: [491_594, 492_634] as const,
|
|
durationMs: 15_000,
|
|
cooldownMs: 20_000,
|
|
lightDamagePercent: 8,
|
|
cooldownAuraTag: "rom-mage-knight-light-of-day-cooldown",
|
|
});
|
|
|
|
export const ROM_MAGE_KNIGHT_LIGHT_OF_DAY_COOLDOWN_EFFECT: Extract<AbilityEffect, { kind: "apply-aura" }> = {
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id: "rom-mage-knight-light-of-day-cooldown",
|
|
name: "Light of Day cooldown",
|
|
disposition: "buff",
|
|
durationMs: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownMs,
|
|
hideFromAuraStrip: true,
|
|
utilityTags: [ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownAuraTag],
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: -1,
|
|
miscValue: 0,
|
|
triggerSpellId: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId,
|
|
};
|
|
|
|
/** Find Light of Day's active internal cooldown marker for one caster. */
|
|
export function romMageKnightLightOfDayReadyAt(
|
|
activeAuras: readonly Pick<ActiveAura, "targetId" | "expiresAt" | "definition">[],
|
|
casterId: string,
|
|
now: number,
|
|
): number {
|
|
const cooldown = activeAuras.find((aura) => aura.targetId === casterId
|
|
&& aura.definition.utilityTags?.includes(ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownAuraTag)
|
|
&& (aura.expiresAt === null || aura.expiresAt > now));
|
|
return cooldown ? cooldown.expiresAt ?? Number.POSITIVE_INFINITY : now;
|
|
}
|
|
|
|
export interface RomMageKnightLightOfDayResult {
|
|
readonly effect: AbilityEffect | null;
|
|
readonly nextReadyAt: number;
|
|
}
|
|
|
|
/**
|
|
* RuneWaker Light of Day (493034): a landed Holy Light Strike (491594) or
|
|
* Light Charge (492634) hit grants +8% Holy damage for 15 seconds, with a
|
|
* 20-second internal cooldown. The effect only belongs to Mage/Knight builds.
|
|
*/
|
|
export function romMageKnightLightOfDayProc(
|
|
classId: string | null,
|
|
secondaryClassId: string | null | undefined,
|
|
ability: Pick<AbilityDefinition, "classId" | "secondaryClassId" | "source" | "dbcSpellId">,
|
|
learnedPassiveSpellIds: readonly number[],
|
|
landed: boolean,
|
|
readyAt: number,
|
|
now: number,
|
|
): RomMageKnightLightOfDayResult {
|
|
const eligible = classId === "rom-mage"
|
|
&& secondaryClassId === "rom-knight"
|
|
&& ability.classId === "rom-mage"
|
|
&& ability.secondaryClassId === "rom-knight"
|
|
&& ability.source === "runewaker"
|
|
&& ROM_MAGE_KNIGHT_LIGHT_OF_DAY.triggerSpellIds.includes(ability.dbcSpellId as 491_594 | 492_634)
|
|
&& learnedPassiveSpellIds.includes(ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId)
|
|
&& landed
|
|
&& now >= readyAt;
|
|
if (!eligible) return { effect: null, nextReadyAt: readyAt };
|
|
|
|
return {
|
|
effect: {
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id: "rom-mage-knight-light-of-day",
|
|
name: "Light of Day",
|
|
description: "A landed Holy Light Strike or Light Charge hit grants +8% Holy damage for 15 seconds. This effect has a 20-second internal cooldown.",
|
|
disposition: "buff",
|
|
durationMs: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.durationMs,
|
|
maxStacks: 1,
|
|
stackBehavior: "refresh",
|
|
utilityTags: ["rom-mage-knight-light-of-day-active"],
|
|
modifiers: [{
|
|
kind: "damage",
|
|
direction: "dealt",
|
|
school: "holy",
|
|
operation: "percent",
|
|
value: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.lightDamagePercent / 100,
|
|
}],
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: 49,
|
|
miscValue: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.lightDamagePercent,
|
|
triggerSpellId: ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId,
|
|
},
|
|
nextReadyAt: now + ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownMs,
|
|
};
|
|
}
|
|
|
|
/** Find the active per-caster internal cooldown marker, if one exists. */
|
|
export function romMageMeditativeCurrentReadyAt(
|
|
activeAuras: readonly Pick<ActiveAura, "targetId" | "expiresAt" | "definition">[],
|
|
casterId: string,
|
|
now: number,
|
|
): number {
|
|
const cooldown = activeAuras.find((aura) => aura.targetId === casterId
|
|
&& aura.definition.utilityTags?.includes(ROM_MAGE_MEDITATIVE_CURRENT.cooldownAuraTag)
|
|
&& (aura.expiresAt === null || aura.expiresAt > now));
|
|
return cooldown ? cooldown.expiresAt ?? Number.POSITIVE_INFINITY : now;
|
|
}
|
|
|
|
export interface RomMageMeditativeCurrentResult {
|
|
readonly effect: AbilityEffect | null;
|
|
readonly nextReadyAt: number;
|
|
}
|
|
|
|
/**
|
|
* RuneWaker Meditative Current (493262): a landed Flame (491150) or Plasma
|
|
* Arrow (491169) hit grants +10 spell haste for 10 seconds, with a 30-second
|
|
* internal cooldown. Cooldown readiness is passed in by the combat event owner.
|
|
*/
|
|
export function romMageMeditativeCurrentProc(
|
|
classId: string | null,
|
|
ability: Pick<AbilityDefinition, "source" | "dbcSpellId">,
|
|
learnedPassiveSpellIds: readonly number[],
|
|
landed: boolean,
|
|
readyAt: number,
|
|
now: number,
|
|
): RomMageMeditativeCurrentResult {
|
|
const eligible = classId === "rom-mage"
|
|
&& ability.source === "runewaker"
|
|
&& ROM_MAGE_MEDITATIVE_CURRENT.triggerSpellIds.some((spellId) => spellId === ability.dbcSpellId)
|
|
&& learnedPassiveSpellIds.includes(ROM_MAGE_MEDITATIVE_CURRENT.passiveSpellId)
|
|
&& landed
|
|
&& now >= readyAt;
|
|
if (!eligible) return { effect: null, nextReadyAt: readyAt };
|
|
|
|
return {
|
|
effect: {
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id: "rom-mage-meditative-current",
|
|
name: "Meditative Current",
|
|
description: "A landed Flame or Plasma Arrow hit grants +10 spell haste for 10 seconds. This effect has a 30-second internal cooldown.",
|
|
disposition: "buff",
|
|
durationMs: ROM_MAGE_MEDITATIVE_CURRENT.durationMs,
|
|
maxStacks: 1,
|
|
stackBehavior: "refresh",
|
|
utilityTags: ["rom-mage-meditative-current-active"],
|
|
modifiers: [{
|
|
kind: "stat",
|
|
stat: "spell-haste",
|
|
operation: "flat",
|
|
value: ROM_MAGE_MEDITATIVE_CURRENT.spellHaste,
|
|
}],
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: 51,
|
|
miscValue: -ROM_MAGE_MEDITATIVE_CURRENT.spellHaste,
|
|
triggerSpellId: ROM_MAGE_MEDITATIVE_CURRENT.passiveSpellId,
|
|
},
|
|
nextReadyAt: now + ROM_MAGE_MEDITATIVE_CURRENT.cooldownMs,
|
|
};
|
|
}
|
|
|
|
/** Mage/Champion's level-45 passive shortens Electrostatic Charge by 30 seconds. */
|
|
export function romMageCooldownReductionMs(
|
|
classId: string | null,
|
|
secondaryClassId: string | null | undefined,
|
|
ability: Pick<AbilityDefinition, "dbcSpellId">,
|
|
level: number,
|
|
abilities: readonly Pick<AbilityDefinition, "dbcSpellId" | "unlockLevel" | "passive">[],
|
|
): number {
|
|
if (classId !== "rom-mage"
|
|
|| secondaryClassId !== "rom-champion"
|
|
|| ability.dbcSpellId !== ELECTROSTATIC_CHARGE_SPELL_ID
|
|
|| level < 45) return 0;
|
|
|
|
return abilities.some((candidate) => candidate.passive
|
|
&& candidate.dbcSpellId === ELECTROSTATIC_CHARGE_EXPERTISE_SPELL_ID
|
|
&& candidate.unlockLevel <= level)
|
|
? 30_000
|
|
: 0;
|
|
}
|