forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -0,0 +1,188 @@
|
||||
import type { AbilityDefinition, AbilityEffect, AbilityRankDefinition } from "./abilityCatalog";
|
||||
import type { ActiveAura } from "./combatAuras";
|
||||
import { runtimeTalentNodeById } from "./talentRuntimeCatalog";
|
||||
|
||||
const THE_BEAST_WITHIN_NODE_ID = "talent-1803";
|
||||
export const WOW335_HUNTER_LOCK_AND_LOAD_TALENT_ID = "talent-1306";
|
||||
export const WOW335_HUNTER_ENTRAPMENT_TALENT_ID = "talent-1304";
|
||||
export const WOW335_HUNTER_LOCK_AND_LOAD_READY_TAG = "wow335-hunter-lock-and-load-ready";
|
||||
export const WOW335_HUNTER_LOCK_AND_LOAD_ICD_TAG = "wow335-hunter-lock-and-load-icd";
|
||||
|
||||
export interface BeastWithinRankMechanics {
|
||||
readonly description: string;
|
||||
readonly procChance: number;
|
||||
readonly additionalDamage: number;
|
||||
readonly manaCostReduction: number;
|
||||
readonly durationMs: number;
|
||||
}
|
||||
|
||||
export interface LockAndLoadRankMechanics {
|
||||
readonly description: string;
|
||||
readonly trapProcChance: number;
|
||||
readonly periodicProcChance: number;
|
||||
readonly freeCastCount: number;
|
||||
readonly durationMs: number;
|
||||
readonly internalCooldownMs: number;
|
||||
}
|
||||
|
||||
export interface EntrapmentRankMechanics {
|
||||
readonly description: string;
|
||||
readonly frostTrapSlowBonus: number;
|
||||
readonly rootProcChance: number;
|
||||
readonly rootDurationMs: number;
|
||||
}
|
||||
|
||||
function tooltipNumber(description: string, pattern: RegExp): number | null {
|
||||
const match = description.match(pattern);
|
||||
if (!match) return null;
|
||||
const value = Number(match[1]);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
/** Read The Beast Within's mechanics from the generated talent rank text. */
|
||||
export function beastWithinRankMechanics(rank: number): BeastWithinRankMechanics | null {
|
||||
const node = runtimeTalentNodeById(THE_BEAST_WITHIN_NODE_ID);
|
||||
const description = node?.rankDescriptions[Math.trunc(rank) - 1];
|
||||
if (!description) return null;
|
||||
|
||||
const procChance = tooltipNumber(description, /you have a (\d+(?:\.\d+)?)% chance/i);
|
||||
const additionalDamage = tooltipNumber(description, /causing (\d+(?:\.\d+)?)% additional damage/i);
|
||||
const manaCostReduction = tooltipNumber(description, /reducing mana costs of all spells by (\d+(?:\.\d+)?)%/i);
|
||||
const durationSeconds = tooltipNumber(description, /for (\d+(?:\.\d+)?) seconds/i);
|
||||
if (procChance === null || additionalDamage === null || manaCostReduction === null || durationSeconds === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
description,
|
||||
procChance: Math.max(0, Math.min(1, procChance / 100)),
|
||||
additionalDamage: additionalDamage / 100,
|
||||
manaCostReduction: manaCostReduction / 100,
|
||||
durationMs: Math.max(0, Math.round(durationSeconds * 1_000)),
|
||||
};
|
||||
}
|
||||
|
||||
/** Read Lock and Load's rank values from its generated tooltip text. */
|
||||
export function lockAndLoadRankMechanics(rank: number): LockAndLoadRankMechanics | null {
|
||||
const node = runtimeTalentNodeById(WOW335_HUNTER_LOCK_AND_LOAD_TALENT_ID);
|
||||
const description = node?.rankDescriptions[Math.trunc(rank) - 1];
|
||||
if (!description) return null;
|
||||
|
||||
const trapProcPercent = tooltipNumber(description, /trapping a target[^.]*?has a (\d+(?:\.\d+)?)% chance/i);
|
||||
const periodicProcPercent = tooltipNumber(description, /periodic damage[^.]*?has a (\d+(?:\.\d+)?)% to trigger/i);
|
||||
const freeCastCount = tooltipNumber(description, /your next (\d+) Arcane Shot or Explosive Shot/i);
|
||||
const cooldownSeconds = tooltipNumber(description, /this effect has a (\d+(?:\.\d+)?) sec cooldown/i);
|
||||
if (trapProcPercent === null || periodicProcPercent === null || freeCastCount === null || cooldownSeconds === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const durationMs = Math.max(0, Math.round(cooldownSeconds * 1_000));
|
||||
return {
|
||||
description,
|
||||
trapProcChance: Math.max(0, Math.min(1, trapProcPercent / 100)),
|
||||
periodicProcChance: Math.max(0, Math.min(1, periodicProcPercent / 100)),
|
||||
freeCastCount: Math.max(0, Math.trunc(freeCastCount)),
|
||||
durationMs,
|
||||
internalCooldownMs: durationMs,
|
||||
};
|
||||
}
|
||||
|
||||
/** Read the authored Entrapment slow/root values from the generated talent tooltip. */
|
||||
export function entrapmentRankMechanics(rank: number): EntrapmentRankMechanics | null {
|
||||
const normalizedRank = Math.trunc(rank);
|
||||
const node = runtimeTalentNodeById(WOW335_HUNTER_ENTRAPMENT_TALENT_ID);
|
||||
const description = node?.rankDescriptions[normalizedRank - 1];
|
||||
if (!description) return null;
|
||||
|
||||
const slowPercent = tooltipNumber(description, /increases the slow effect of Frost Trap by (\d+(?:\.\d+)?)%/i);
|
||||
const rootDurationSeconds = tooltipNumber(description, /preventing them from moving for (\d+(?:\.\d+)?) seconds/i);
|
||||
if (slowPercent === null || rootDurationSeconds === null) return null;
|
||||
|
||||
// The local Hunter DBC Entrapment rank rows carry 2/4/6 as their proc base
|
||||
// points; their companion child aura supplies the root duration.
|
||||
const rootProcChance = [0, 0.02, 0.04, 0.06][normalizedRank];
|
||||
if (rootProcChance === undefined) return null;
|
||||
return {
|
||||
description,
|
||||
frostTrapSlowBonus: slowPercent / 100,
|
||||
rootProcChance,
|
||||
rootDurationMs: Math.max(0, Math.round(rootDurationSeconds * 1_000)),
|
||||
};
|
||||
}
|
||||
|
||||
/** Spell.dbc child-aura durations for Freezing Trap's three learned ranks. */
|
||||
export function freezingTrapDurationMs(spellId: number): number {
|
||||
switch (spellId) {
|
||||
case 1499: return 10_000;
|
||||
case 14310: return 15_000;
|
||||
case 14311:
|
||||
case 60192:
|
||||
case 60202: return 20_000;
|
||||
default: return 20_000;
|
||||
}
|
||||
}
|
||||
|
||||
export function consumeLockAndLoadCharge(
|
||||
auras: readonly ActiveAura[],
|
||||
auraInstanceId: string,
|
||||
): readonly ActiveAura[] {
|
||||
return auras.flatMap((aura) => aura.instanceId !== auraInstanceId
|
||||
? [aura]
|
||||
: aura.stacks > 1 ? [{ ...aura, stacks: aura.stacks - 1 }] : []);
|
||||
}
|
||||
|
||||
function applyBeastWithinRankMechanics(
|
||||
effects: readonly AbilityEffect[],
|
||||
mechanics: BeastWithinRankMechanics,
|
||||
): readonly AbilityEffect[] {
|
||||
return effects.map((effect) => {
|
||||
if (effect.kind !== "apply-aura") return effect;
|
||||
return {
|
||||
...effect,
|
||||
aura: {
|
||||
...effect.aura,
|
||||
description: mechanics.description,
|
||||
durationMs: mechanics.durationMs,
|
||||
modifiers: effect.aura.modifiers?.map((modifier) => {
|
||||
if (modifier.kind === "resource" && modifier.property === "cost" && modifier.resource === "power") {
|
||||
return { ...modifier, value: -mechanics.manaCostReduction };
|
||||
}
|
||||
if (modifier.kind === "damage" && modifier.direction === "dealt") {
|
||||
return { ...modifier, value: mechanics.additionalDamage };
|
||||
}
|
||||
return modifier;
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Replace the duplicate DBC child-rank text and payload with the generated talent's actual ranks. */
|
||||
export function applyGeneratedBeastWithinTalent(ability: AbilityDefinition): AbilityDefinition {
|
||||
if (ability.classId !== "hunter" || ability.name !== "The Beast Within") return ability;
|
||||
const node = runtimeTalentNodeById(THE_BEAST_WITHIN_NODE_ID);
|
||||
const sourceRank = ability.ranks?.[0];
|
||||
if (!node || !sourceRank || node.rankDescriptions.length === 0) return ability;
|
||||
|
||||
const ranks: AbilityRankDefinition[] = [];
|
||||
for (const [index, description] of node.rankDescriptions.entries()) {
|
||||
const mechanics = beastWithinRankMechanics(index + 1);
|
||||
if (!mechanics) return ability;
|
||||
ranks.push({
|
||||
...sourceRank,
|
||||
rank: index + 1,
|
||||
description,
|
||||
effects: applyBeastWithinRankMechanics(sourceRank.effects, mechanics),
|
||||
});
|
||||
}
|
||||
|
||||
const firstRank = ranks[0];
|
||||
if (!firstRank) return ability;
|
||||
return {
|
||||
...ability,
|
||||
talentNodeId: node.id,
|
||||
description: firstRank.description,
|
||||
effects: firstRank.effects,
|
||||
ranks,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user