forked from phenom/healer-man
227 lines
8.0 KiB
TypeScript
227 lines
8.0 KiB
TypeScript
import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog";
|
|
import { weaponDps, type WeaponStats } from "./itemStats";
|
|
import { runewakerOrgPower } from "./runewakerCombatFormula";
|
|
|
|
export const ROM_WARDEN_BRIAR_SHIELD_TAG = "rom-briar-shield";
|
|
export const ROM_WARDEN_WILL_ATTACK_TAG = "rom-will-attack";
|
|
export const ROM_WARDEN_DAMAGE_TRANSFER_TAG = "rom-warden-damage-transfer";
|
|
export const ROM_WARDEN_TOUGH_OAK_TAG = "rom-warden-tough-oak";
|
|
export const ROM_WARDEN_OAK_BLESSING_TAG = "rom-warden-oak-blessing";
|
|
export const ROM_WARDEN_DAMAGE_TRANSFER_FRACTION = 0.3;
|
|
|
|
/** RuneWaker component 503959's level-scaled physical rebound coefficient. */
|
|
export function romWardenBriarShieldReflectFraction(characterLevel: number): number {
|
|
return Math.abs(runewakerOrgPower(-0.01, characterLevel, 8));
|
|
}
|
|
|
|
/** The local tooltip defines the rebound basis as main-hand weapon DPS. */
|
|
export function romWardenBriarShieldReflectDamage(
|
|
characterLevel: number,
|
|
mainHandWeapon: WeaponStats | null,
|
|
): number {
|
|
return mainHandWeapon
|
|
? Math.max(0, Math.round(weaponDps(mainHandWeapon) * romWardenBriarShieldReflectFraction(characterLevel)))
|
|
: 0;
|
|
}
|
|
|
|
export function romWardenBriarShieldShouldReflect(input: {
|
|
readonly auraActive: boolean;
|
|
readonly landed: boolean;
|
|
readonly school: string;
|
|
readonly attackKind: "melee" | "ranged" | "spell";
|
|
readonly withinRange: boolean;
|
|
}): boolean {
|
|
return input.auraActive
|
|
&& input.landed
|
|
&& input.school === "physical"
|
|
&& input.attackKind !== "spell"
|
|
&& input.withinRange;
|
|
}
|
|
|
|
const ROM_WARDEN_TOUGH_OAK_COOLDOWN_SPELLS = new Set([493382, 493385]);
|
|
|
|
const classPair = (ability: AbilityDefinition, primary: string, secondary: string): boolean => (
|
|
ability.classId === primary && ability.secondaryClassId === secondary
|
|
);
|
|
|
|
function casterAura(
|
|
ability: AbilityDefinition,
|
|
id: string,
|
|
name: string,
|
|
durationMs: number | null,
|
|
options: Pick<Extract<AbilityEffect, { kind: "apply-aura" }>["aura"], "controlImmunities" | "utilityTags">,
|
|
): Extract<AbilityEffect, { kind: "apply-aura" }> {
|
|
return {
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id,
|
|
name,
|
|
disposition: "buff",
|
|
durationMs,
|
|
...options,
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: -1,
|
|
miscValue: 0,
|
|
triggerSpellId: ability.dbcSpellId,
|
|
};
|
|
}
|
|
|
|
/** Select source-described Warden and Warden-linked class payloads. */
|
|
export function romWardenCastEffects(
|
|
ability: AbilityDefinition,
|
|
effects: readonly AbilityEffect[],
|
|
naturePower: number,
|
|
passiveTags: readonly string[] = [],
|
|
): readonly AbilityEffect[] {
|
|
if (ability.source !== "runewaker") return effects;
|
|
|
|
if (ability.classId === "rom-warden"
|
|
&& ability.dbcSpellId !== undefined
|
|
&& ROM_WARDEN_TOUGH_OAK_COOLDOWN_SPELLS.has(ability.dbcSpellId)
|
|
&& passiveTags.includes(ROM_WARDEN_TOUGH_OAK_TAG)) {
|
|
return [...effects, {
|
|
kind: "cooldown-reset",
|
|
mode: "reduce",
|
|
abilityName: ability.name,
|
|
amountMs: 1_000,
|
|
}];
|
|
}
|
|
|
|
if (ability.classId === "rom-warden"
|
|
&& ability.dbcSpellId === 491668
|
|
&& passiveTags.includes(ROM_WARDEN_OAK_BLESSING_TAG)) {
|
|
// Warden/Mage's local Oak Blessing component 620679 is conditional on
|
|
// Heart of the Oak: +5% physical attack and 1% maximum HP every 3s for
|
|
// 60s. The generated passive itself is only a marker so it cannot apply
|
|
// this rider outside the parent buff's lifetime.
|
|
return [...effects,
|
|
{
|
|
kind: "apply-aura",
|
|
recipient: "caster",
|
|
aura: {
|
|
id: "rom-warden-oak-blessing-attack",
|
|
name: "Oak Blessing",
|
|
disposition: "buff",
|
|
durationMs: 60_000,
|
|
modifiers: [{ kind: "stat", stat: "attack-power", operation: "percent", value: 0.05 }],
|
|
},
|
|
sourceEffectType: -1,
|
|
sourceAuraType: 134,
|
|
miscValue: 5,
|
|
triggerSpellId: 491668,
|
|
},
|
|
{
|
|
kind: "hot",
|
|
coefficient: 0,
|
|
percentMaxHealth: 0.01,
|
|
ticks: 20,
|
|
intervalMs: 3_000,
|
|
},
|
|
];
|
|
}
|
|
|
|
if (ability.dbcSpellId === 493391 && ability.classId === "rom-warden") {
|
|
// The source row includes Will Attack's component, but it belongs on the
|
|
// target only when the Warden has learned that passive.
|
|
const reduction = effects.find((effect) => effect.kind === "apply-aura" && effect.sourceAuraType === 173);
|
|
const baseEffects = effects.filter((effect) => !(effect.kind === "apply-aura" && effect.sourceAuraType === 173));
|
|
if (!passiveTags.includes(ROM_WARDEN_WILL_ATTACK_TAG) || !reduction || reduction.kind !== "apply-aura") {
|
|
return baseEffects;
|
|
}
|
|
return [...baseEffects, {
|
|
...reduction,
|
|
recipient: "ability-target",
|
|
aura: {
|
|
...reduction.aura,
|
|
id: "rom-warden-will-attack",
|
|
name: "Will Attack",
|
|
disposition: "debuff",
|
|
utilityTags: [...(reduction.aura.utilityTags ?? []), ROM_WARDEN_WILL_ATTACK_TAG],
|
|
},
|
|
}];
|
|
}
|
|
|
|
if (ability.dbcSpellId === 493399 && ability.classId === "rom-warden") {
|
|
return effects.map((effect) => effect.kind === "apply-aura"
|
|
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "damage"
|
|
&& modifier.direction === "taken"
|
|
&& modifier.operation === "percent"
|
|
&& modifier.value === -ROM_WARDEN_DAMAGE_TRANSFER_FRACTION)
|
|
? {
|
|
...effect,
|
|
aura: {
|
|
...effect.aura,
|
|
// Component 503942 is the executable modifier: RuneWaker encodes
|
|
// ten seconds here. Component 503643 is tooltip-only and says 15s.
|
|
durationMs: 10_000,
|
|
utilityTags: [...(effect.aura.utilityTags ?? []), ROM_WARDEN_DAMAGE_TRANSFER_TAG],
|
|
},
|
|
}
|
|
: effect);
|
|
}
|
|
|
|
if (ability.dbcSpellId === 494043 && classPair(ability, "rom-rogue", "rom-warden")) {
|
|
// The source describes a chance on the second hit but contains no proc rate.
|
|
// Keep the guaranteed base strike and do not turn an unknown chance into 100%.
|
|
let keptBaseHit = false;
|
|
return effects.filter((effect) => {
|
|
if (effect.kind !== "damage") return true;
|
|
if (keptBaseHit) return false;
|
|
keptBaseHit = true;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
if (ability.dbcSpellId === 499495 && classPair(ability, "rom-warrior", "rom-warden")) {
|
|
return [
|
|
casterAura(ability, "rom-warden-awakening-root-slow-immunity", ability.name, 10_000, {
|
|
controlImmunities: ["root", "slow"],
|
|
utilityTags: ["rom-awakening-of-the-wild-immunity"],
|
|
}),
|
|
casterAura(ability, "rom-warden-awakening-next-critical", ability.name, null, {
|
|
utilityTags: ["next-offensive-critical", "rom-awakening-of-the-wild-next-critical"],
|
|
}),
|
|
];
|
|
}
|
|
|
|
if (ability.dbcSpellId === 494323 && classPair(ability, "rom-druid", "rom-warden")) {
|
|
const heals = effects.filter((effect): effect is Extract<AbilityEffect, { kind: "heal" }> => effect.kind === "heal");
|
|
if (!heals.length) return effects;
|
|
if (naturePower < 1) return [heals[0]!];
|
|
return [
|
|
...heals.slice(0, 2),
|
|
{ kind: "resource-drain", amount: 1, burn: false, resource: "nature-power", recipient: "caster" },
|
|
];
|
|
}
|
|
|
|
if (ability.dbcSpellId === 499936 && classPair(ability, "rom-druid", "rom-warden")) {
|
|
const points = Math.max(0, Math.floor(naturePower));
|
|
if (points < 1) return [];
|
|
const heal = effects.find((effect): effect is Extract<AbilityEffect, { kind: "heal" }> => effect.kind === "heal");
|
|
if (!heal) return effects;
|
|
return [
|
|
{
|
|
...heal,
|
|
coefficient: 0,
|
|
percentMaxHealth: Math.min(1, points * 0.1),
|
|
basePoints: 0,
|
|
dieSides: 0,
|
|
pointsPerLevel: 0,
|
|
coefficientPerLevel: 0,
|
|
bonusPowerCoefficient: 0,
|
|
},
|
|
{ kind: "resource-drain", amount: points, burn: false, resource: "nature-power", recipient: "caster" },
|
|
];
|
|
}
|
|
|
|
if (ability.dbcSpellId === 494372 && classPair(ability, "rom-druid", "rom-warden")) {
|
|
return effects.map((effect) => effect.kind === "apply-aura" && effect.aura.modifiers?.length
|
|
? { ...effect, scope: "party" }
|
|
: effect);
|
|
}
|
|
|
|
return effects;
|
|
}
|