Update Healer Man server

This commit is contained in:
phenom committed 2026-09-30 09:57:13 -04:00
1 parent 35dfdaed61
commit 4fb7a0b9c8
161 files changed
+24618 -529

No files matched your search

+155 -6
View File
@@ -5,6 +5,15 @@ import {
ROM_ROGUE_DAGGER_MASTERY_TAG,
ROM_ROGUE_TREASURE_HUNTER_TAG,
} from "./romRogueMechanics";
import {
ROM_ROGUE_CHAMPION_NERVE_GEAR_MARKER_TAG,
ROM_ROGUE_CHAMPION_NERVE_GEAR_PASSIVE_SPELL_ID,
} from "./romRogueChampionNerveGearRuntime";
export const ROM_ROGUE_MAGE_ENCHANTED_THROW_TAG = "rom-491178-enchanted-throw";
export const ROM_ROGUE_PRIEST_SINISTER_METHODS_TAG = "rom-rogue-priest-sinister-methods";
export const ROM_ROGUE_PRIEST_SINISTER_METHODS_COOLDOWN_KEY = "rom-rogue-priest-sinister-methods";
export const ROM_SCOUT_PRIEST_SNIPER_TAG = "rom-scout-priest-sniper";
function firstDamage(effects: readonly AbilityEffect[]): AbilityEffect[] {
return effects.filter((effect) => effect.kind === "damage").slice(0, 1);
@@ -56,14 +65,18 @@ function customProc(
sourceId: number,
trigger: "hit" | "crit",
actionId: string,
options: { readonly chance?: number; readonly internalCooldownMs?: number } = {},
options: {
readonly chance?: number;
readonly internalCooldownMs?: number;
readonly data?: Readonly<Record<string, string | number | boolean>>;
} = {},
): AuraProcDefinition {
return {
id: `rom-pair-proc-${sourceId}`,
triggers: trigger === "hit" ? ["hit", "crit"] : trigger,
chance: options.chance ?? 1,
...(options.internalCooldownMs ? { internalCooldownMs: options.internalCooldownMs } : {}),
action: { kind: "custom", id: actionId },
action: { kind: "custom", id: actionId, ...(options.data ? { data: options.data } : {}) },
};
}
@@ -91,8 +104,8 @@ function timedCasterAura(
}
/**
* Source-specific translations for the accepted Scout/Rogue and Rogue/Warrior
* pairings. Passive tags and custom proc IDs are consumed by combat-store
* Source-specific translations for the accepted Scout paired-class
* mechanics. Passive tags and custom proc IDs are consumed by combat-store
* transitions; they intentionally do not apply unrelated generic stat buffs.
*/
export function overrideRomScoutRogueEffects(
@@ -100,6 +113,28 @@ export function overrideRomScoutRogueEffects(
translated: readonly AbilityEffect[],
): readonly AbilityEffect[] | null {
switch (row.sourceSkillId) {
case 497206: { // Dodge arms the usable spell; its child applies the three-second concealment.
if (row.classId !== "rom-rogue" || row.secondaryClassId !== "rom-mage") return null;
const concealment = row.source?.components?.find((component) => component.id === 502597);
const durationMs = Math.max(1_000, Math.round((concealment?.effectTime ?? 3) * 1_000));
return [{
kind: "apply-aura",
recipient: "caster",
aura: {
id: "rom-rogue:instant-shadow",
name: "Instant Shadow",
disposition: "buff",
durationMs,
maxStacks: 1,
stackBehavior: "refresh",
utilityTags: ["stealth"],
},
sourceEffectType: row.source?.sourceEffectType ?? -1,
sourceAuraType: -1,
miscValue: 0,
triggerSpellId: 502597,
}];
}
case 490504:
return taggedRoguePassive(490504, "Dagger Mastery", ROM_ROGUE_DAGGER_MASTERY_TAG);
case 490316:
@@ -109,6 +144,46 @@ export function overrideRomScoutRogueEffects(
// is no distinct RoM off-hand hit yet, so do not turn either value into a
// generic player melee modifier.
return [];
case 493020: { // Successful Low Blow/Blind Spot hits trigger the local 10s crit buff.
if (row.classId !== "rom-rogue" || row.secondaryClassId !== "rom-priest") return null;
const buff = row.source?.components?.find((component) => component.id === 503265);
const criticalModifier = buff?.abilityModifiers?.find((modifier) => modifier.type === 18);
const cooldown = row.source?.components?.find((component) => component.id === 503300);
const durationMs = Math.max(0, Math.round((buff?.effectTime ?? 0) * 1_000));
const cooldownMs = Math.max(0, Math.round((cooldown?.effectTime ?? 0) * 1_000));
const proc = criticalModifier && durationMs > 0 && cooldownMs > 0
? customProc(493020, "hit", "rom-sinister-methods", {
data: { criticalStrike: criticalModifier.value, durationMs, cooldownMs },
})
: undefined;
return taggedPassive(493020, "Sinister Methods", ROM_ROGUE_PRIEST_SINISTER_METHODS_TAG, proc);
}
case ROM_ROGUE_CHAMPION_NERVE_GEAR_PASSIVE_SPELL_ID:
if (row.classId !== "rom-rogue" || row.secondaryClassId !== "rom-champion") return null;
// The timed child only triggers after an elite skill use. Keep a learned
// marker, not its generic aura, so party passive loading cannot grant the
// attack-speed/dodge payload on an unrelated cast.
return taggedPassive(
ROM_ROGUE_CHAMPION_NERVE_GEAR_PASSIVE_SPELL_ID,
"Nerve Gear",
ROM_ROGUE_CHAMPION_NERVE_GEAR_MARKER_TAG,
);
case 491178:
if (row.classId !== "rom-rogue" || row.secondaryClassId !== "rom-mage") return null;
// Hidden child 491185 has no generated payload. The auto-attack runtime
// applies its referenced Throw (490309) damage explicitly, so keep the
// tag and avoid also registering the untranslated generic child proc.
if (translated.some((effect) => effect.kind === "apply-aura")) {
return translated.map((effect) => effect.kind === "apply-aura" ? {
...effect,
aura: {
...effect.aura,
procs: [],
utilityTags: [...(effect.aura.utilityTags ?? []), ROM_ROGUE_MAGE_ENCHANTED_THROW_TAG],
},
} : effect);
}
return [...translated, ...taggedRoguePassive(491178, "Enchanted Throw", ROM_ROGUE_MAGE_ENCHANTED_THROW_TAG)];
case 491552:
if (row.classId !== "rom-rogue") return null;
return translated.map((effect) => effect.kind === "apply-aura" ? {
@@ -164,6 +239,17 @@ export function overrideRomScoutRogueEffects(
case 493006:
return taggedPassive(493006, "Accurate Targeting", "rom-accurate-targeting",
customProc(493006, "hit", "rom-accurate-targeting", { internalCooldownMs: 30_000 }));
case 491489:
if (row.classId !== "rom-scout" || row.secondaryClassId !== "rom-warrior") return null;
return taggedPassive(491489, "Enhanced Throat Attack", "rom-enhanced-throat-attack",
customProc(491489, "hit", "rom-enhanced-throat-attack"));
case 491532:
if (row.classId !== "rom-rogue" || row.secondaryClassId !== "rom-scout") return null;
return taggedPassive(491532, "Slit Throat", "rom-slit-throat",
customProc(491532, "hit", "rom-slit-throat"));
case 492344:
if (row.classId !== "rom-scout" || row.secondaryClassId !== "rom-priest") return null;
return taggedPassive(492344, "Sniper", ROM_SCOUT_PRIEST_SNIPER_TAG);
case 493007:
return taggedPassive(493007, "Deadly Counterattack", "rom-deadly-counterattack",
customProc(493007, "hit", "rom-deadly-counterattack", { internalCooldownMs: 20_000 }));
@@ -301,8 +387,14 @@ export function romScoutRogueCastEffects(
breakOnDamage: true,
}];
}
if (ability.dbcSpellId === 490309 && passives.has("rom-throwing-mastery")) {
return [...effects, { kind: "status", status: "slow", durationMs: 5_000, magnitude: 0.4 }];
if (ability.dbcSpellId === 490309) {
return [
...effects,
...(passives.has("rom-throwing-mastery")
? [{ kind: "status" as const, status: "slow" as const, durationMs: 5_000, magnitude: 0.4 }]
: []),
{ kind: "cooldown-reset", mode: "reduce", abilityName: "Day of Rain", amountMs: 1_000 },
];
}
if (ability.dbcSpellId === 492621) {
const hasSourcePoison = target.has("rom-deadly-poison") || target.has("rom-501690");
@@ -316,6 +408,26 @@ export function romScoutRogueCastEffects(
return effects;
}
/** Day of Rain has an exact 50% miss chance while Enchanted Throw is active. */
export function romRogueMageDayOfRainBaseMissPercent(
classId: string,
secondaryClassId: string | null | undefined,
abilitySpellId: number | undefined,
activeAuraTags: readonly string[],
attackerHitPercent: number,
): number | null {
if (classId !== "rom-rogue"
|| secondaryClassId !== "rom-mage"
|| abilitySpellId !== 491183
|| !activeAuraTags.includes(ROM_ROGUE_MAGE_ENCHANTED_THROW_TAG)) return null;
const hitPercent = Number.isFinite(attackerHitPercent)
? Math.max(0, Math.min(100, attackerHitPercent))
: 0;
// combatAttackTable subtracts attacker hit rating from base miss chance.
// Offset it here so the tooltip's 50% rate remains exact at any hit rating.
return 50 + hitPercent;
}
/** 499481/622993 adds a hit only to targets already carrying 622992's poison. */
export function romScoutRogueWhirlwindBonusEffect(
ability: AbilityDefinition,
@@ -333,6 +445,7 @@ export type RomScoutRogueProcActionSpec =
readonly recipient: "caster" | "target";
readonly aura: AuraDefinition;
readonly provenance: "source" | "hybrid-estimate";
readonly cooldownMs?: number;
}
| {
readonly kind: "dot";
@@ -352,6 +465,13 @@ export type RomScoutRogueProcActionSpec =
readonly recipient: "target";
readonly effect: Extract<AbilityEffect, { kind: "damage" }>;
readonly provenance: "source" | "hybrid-estimate";
}
| {
readonly kind: "status";
readonly recipient: "target";
readonly status: "silence";
readonly durationMs: number;
readonly provenance: "source";
};
/**
@@ -364,8 +484,37 @@ export function romScoutRogueProcActionSpec(
actionId: string,
triggeringSourceSkillId: number,
casterPoisonous: boolean,
data: Readonly<Record<string, string | number | boolean>> = {},
): RomScoutRogueProcActionSpec | null {
switch (actionId) {
case "rom-enhanced-throat-attack":
case "rom-slit-throat":
if (triggeringSourceSkillId !== 490428) return null;
return { kind: "status", recipient: "target", status: "silence", durationMs: 4_000, provenance: "source" };
case "rom-sinister-methods": {
if (![490323, 490311].includes(triggeringSourceSkillId)) return null;
const criticalStrike = data.criticalStrike;
const durationMs = data.durationMs;
const cooldownMs = data.cooldownMs;
if (typeof criticalStrike !== "number" || !Number.isFinite(criticalStrike)
|| typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs <= 0
|| typeof cooldownMs !== "number" || !Number.isFinite(cooldownMs) || cooldownMs <= 0) return null;
return {
kind: "aura",
recipient: "caster",
aura: {
id: "rom-pair-sinister-methods-critical",
name: "Sinister Methods",
disposition: "buff",
durationMs,
maxStacks: 1,
stackBehavior: "refresh",
modifiers: [{ kind: "stat", stat: "physical-critical-strike", operation: "flat", value: criticalStrike }],
},
cooldownMs,
provenance: "source",
};
}
case "rom-accurate-targeting": {
if (triggeringSourceSkillId !== 490423) return null;
return {