98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
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;
|
|
}
|