Files
healer-man/src/game/romRoguePoisonRuntime.ts
T
2026-10-02 10:38:12 -04:00

146 lines
5.8 KiB
TypeScript

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;
}