import type { ActiveAura, AuraDefinition, AttackKind } from "./combatAuras"; export const ROM_ROGUE_POISON_SKILL_ID = 490329; export const ROM_ROGUE_POISON_COMPONENT_ID = 500715; export const ROM_ROGUE_POISON_TRIGGER_SPELL_ID = 490357; export const ROM_ROGUE_POISON_AURA_TAG = "rom-poisonous"; export const ROM_ROGUE_POISONED_TARGET_TAG = "rom-poisonous-hands-debuff"; export const ROM_ROGUE_POISON_PROC_CHANCE = 0.5; export const ROM_ROGUE_POISON_MAX_STACKS = 10; export const ROM_ROGUE_POISON_DAMAGE_REDUCTION = 0.08; export const ROM_ROGUE_POISON_ATTACK_SPEED_PER_STACK_PERCENT = 1; export const ROM_ROGUE_POISON_DEBUFF_DURATION_MS = 900_000; export const ROM_ROGUE_POISON_ATTACK_SPEED_TAG = "rom-poisonous-hands-attack-speed"; const ROM_ROGUE_POISON_ATTACK_SPEED_TARGET_PREFIX = "rom-poisonous-hands-target:"; export interface RomRoguePoisonAttackContext { readonly classId: string; readonly sourceActorId: string; readonly targetActorId: string; readonly attackKind: AttackKind; readonly isNormalAttack: boolean; readonly attackLanded: boolean; readonly poisonActive: boolean; readonly roll: number; /** Target represented by the Rogue's current Poisonous Hands speed buff, if any. */ readonly attackSpeedTargetId?: string | null; /** Existing Poisonous Hands stacks on this attack's target for this Rogue. */ readonly targetPoisonStacks?: number; } export interface RomRoguePoisonApplication { readonly sourceId: string; readonly targetId: string; readonly definition: AuraDefinition; readonly stacks: 1; } export interface RomRoguePoisonAttackResult { /** Clear the prior target's speed ramp when the Rogue changes targets. */ readonly resetAttackSpeed: boolean; /** Set only when this attack procs the child Poisoned effect. */ readonly application: RomRoguePoisonApplication | null; /** A successful Poisoned application also advances the current-target speed ramp. */ readonly attackSpeedApplication: RomRoguePoisonApplication | null; } export function romRoguePoisonAttackSpeedTargetId(aura: ActiveAura): string | null { return aura.definition.utilityTags ?.find((tag) => tag.startsWith(ROM_ROGUE_POISON_ATTACK_SPEED_TARGET_PREFIX)) ?.slice(ROM_ROGUE_POISON_ATTACK_SPEED_TARGET_PREFIX.length) ?? null; } export function romRoguePoisonAttackSpeedAuraForTarget(targetId: string): AuraDefinition { return { id: "rom-poisonous-hands-attack-speed", name: "Poisonous Hands Attack Speed", disposition: "buff", // CoA does not list a separate timer for the ramp; scope it to the same // 900-second Poisonous Hands window and clear it on a target switch. durationMs: ROM_ROGUE_POISON_DEBUFF_DURATION_MS, maxStacks: ROM_ROGUE_POISON_MAX_STACKS, stackBehavior: "add", uniqueBySource: true, utilityTags: [ROM_ROGUE_POISON_ATTACK_SPEED_TAG, `${ROM_ROGUE_POISON_ATTACK_SPEED_TARGET_PREFIX}${targetId}`], modifiers: [{ kind: "stat", stat: "melee-haste", operation: "flat", value: ROM_ROGUE_POISON_ATTACK_SPEED_PER_STACK_PERCENT, perStack: true, }], }; } /** * Current CoA Poisonous Hands (490329/500715) triggers child 490357 on a * successful normal attack. Each proc stacks Poisoned on the target to ten, * with a fixed 8% outgoing-damage reduction. The current-target speed ramp * advances on the same proc and is reset by a landed normal attack on a new * target. The source gives the exact 50% trigger rate locally; CoA patch notes * supply the revised ten-stack and attack-speed behavior. */ export function romRoguePoisonAttackResultForAttack( context: RomRoguePoisonAttackContext, ): RomRoguePoisonAttackResult | null { if (context.classId !== "rom-rogue" || !context.sourceActorId || !context.targetActorId || context.sourceActorId === context.targetActorId || context.attackKind === "spell" || !context.isNormalAttack || !context.attackLanded || !context.poisonActive || !Number.isFinite(context.roll) || context.roll < 0) return null; const resetAttackSpeed = context.attackSpeedTargetId != null && context.attackSpeedTargetId !== context.targetActorId; const application = context.roll < ROM_ROGUE_POISON_PROC_CHANCE ? { sourceId: context.sourceActorId, targetId: context.targetActorId, stacks: 1 as const, definition: { id: `rom-rogue-poisoned:${ROM_ROGUE_POISON_TRIGGER_SPELL_ID}`, name: "Poisoned", disposition: "debuff" as const, durationMs: ROM_ROGUE_POISON_DEBUFF_DURATION_MS, maxStacks: ROM_ROGUE_POISON_MAX_STACKS, stackBehavior: "add" as const, uniqueBySource: true, dispelCategory: "poison" as const, utilityTags: [ROM_ROGUE_POISONED_TARGET_TAG], modifiers: [{ kind: "damage" as const, direction: "dealt" as const, operation: "percent" as const, value: -ROM_ROGUE_POISON_DAMAGE_REDUCTION, // CoA says the target's damage is reduced by 8%; later stacks do not // multiply that penalty (patch 12.1.0.4000). perStack: false, }], }, } : null; return { resetAttackSpeed, application, attackSpeedApplication: application && Math.max(0, context.targetPoisonStacks ?? 0) < ROM_ROGUE_POISON_MAX_STACKS ? { sourceId: context.sourceActorId, // Player runtime maps this logical self target to its aura entity ID. targetId: context.sourceActorId, definition: romRoguePoisonAttackSpeedAuraForTarget(context.targetActorId), stacks: 1, } : null, }; } /** Compatibility helper for callers/tests that only need the target debuff. */ export function romRoguePoisonApplicationForAttack( context: RomRoguePoisonAttackContext, ): RomRoguePoisonApplication | null { return romRoguePoisonAttackResultForAttack(context)?.application ?? null; }