Files
healer-man/src/game/wow335WarlockSoulLinkRuntime.ts
T
2026-09-30 09:57:13 -04:00

66 lines
2.4 KiB
TypeScript

export const WOW335_WARLOCK_SOUL_LINK_TAG = "wow335-warlock-soul-link";
const WOW335_SOUL_LINKABLE_DEMON_SPELLS = new Set([
"wow335-warlock-summon-voidwalker",
"wow335-warlock-summon-succubus",
"wow335-warlock-summon-felhunter",
"wow335-warlock-summon-felguard",
"wow335-warlock-inferno",
]);
export interface WarlockSoulLinkSummon {
readonly sourceAbilityId: string;
readonly ownerActorId: string;
readonly health: number;
readonly expiresAt: number;
readonly controlledUntil: number;
readonly position: readonly [number, number, number];
}
/** Soul Link is 6%, plus Demonic Embrace's local rank tooltip of 2/4/6%. */
export function warlockSoulLinkSharePercent(
soulLinkRank: number,
demonicEmbraceRank: number,
): number {
const linkRank = Math.max(0, Math.min(1, Math.trunc(Number.isFinite(soulLinkRank) ? soulLinkRank : 0)));
if (linkRank === 0) return 0;
const embraceRank = Math.max(0, Math.min(3, Math.trunc(Number.isFinite(demonicEmbraceRank) ? demonicEmbraceRank : 0)));
return 6 + 2 * embraceRank;
}
/**
* Selects the caster's live, controlled Warlock demon within the generated
* talent tooltip's 30-yard link distance. The simulator represents yards as
* ten combat-position units.
*/
export function warlockSoulLinkDemon<T extends WarlockSoulLinkSummon>(
summons: readonly T[],
ownerActorId: string,
ownerPosition: readonly [number, number, number],
now: number,
): T | null {
return summons.find((summon) => (
WOW335_SOUL_LINKABLE_DEMON_SPELLS.has(summon.sourceAbilityId)
&& summon.ownerActorId === ownerActorId
&& summon.health > 0
&& summon.expiresAt > now
&& summon.controlledUntil <= now
&& Math.hypot(
summon.position[0] - ownerPosition[0],
summon.position[1] - ownerPosition[1],
summon.position[2] - ownerPosition[2],
) <= 300
)) ?? null;
}
/** Split post-mitigation incoming damage; the transferred portion bypasses prevention. */
export function warlockSoulLinkDamageSplit(amount: number, sharePercent: number): {
readonly casterDamage: number;
readonly demonDamage: number;
} {
const safeAmount = Math.max(0, Math.trunc(Number.isFinite(amount) ? amount : 0));
const safePercent = Math.max(0, Math.min(100, Number.isFinite(sharePercent) ? sharePercent : 0));
const demonDamage = Math.min(safeAmount, Math.round(safeAmount * safePercent / 100));
return { casterDamage: safeAmount - demonDamage, demonDamage };
}