Files
healer-man/src/game/romBloodArrow.ts
T
2026-10-04 22:51:21 -04:00

188 lines
7.4 KiB
TypeScript

import type { AbilityDefinition, ResourceType } from "./abilityCatalog";
import type { ActiveAura } from "./combatAuras";
export const ROM_SCOUT_BLOOD_ARROW_ABILITY_ID = "rom-scout-general-4";
export const ROM_SCOUT_BLOOD_ARROW_SOURCE_SKILL_ID = 490434;
export const ROM_SCOUT_BLOOD_ARROW_AURA_TAG = "rom-self-health-drain";
export const ROM_SCOUT_BLOOD_ARROW_DEACTIVATION_COOLDOWN_MS = 10_000;
export interface RomBloodArrowOwnerBuild {
readonly classId: string;
readonly secondaryClassId?: string | null;
}
export function isRomScoutBloodArrowAbility(ability: AbilityDefinition): boolean {
return ability.source === "runewaker"
&& ability.id === ROM_SCOUT_BLOOD_ARROW_ABILITY_ID
&& ability.classId === "rom-scout"
&& ability.secondaryClassId === undefined
&& ability.dbcSpellId === ROM_SCOUT_BLOOD_ARROW_SOURCE_SKILL_ID;
}
export function isRomKnightScoutBloodArrowOwner(owner: RomBloodArrowOwnerBuild): boolean {
return owner.classId === "rom-knight" && owner.secondaryClassId === "rom-scout";
}
export function isRomScoutRogueBloodArrowOwner(owner: RomBloodArrowOwnerBuild): boolean {
return owner.classId === "rom-scout" && owner.secondaryClassId === "rom-rogue";
}
export function isRomBloodArrowOwner(owner: RomBloodArrowOwnerBuild): boolean {
return isRomKnightScoutBloodArrowOwner(owner) || isRomScoutRogueBloodArrowOwner(owner);
}
function bloodArrowDescriptionForOwner(owner: RomBloodArrowOwnerBuild): string {
if (isRomKnightScoutBloodArrowOwner(owner)) {
return "Activates Blood Arrow and increases ranged weapon damage. Each offensive ranged weapon skill costs 2.0% HP and 1.0% MP. Right-click the buff icon or use the skill again to deactivate; the skill enters a 10-second cooldown after deactivation.";
}
if (isRomScoutRogueBloodArrowOwner(owner)) {
return "Activates Blood Arrow and increases physical ranged weapon damage. Ranged attacks cost 2.0% HP each. Right-click the buff icon or use the skill again to deactivate; the skill enters a 10-second cooldown after deactivation.";
}
return "Activates Blood Arrow and increases physical ranged weapon damage. Right-click the buff icon or use the skill again to deactivate; the skill enters a 10-second cooldown after deactivation.";
}
function indefiniteBloodArrowEffects(effects: AbilityDefinition["effects"]): AbilityDefinition["effects"] {
const auraEffect = effects.find((effect) => effect.kind === "apply-aura" && effect.recipient === "caster");
if (auraEffect?.kind !== "apply-aura") return effects;
return [{
...auraEffect,
aura: {
...auraEffect.aura,
durationMs: null,
utilityTags: auraEffect.aura.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)
? auraEffect.aura.utilityTags
: [...(auraEffect.aura.utilityTags ?? []), ROM_SCOUT_BLOOD_ARROW_AURA_TAG],
},
}];
}
export function romBloodArrowAbilityForOwner(
ability: AbilityDefinition,
owner: RomBloodArrowOwnerBuild,
): AbilityDefinition {
if (!isRomScoutBloodArrowAbility(ability) || !isRomBloodArrowOwner(owner)) return ability;
const description = bloodArrowDescriptionForOwner(owner);
return {
...ability,
description,
cooldownMs: 0,
effects: indefiniteBloodArrowEffects(ability.effects),
ranks: ability.ranks?.map((rank) => ({
...rank,
description,
cooldownMs: 0,
effects: indefiniteBloodArrowEffects(rank.effects),
})),
};
}
export function isRomKnightScoutOffensiveRangedWeaponSkill(
ability: AbilityDefinition,
owner: RomBloodArrowOwnerBuild,
): boolean {
if (!isRomKnightScoutBloodArrowOwner(owner)
|| ability.source !== "runewaker"
|| ability.passive
|| ability.dbcSpellId === ROM_SCOUT_BLOOD_ARROW_SOURCE_SKILL_ID
|| ability.dbcSpellId === 492589 // Autoshot is an ongoing mode, not a cast trigger.
|| ability.sourceAttackKind === "melee"
|| !ability.effects.some((effect) => effect.kind === "damage" || effect.kind === "dot")) return false;
const scoutSkill = ability.classId === "rom-scout"
&& ability.secondaryClassId === undefined
&& ability.romSkillGroup === "general";
const knightScoutElite = ability.classId === "rom-knight" && ability.secondaryClassId === "rom-scout";
return (scoutSkill || knightScoutElite)
&& (/ranged weapon DPS/i.test(ability.description) || ability.dbcSpellId === 490420);
}
export function isRomScoutRogueRangedAttackSkill(
ability: AbilityDefinition,
owner: RomBloodArrowOwnerBuild,
): boolean {
if (!isRomScoutRogueBloodArrowOwner(owner)
|| ability.source !== "runewaker"
|| ability.passive
|| ability.dbcSpellId === ROM_SCOUT_BLOOD_ARROW_SOURCE_SKILL_ID
|| ability.dbcSpellId === 492589 // Autoshot is an ongoing mode, not a cast trigger.
|| ability.sourceAttackKind === "melee"
|| !ability.effects.some((effect) => effect.kind === "damage" || effect.kind === "dot")) return false;
const scoutGeneralSkill = ability.classId === "rom-scout"
&& ability.secondaryClassId === undefined
&& (ability.romSkillGroup === "general" || ability.romSkillGroup === "primary");
return scoutGeneralSkill
&& (/ranged weapon DPS/i.test(ability.description) || ability.dbcSpellId === 490420);
}
export function romBloodArrowCastCosts(
ability: AbilityDefinition,
owner: RomBloodArrowOwnerBuild,
auras: readonly ActiveAura[],
ownerId: string,
maxHealth: number,
maxMana: number,
now?: number,
): readonly { readonly resource: ResourceType; readonly amount: number }[] {
if (!isRomBloodArrowOwner(owner)
|| !auras.some((aura) => aura.sourceId === ownerId
&& aura.targetId === ownerId
&& (now === undefined || aura.expiresAt === null || aura.expiresAt > now)
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG))) return [];
if (isRomKnightScoutBloodArrowOwner(owner)
&& isRomKnightScoutOffensiveRangedWeaponSkill(ability, owner)) {
return [
{ resource: "health", amount: Math.max(0, Math.round(maxHealth * 0.02)) },
{ resource: "mana", amount: Math.max(0, Math.round(maxMana * 0.01)) },
];
}
if (isRomScoutRogueBloodArrowOwner(owner)
&& isRomScoutRogueRangedAttackSkill(ability, owner)) {
return [{ resource: "health", amount: Math.max(0, Math.round(maxHealth * 0.02)) }];
}
return [];
}
export function romScoutRogueBloodArrowRangedAutoAttackCost(
owner: RomBloodArrowOwnerBuild,
auras: readonly ActiveAura[],
ownerId: string,
maxHealth: number,
now: number,
): number {
return isRomScoutRogueBloodArrowOwner(owner)
&& hasOwnedRomScoutBloodArrow(auras, ownerId, now)
? Math.max(0, Math.round(maxHealth * 0.02))
: 0;
}
export function hasOwnedRomScoutBloodArrow(
auras: readonly ActiveAura[],
ownerId: string,
now: number,
): boolean {
return auras.some((aura) => aura.sourceId === ownerId
&& aura.targetId === ownerId
&& (aura.expiresAt === null || aura.expiresAt > now)
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG));
}
export function isOwnedRomScoutBloodArrowAura(aura: ActiveAura, ownerId: string): boolean {
return aura.sourceId === ownerId
&& aura.targetId === ownerId
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG) === true;
}
export function deactivateRomBloodArrowFromAuraContextMenu(
aura: ActiveAura,
ownerId: string,
deactivate: (ownerId: string, now?: number) => boolean,
now?: number,
): boolean {
return isOwnedRomScoutBloodArrowAura(aura, ownerId)
&& deactivate(ownerId, now);
}