188 lines
7.4 KiB
TypeScript
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);
|
|
}
|