import type { ActiveAura, AuraDefinition } from "./combatAuras"; export const ROM_WARDEN_REVIVAL_STATE_TAG = "rom-warden-revival-state"; export const ROM_WARDEN_REVIVAL_CHARGE_TAG = "rom-revival-charge"; export const ROM_WARDEN_REVIVAL_DODGE_TAG = "rom-revival-dodge"; export const ROM_WARDEN_REVIVAL_CHARGE_DURATION_MS = 15_000; export const ROM_WARDEN_REVIVAL_DODGE_DURATION_MS = 6_000; export function romWardenRevivalBuildMatches(classId: string, secondaryClassId: string | null | undefined): boolean { return classId === "rom-rogue" && secondaryClassId === "rom-warden"; } export function romWardenRevivalStateIsActive( auras: readonly ActiveAura[], entityId: string, now: number, ): boolean { return auras.some((aura) => aura.sourceId === entityId && aura.targetId === entityId && (aura.expiresAt === null || aura.expiresAt > now) && aura.definition.utilityTags?.includes(ROM_WARDEN_REVIVAL_STATE_TAG)); } export interface RomWardenRevivalIncomingHitInput { readonly classId: string; readonly secondaryClassId?: string | null; readonly stateActive: boolean; /** Normal excludes critical, dodge, miss, and fully absorbed source events. */ readonly normal: boolean; /** Health damage after shields and other absorption. */ readonly healthDamage: number; /** Source calls AddHP before the proc, so dead owners cannot receive it. */ readonly healthAfter: number; } export function romWardenRevivalShouldTriggerOnIncomingHit(input: RomWardenRevivalIncomingHitInput): boolean { return romWardenRevivalBuildMatches(input.classId, input.secondaryClassId) && input.stateActive && input.normal && Number.isFinite(input.healthDamage) && input.healthDamage > 0 && Number.isFinite(input.healthAfter) && input.healthAfter > 0; } export const ROM_WARDEN_REVIVAL_CHARGE_AURA: AuraDefinition = { id: "rom-state-revival-charge", name: "Revival charge", description: "This RuneWaker state can stack up to ten times and expires after 15 seconds.", disposition: "buff", durationMs: ROM_WARDEN_REVIVAL_CHARGE_DURATION_MS, maxStacks: 10, stackBehavior: "add", utilityTags: [ROM_WARDEN_REVIVAL_CHARGE_TAG], }; export const ROM_WARDEN_REVIVAL_DODGE_AURA: AuraDefinition = { id: "rom-state-revival-dodge", name: "Revival dodge", description: "RuneWaker component 623527 adds 20 physical dodge-rate points for six seconds.", disposition: "buff", durationMs: ROM_WARDEN_REVIVAL_DODGE_DURATION_MS, stackBehavior: "refresh", utilityTags: [ROM_WARDEN_REVIVAL_DODGE_TAG], // Source type 198 is physical dodge rate; the current runtime consumes this // as physical dodge. Type 200's magical-dodge channel has no resolver yet. modifiers: [{ kind: "stat", stat: "dodge", operation: "flat", value: 20 }], }; export interface RomWardenRevivalHealInput { /** Current stack count of RuneWaker component 623072 (source cap: ten). */ readonly stacks: number; /** Max HP of the self-target receiving hidden child spell 499543. */ readonly targetMaxHealth: number; } /** * Returns Revival's source-backed, noncritical heal amount before overheal. * * Lua un_499542 passes FN_CountBuffLevel(owner, 623072) as 499543's MagicLv. * Child component 623071 has DmgPower 10 and DmgPower_SkillLVArg 50, so the * source's FixPer branch computes `(10 + 5 * stacks)%` of the target's MaxHP. * Its FixValue is zero; the other fixed-damage argument is not used by FixPer. * The child MagicCollect targets self. This path does not scale from weapon * damage, attack speed, MagicPow, or critical power. Positive Magic defense * conversion leaves the heal unchanged in the source's CalMagicDMG routine. */ export function romWardenRevivalNormalHeal(input: RomWardenRevivalHealInput): number { const stacks = Number.isFinite(input.stacks) ? Math.max(0, Math.min(10, Math.floor(input.stacks))) : 0; const maxHealth = Number.isFinite(input.targetMaxHealth) ? Math.max(0, input.targetMaxHealth) : 0; return maxHealth * (10 + 5 * stacks) / 100; }