forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
4c768fbec6
commit
b16821e92e
330 files changed
+35406
-1760
No files matched your search
+499
-45
@@ -22,11 +22,63 @@ import { overrideRomMageWarlockEffects } from "./romMageWarlockOverrides";
|
||||
import { overrideRomMageEffects } from "./romMageOverrides";
|
||||
import { overrideRomWarlockEffects } from "./romWarlockOverrides";
|
||||
import { overrideRomDruidEffects } from "./romDruidOverrides";
|
||||
import { romPriestDescription } from "./romPriestTooltips";
|
||||
import { romPriestDescription, romPriestName } from "./romPriestTooltips";
|
||||
import { overrideRomPriestMageEffects } from "./romPriestMageRuntime";
|
||||
import { romPriestHolyBlessingEffects } from "./romPriestHolyBlessingRuntime";
|
||||
import {
|
||||
ROM_PRIEST_CHAMPION_BEACON_OF_REGENERATION_AURA,
|
||||
} from "./romPriestChampionBeaconRuntime";
|
||||
import { overrideRomPriestWarlockEffects } from "./romPriestWarlockOverrides";
|
||||
import { overrideRomChampionEffects } from "./romChampionOverrides";
|
||||
import { ROM_WARDEN_BRIAR_SHIELD_TAG } from "./romWardenOverrides";
|
||||
import { romWarriorCoaAdditionalAbilities } from "./romWarriorCoaCatalog";
|
||||
import { romWarriorDruidRageOfCalmnessCatalogOverride } from "./romWarriorDruidRageOfCalmness";
|
||||
import { romScoutPriestScreenAttackCatalogOverride } from "./romScoutPriestScreenAttack";
|
||||
import { romScoutPriestDrumsOfWarCatalogOverride } from "./romScoutPriestOverrides";
|
||||
import { romPriestScoutCuringShotCatalogOverride } from "./romPriestScoutCuringShot";
|
||||
import { romWarlockWarriorEnragedCoaAdditionalAbilities } from "./romWarlockWarriorEnragedCoaMechanics";
|
||||
import {
|
||||
ROM_WARLOCK_MAGE_SOUL_BRAND_STING_COST,
|
||||
ROM_WARLOCK_MAGE_SOUL_BRAND_STING_DESCRIPTION,
|
||||
} from "./romWarlockMageSoulBrandSting";
|
||||
import {
|
||||
ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_COST,
|
||||
ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_DESCRIPTION,
|
||||
ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_RADIUS,
|
||||
isRomWarlockPriestSoulEnlightenment,
|
||||
} from "./romWarlockPriestSoulEnlightenment";
|
||||
import {
|
||||
ROM_DRUID_ROGUE_CORROSIVE_POISON_DESCRIPTION,
|
||||
ROM_DRUID_ROGUE_NECROTIC_WOUND_DESCRIPTION,
|
||||
ROM_DRUID_ROGUE_SINISTER_REQUEST_DESCRIPTION,
|
||||
romDruidRogueSinisterRequestEffects,
|
||||
isRomDruidRoguePair,
|
||||
} from "./romDruidRogueCurrentCoa";
|
||||
import {
|
||||
isRomDruidMagePair,
|
||||
ROM_DRUID_MAGE_INSPIRATION_DESCRIPTION,
|
||||
} from "./romDruidMageInspirationRuntime";
|
||||
import {
|
||||
isRomMageDruidPair,
|
||||
ROM_MAGE_DRUID_GREEN_GUARDIAN_DESCRIPTION,
|
||||
ROM_MAGE_DRUID_GREEN_GUARDIAN_SKILL_ID,
|
||||
} from "./romMageDruidGreenGuardianRuntime";
|
||||
import {
|
||||
ROM_ROGUE_ENERGIZED_HANDS_DESCRIPTION,
|
||||
ROM_ROGUE_ENERGIZED_HANDS_SKILL_ID,
|
||||
ROM_ROGUE_POISONOUS_HANDS_DESCRIPTION,
|
||||
ROM_ROGUE_POISONOUS_HANDS_EXCLUSIVE_GROUP,
|
||||
romRogueEnergizedHandsApplyEffect,
|
||||
} from "./romRogueEnergizedHandsRuntime";
|
||||
import {
|
||||
ROM_ROGUE_MAGE_CHAOS_DAGGER_DESCRIPTION,
|
||||
ROM_ROGUE_MAGE_CHAOS_DAGGER_SPELL_ID,
|
||||
} from "./romRogueChaosDagger";
|
||||
import {
|
||||
ROM_PRIEST_HOLY_CANDLE_COST,
|
||||
ROM_PRIEST_HOLY_CANDLE_DESCRIPTION,
|
||||
romPriestHolyCandleEffects,
|
||||
} from "./romPriestHolyCandleRuntime";
|
||||
|
||||
export type RomSkillGroup = "primary" | "general" | "elite" | "passive";
|
||||
|
||||
@@ -99,6 +151,13 @@ function costs(rows: readonly GeneratedCost[]): readonly AbilityCost[] {
|
||||
}
|
||||
|
||||
function costsFor(row: Pick<GeneratedRank | GeneratedSkill, "sourceSkillId" | "costs">): readonly AbilityCost[] {
|
||||
if (row.sourceSkillId === 490274) return [];
|
||||
if (row.sourceSkillId === 490275) return [{ resource: "mana", amount: 120 }];
|
||||
if (row.sourceSkillId === 490276) return [{
|
||||
resource: "mana", amount: 10, percentage: true, percentageBasis: "base-mana",
|
||||
}];
|
||||
if (row.sourceSkillId === 498612) return [{ resource: "rage", amount: 15 }];
|
||||
if (row.sourceSkillId === 498642) return [{ resource: "rage", amount: 75, percentage: false }];
|
||||
let resolved = costs(row.costs);
|
||||
if (row.sourceSkillId === 490136) {
|
||||
resolved = resolved.map((cost) => cost.resource === "rage" ? { ...cost, amount: 15 } : cost);
|
||||
@@ -108,26 +167,81 @@ function costsFor(row: Pick<GeneratedRank | GeneratedSkill, "sourceSkillId" | "c
|
||||
}
|
||||
|
||||
function cooldownMsFor(row: Pick<GeneratedRank | GeneratedSkill, "sourceSkillId" | "cooldownMs">): number {
|
||||
if (row.sourceSkillId === 490274) return 0;
|
||||
if (row.sourceSkillId === 490275 || row.sourceSkillId === 490276) return 18_000;
|
||||
if (row.sourceSkillId === 498612) return 12_000;
|
||||
if (row.sourceSkillId === 498642) return 1_000;
|
||||
// The local snapshot omitted Prayer of Tribulation's timing. CoA's current
|
||||
// skill list identifies this same Priest/Warlock spell (498770) as a 1.5s
|
||||
// channel with a 5s cooldown; its healing and buff payload still come from
|
||||
// the local RuneWaker tooltip/components below.
|
||||
if (row.sourceSkillId === 498770) return 5_000;
|
||||
// CoA's current Knight/Shaman Whirlwind Shield is 5s before Compassion.
|
||||
if (row.sourceSkillId === 490175) return 5_000;
|
||||
return row.sourceSkillId === 490136 ? 40_000 : row.cooldownMs;
|
||||
}
|
||||
|
||||
function castTimeMsFor(row: Pick<GeneratedRank | GeneratedSkill, "sourceSkillId" | "castTimeMs">): number {
|
||||
if ([490274, 490275, 490276, 498612].includes(row.sourceSkillId)) return 0;
|
||||
if (row.sourceSkillId === 498770) return 1_500;
|
||||
if (row.sourceSkillId === 491164) return 4_000;
|
||||
return row.castTimeMs;
|
||||
}
|
||||
|
||||
function castModeFor(row: Pick<GeneratedRank | GeneratedSkill, "sourceSkillId" | "castTimeMs">): AbilityDefinition["castMode"] {
|
||||
if ([490274, 490275, 490276, 498612].includes(row.sourceSkillId)) return "instant";
|
||||
return row.sourceSkillId === 491164 || row.sourceSkillId === 498770
|
||||
? "channel"
|
||||
: row.castTimeMs > 0 ? "cast" : "instant";
|
||||
}
|
||||
|
||||
function currentWarlockCoaDescription(sourceSkillId: number, fallback: string): string {
|
||||
// Current CoA Warrior/Warlock skill list: https://chroniclesofarcadia.com/en/wiki/skills/list/1-9
|
||||
// These two local passive rows describe obsolete cooldown-reset children;
|
||||
// the current page instead gives Speed Slash's Rage discount and Excellent
|
||||
// Combat Training's physical-damage bonus.
|
||||
const descriptions: Readonly<Record<number, string>> = {
|
||||
499827: "Passive: Increases Slash damage by an unspecified amount, which is not modeled. Reduces Slash's Rage cost by 10.",
|
||||
499806: "Passive: Grants an additional 20% Physical Damage.",
|
||||
498642: "Converts 75 Rage into 6 Psi. 1-second cooldown.",
|
||||
498634: "Enraged restores additional Focus. Casting Psychic Arrows, Warp Charge, or Weakening Weave Curse can reset Enraged's cooldown. Current CoA does not publish the proc chance or Focus amount, so these riders are not modeled.",
|
||||
498633: "When Psychic Arrows hits, gain 12% healing power and 12% Wisdom for 20 seconds. Warp Charge heals a friendly target instead of dealing damage. The simulator does not model ally-target redirection to that ally's hostile target.",
|
||||
498643: "Rising Tide becomes instant with a 5-second cooldown and restores 1 Psi. A critical hit activates Healing Fortune. The simulator cannot redirect an ally-targeted cast to that ally's hostile target.",
|
||||
498648: "Urgent Heal restores 1 Psi. Defense Net's effect time is reduced by 1 second. Ring of Comfort's reset chance is not specified and is not modeled.",
|
||||
498668: "Passively increases magical damage and healing by 16%.",
|
||||
499856: "Inflicts [497959], [497960], and [497964] on the target. Increases Magical Attack Power by 18% and Magical Critical Bonus by 25% for 30 seconds, and activates Healing Fortune, increasing healing by 12% for 20 seconds. Current CoA lists a 10% MP cost plus 15 Focus and range 150; RuneWaker supplies 15 Focus and range 22.5. The simulator retains those local cost and range values because RoM Warlock has no modeled Mana pool and no verified range-unit conversion.",
|
||||
499868: "Passive: magical damage skills call an Ethereal Hammer for 187.5% main-hand weapon DPS against area targets or an Ethereal Sword for 1,050% main-hand weapon DPS against a single target. The source calls this Physical Dark damage, which the combat model cannot represent as a composite damage type, so this rider is not yet applied. The additional main-hand damage for Slash and Consistent Heart Strike is unspecified and is not modeled.",
|
||||
499870: "Increases Breathing Control's Focus recovery and grants an extra 15% HP. Current CoA does not publish the Focus amount or proc chance, so these riders are not modeled.",
|
||||
};
|
||||
return descriptions[sourceSkillId] ?? fallback;
|
||||
}
|
||||
|
||||
const ROM_WARLOCK_ROGUE_SHADOW_SLICE_DESCRIPTION = "Reworks Shadow Step (490344): the cast deals 4,400 Dark damage, stuns for 1 second, grants 3 Psi, and increases Dark damage by 30% and movement speed by 10% for 30 seconds. This replaces Shadow Step's teleport; the simulator does not model the behind-target positioning.";
|
||||
const ROM_WARLOCK_ROGUE_SHADOW_STEP_DESCRIPTION = "Instantly moves behind the target. In the Warlock/Rogue build with Shadow Slice (498678), it instead deals 4,400 Dark damage, stuns for 1 second, grants 3 Psi, and increases Dark damage by 30% and movement speed by 10% for 30 seconds. The simulator cannot model the behind-target positioning.";
|
||||
|
||||
function currentPriestCoaDescription(sourceSkillId: number, fallback: string): string {
|
||||
if (sourceSkillId === 490304) {
|
||||
return ROM_PRIEST_HOLY_CANDLE_DESCRIPTION;
|
||||
}
|
||||
if (sourceSkillId === 498620) {
|
||||
return "Creates a 30-second Beacon on one friendly party member. Each direct healing cast restores 770 HP to that member. CoA publishes 770 at skill level +105 (the maximum); the simulator applies this value at every unlocked rank because lower skill ranks are not modeled separately. HoT ticks and channel pulses are outside the simulator's direct-cast trigger boundary.";
|
||||
}
|
||||
if (sourceSkillId === 492933) {
|
||||
return "Passive: Increases Wisdom by 12%.";
|
||||
}
|
||||
if (sourceSkillId === 498612) {
|
||||
return "Inflicts 975.1% CPS Light Damage to nearby targets around you and reduces Blessing of Humility's cooldown by 15 seconds. Cost: 15 Rage. Radius: 80. Cooldown: 12 seconds.";
|
||||
}
|
||||
return romPriestDescription(sourceSkillId, fallback);
|
||||
}
|
||||
|
||||
function currentMageCoaDescription(sourceSkillId: number, fallback: string): string {
|
||||
if (sourceSkillId === 490248) {
|
||||
return "Increases Fire resistance by 10 points and reduces received magical damage by 11.5% within radius 100. CoA lists +230 Fire resistance at skill level 105, but the simulator retains RuneWaker's local +10 because it has no source-backed scaling curve or skill-level state. Costs 120 MP; range 150; instant. CoA omits a duration, so this catalog retains RuneWaker's 10-minute duration.";
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
type GeneratedSemanticRow = Pick<GeneratedSkill, "sourceSkillId" | "name" | "description" | "target" | "passive" | "source">
|
||||
& Partial<Pick<GeneratedSkill, "classId" | "secondaryClassId">>;
|
||||
|
||||
@@ -169,6 +283,8 @@ function auraRecipient(row: GeneratedSemanticRow, semantic: Extract<RomAbilitySe
|
||||
}
|
||||
|
||||
function abilityTarget(row: Pick<GeneratedSemanticRow, "sourceSkillId" | "target">): GeneratedSemanticRow["target"] {
|
||||
if (row.sourceSkillId === 490274 || row.sourceSkillId === 498612) return "self";
|
||||
if (row.sourceSkillId === 490275 || row.sourceSkillId === 490276) return "hostile";
|
||||
if ([490290, 490491, 492636, 493531, 494021, 494027, 498641].includes(row.sourceSkillId)) return "friendly";
|
||||
// RuneWaker's tooltip calls this a four-pulse heal on a friendly target;
|
||||
// sourceTarget 7 is stale/conflicting and was incorrectly making it hostile.
|
||||
@@ -781,6 +897,85 @@ function specializedEffects(row: GeneratedSemanticRow): readonly AbilityEffect[]
|
||||
}
|
||||
|
||||
function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
if (isRomDruidRoguePair(row) && row.sourceSkillId === 494023) {
|
||||
return romDruidRogueSinisterRequestEffects();
|
||||
}
|
||||
if (row.classId === "rom-priest") {
|
||||
const holyBlessing = romPriestHolyBlessingEffects(row);
|
||||
if (holyBlessing) return holyBlessing;
|
||||
if (row.sourceSkillId === 498620) {
|
||||
return [{
|
||||
kind: "apply-aura",
|
||||
aura: ROM_PRIEST_CHAMPION_BEACON_OF_REGENERATION_AURA,
|
||||
recipient: "ability-target",
|
||||
sourceEffectType: 1,
|
||||
sourceAuraType: -1,
|
||||
miscValue: 0,
|
||||
triggerSpellId: 498620,
|
||||
}];
|
||||
}
|
||||
if (row.sourceSkillId === 490275) {
|
||||
const silence: AbilityEffect = {
|
||||
kind: "apply-aura",
|
||||
aura: {
|
||||
id: "rom-priest-vow-of-silence",
|
||||
name: "Vow of Silence",
|
||||
disposition: "debuff",
|
||||
durationMs: 8_400,
|
||||
dispelCategory: "magic",
|
||||
},
|
||||
recipient: "ability-target",
|
||||
control: { kind: "silence" },
|
||||
sourceEffectType: -1,
|
||||
sourceAuraType: -1,
|
||||
miscValue: 0,
|
||||
triggerSpellId: 490275,
|
||||
};
|
||||
return [silence, { kind: "interrupt", lockoutMs: 8_400 }];
|
||||
}
|
||||
if (row.sourceSkillId === 490276) {
|
||||
return [
|
||||
{
|
||||
kind: "damage",
|
||||
coefficient: 0,
|
||||
basePoints: 1_655,
|
||||
pointsPerLevel: 0,
|
||||
sourceLevel: 1,
|
||||
school: "holy",
|
||||
},
|
||||
{
|
||||
kind: "apply-aura",
|
||||
aura: {
|
||||
id: "rom-priest-holy-void",
|
||||
name: "Voidal Infection",
|
||||
disposition: "debuff",
|
||||
durationMs: 20_000,
|
||||
dispelCategory: "magic",
|
||||
modifiers: [
|
||||
{ kind: "stat", stat: "physical-defense", operation: "percent", value: -0.188 },
|
||||
{ kind: "stat", stat: "magical-defense", operation: "percent", value: -0.188 },
|
||||
],
|
||||
},
|
||||
recipient: "ability-target",
|
||||
sourceEffectType: -1,
|
||||
sourceAuraType: -1,
|
||||
miscValue: 0,
|
||||
triggerSpellId: 490276,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (row.sourceSkillId === 498612) {
|
||||
return [
|
||||
{ kind: "damage", coefficient: 9.751, school: "holy" },
|
||||
{
|
||||
kind: "cooldown-reset",
|
||||
mode: "reduce",
|
||||
abilityName: "Blessing of Humility",
|
||||
amountMs: 15_000,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
const specialized = specializedEffects(row);
|
||||
if (specialized) return specialized;
|
||||
const semantics = translateRomAbilitySemantics(row);
|
||||
@@ -792,6 +987,8 @@ function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
utilityTags: [...(effect.aura.utilityTags ?? []), "rom-briar-shield"],
|
||||
},
|
||||
} : effect);
|
||||
const holyCandle = romPriestHolyCandleEffects(row, translated);
|
||||
if (holyCandle) return holyCandle;
|
||||
const priestMageOverride = overrideRomPriestMageEffects(row, translated);
|
||||
if (priestMageOverride) return priestMageOverride;
|
||||
const championOverride = overrideRomChampionEffects(row, translated);
|
||||
@@ -952,7 +1149,7 @@ function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
if (row.sourceSkillId === 490269) {
|
||||
return translated.filter((effect) => effect.kind === "hot").slice(0, 1);
|
||||
}
|
||||
if (row.sourceSkillId === 490304 || row.sourceSkillId === 490434) {
|
||||
if (row.sourceSkillId === 490434) {
|
||||
return translated.map((effect) => effect.kind === "apply-aura" ? {
|
||||
...effect,
|
||||
aura: { ...effect.aura, utilityTags: [...(effect.aura.utilityTags ?? []), "rom-self-health-drain"] },
|
||||
@@ -1010,8 +1207,8 @@ function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
} : effect
|
||||
));
|
||||
}
|
||||
if (row.sourceSkillId === 490347) {
|
||||
return [{ kind: "status", status: "sleep", durationMs: 10_000, maxTargets: 1, breakOnDamage: true }];
|
||||
if (row.sourceSkillId === ROM_ROGUE_ENERGIZED_HANDS_SKILL_ID && row.classId === "rom-rogue") {
|
||||
return [romRogueEnergizedHandsApplyEffect()];
|
||||
}
|
||||
if (row.sourceSkillId === 490454) {
|
||||
return [{ kind: "status", status: "root", durationMs: 10_000, maxTargets: 1, breakOnDamage: true }];
|
||||
@@ -1042,11 +1239,16 @@ function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
aura: { ...effect.aura, utilityTags: ["stealth", "rom-vanish"] },
|
||||
} : effect);
|
||||
}
|
||||
if (row.sourceSkillId === 490329) {
|
||||
if (row.sourceSkillId === 490329 && row.classId === "rom-rogue") {
|
||||
const aura = translated.find((effect) => effect.kind === "apply-aura");
|
||||
return aura?.kind === "apply-aura" ? [{
|
||||
...aura,
|
||||
aura: { ...aura.aura, durationMs: 900_000, utilityTags: ["rom-poisonous"] },
|
||||
aura: {
|
||||
...aura.aura,
|
||||
durationMs: 900_000,
|
||||
exclusiveGroup: ROM_ROGUE_POISONOUS_HANDS_EXCLUSIVE_GROUP,
|
||||
utilityTags: ["rom-poisonous"],
|
||||
},
|
||||
}] : translated;
|
||||
}
|
||||
// RuneWaker's Knight uses Holy Seal as a target-scoped build/spend resource.
|
||||
@@ -1076,14 +1278,28 @@ function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
return translated;
|
||||
}
|
||||
|
||||
function areaRadius(row: Pick<GeneratedSkill, "description" | "range">): number | undefined {
|
||||
function areaRadius(row: Pick<GeneratedSkill, "sourceSkillId" | "description" | "range">): number | undefined {
|
||||
// Rune Overload's local tooltip says only "immediate vicinity". Its
|
||||
// hash-matched source has no numeric radius, so do not apply the generic
|
||||
// heuristic's 10-yard fallback to this self-targeted ability.
|
||||
if (row.sourceSkillId === 498572) return undefined;
|
||||
if (row.description.includes("up to 3 targets") && row.description.includes("Inflicts")) return 6;
|
||||
if (!/\b(?:multiple targets|multiple enemies|all targets|all enemies|nearby targets|nearby party members|surrounding (?:enemies|targets)|party members within|all teammates within|friendly targets within|(?:enemies|targets) (?:within (?:a )?range|in a range|in range))\b/i.test(row.description)) return undefined;
|
||||
const sourceRange = row.description.match(/\b(?:range|radius)\s+(?:of\s+)?(\d+(?:\.\d+)?)/i)?.[1];
|
||||
return Math.max(3, Math.min(40, sourceRange ? Number(sourceRange) / 10 : row.range > 1 ? row.range : 10));
|
||||
}
|
||||
|
||||
function sourceTargeting(sourceSkillId: number): AbilityDefinition["targeting"] | undefined {
|
||||
function sourceTargeting(sourceSkillId: number, classId?: string, secondaryClassId?: string): AbilityDefinition["targeting"] | undefined {
|
||||
if (isRomDruidRoguePair({ classId, secondaryClassId }) && sourceSkillId === 494321) return { shape: "circle", radius: 8 };
|
||||
if (isRomDruidRoguePair({ classId, secondaryClassId }) && sourceSkillId === 494023) return { shape: "circle", radius: 15 };
|
||||
if (sourceSkillId === 498612) return { shape: "circle", radius: 80 };
|
||||
// Current CoA Fire Ward (490248) is a radius-100 raid aura. RoM's local
|
||||
// runtime uses one tenth of the published distance units, matching range 150
|
||||
// to 15 for this same catalog row.
|
||||
if (sourceSkillId === 490248) return { shape: "circle", radius: 10 };
|
||||
// CoA's current Whirlwind Shield (1493719) is a 100-radius multi-target cast;
|
||||
// map the exact-name local RuneWaker row to that current shape.
|
||||
if (sourceSkillId === 490175 || sourceSkillId === 1_493_719) return { shape: "circle", radius: 100 };
|
||||
if (sourceSkillId === 490311 || sourceSkillId === 490337) return { shape: "unit", requiresBehindTarget: true };
|
||||
if (sourceSkillId === 490239) return { shape: "line", radius: 12 };
|
||||
if (sourceSkillId === 498201 || sourceSkillId === 498202) return { shape: "circle", radius: 7.5 };
|
||||
@@ -1109,28 +1325,116 @@ function sourceTargeting(sourceSkillId: number): AbilityDefinition["targeting"]
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function sourceRange(sourceSkillId: number, fallback: number): number {
|
||||
function sourceRange(sourceSkillId: number, fallback: number, classId?: string, secondaryClassId?: string): number {
|
||||
if (sourceSkillId === 490274) return 1;
|
||||
if (classId === "rom-priest" && secondaryClassId === undefined && sourceSkillId === 490304) return 1;
|
||||
if (sourceSkillId === 490275 || sourceSkillId === 490276 || sourceSkillId === 498612) return 150;
|
||||
if (isRomDruidRoguePair({ classId, secondaryClassId }) && [494024, 494321].includes(sourceSkillId)) return 15;
|
||||
if (isRomDruidRoguePair({ classId, secondaryClassId }) && sourceSkillId === 494023) return 5;
|
||||
// RoM tooltip ranges are expressed in decimeters; Phoenix's source row is
|
||||
// self-targeted, so use its described 120-unit path length for line casting.
|
||||
return sourceSkillId === 490239 ? 12 : fallback;
|
||||
}
|
||||
|
||||
function rank(row: GeneratedRank, parent: GeneratedSkill): AbilityRankDefinition {
|
||||
const rankCosts = costsFor(row);
|
||||
const currentDruidRogue = isRomDruidRoguePair(parent);
|
||||
const currentDruidMageInspiration = isRomDruidMagePair(parent) && row.sourceSkillId === 494322;
|
||||
const currentMageDruidGreenGuardian = isRomMageDruidPair(parent)
|
||||
&& row.sourceSkillId === ROM_MAGE_DRUID_GREEN_GUARDIAN_SKILL_ID;
|
||||
const currentCoaHolyCandle = parent.classId === "rom-priest"
|
||||
&& parent.secondaryClassId === undefined
|
||||
&& row.sourceSkillId === 490304;
|
||||
const currentCoaRogueEnergizedHands = parent.classId === "rom-rogue"
|
||||
&& row.sourceSkillId === ROM_ROGUE_ENERGIZED_HANDS_SKILL_ID;
|
||||
const currentCoaRoguePoisonousHands = parent.classId === "rom-rogue" && row.sourceSkillId === 490329;
|
||||
const currentCoaRogueMageChaosDagger = parent.classId === "rom-rogue"
|
||||
&& parent.secondaryClassId === "rom-mage"
|
||||
&& row.sourceSkillId === ROM_ROGUE_MAGE_CHAOS_DAGGER_SPELL_ID;
|
||||
const currentCoaSoulBrandSting = row.sourceSkillId === 498710
|
||||
&& parent.classId === "rom-warlock" && parent.secondaryClassId === "rom-mage";
|
||||
const currentCoaSoulEnlightenment = isRomWarlockPriestSoulEnlightenment({
|
||||
classId: parent.classId,
|
||||
secondaryClassId: parent.secondaryClassId,
|
||||
sourceSkillId: row.sourceSkillId,
|
||||
});
|
||||
const currentCoaHolyPowerExplosion = row.sourceSkillId === 490172
|
||||
&& parent.classId === "rom-knight" && parent.secondaryClassId === undefined;
|
||||
const currentCoaLeopardInstinct = row.sourceSkillId === 495402
|
||||
&& parent.classId === "rom-knight" && parent.secondaryClassId === "rom-rogue";
|
||||
const currentCoaPassive = currentCoaHolyPowerExplosion || currentCoaLeopardInstinct
|
||||
|| currentDruidMageInspiration;
|
||||
const passiveMode = currentCoaPassive || currentCoaRogueMageChaosDagger;
|
||||
const rankCosts = currentMageDruidGreenGuardian ? []
|
||||
: currentCoaHolyCandle ? [ROM_PRIEST_HOLY_CANDLE_COST]
|
||||
: currentCoaRogueEnergizedHands || currentCoaRogueMageChaosDagger ? []
|
||||
: currentDruidRogue && row.sourceSkillId === 494321
|
||||
? [{ resource: "mana" as const, amount: 4, percentage: true }, { resource: "nature-power" as const, amount: 1 }]
|
||||
: currentDruidRogue && row.sourceSkillId === 494024
|
||||
? [{ resource: "energy" as const, amount: 25 }]
|
||||
: currentDruidRogue && row.sourceSkillId === 494023
|
||||
? []
|
||||
: currentCoaSoulBrandSting
|
||||
? [ROM_WARLOCK_MAGE_SOUL_BRAND_STING_COST]
|
||||
: currentCoaSoulEnlightenment ? [ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_COST]
|
||||
: currentCoaPassive ? [] : costsFor(row);
|
||||
const target = abilityTarget({ ...row, target: row.target ?? parent.target });
|
||||
const priestRankName = parent.classId === "rom-priest" ? romPriestName(row.sourceSkillId, row.name) : row.name;
|
||||
const currentClassDescription = currentMageDruidGreenGuardian
|
||||
? ROM_MAGE_DRUID_GREEN_GUARDIAN_DESCRIPTION
|
||||
: currentDruidMageInspiration
|
||||
? ROM_DRUID_MAGE_INSPIRATION_DESCRIPTION
|
||||
: currentCoaRogueEnergizedHands
|
||||
? ROM_ROGUE_ENERGIZED_HANDS_DESCRIPTION
|
||||
: currentCoaRoguePoisonousHands
|
||||
? ROM_ROGUE_POISONOUS_HANDS_DESCRIPTION
|
||||
: currentCoaRogueMageChaosDagger
|
||||
? ROM_ROGUE_MAGE_CHAOS_DAGGER_DESCRIPTION
|
||||
: parent.classId === "rom-priest"
|
||||
? currentPriestCoaDescription(row.sourceSkillId, row.description)
|
||||
: parent.classId === "rom-mage"
|
||||
? currentMageCoaDescription(row.sourceSkillId, row.description)
|
||||
: currentDruidRogue && row.sourceSkillId === 494321
|
||||
? ROM_DRUID_ROGUE_NECROTIC_WOUND_DESCRIPTION
|
||||
: currentDruidRogue && row.sourceSkillId === 494024
|
||||
? ROM_DRUID_ROGUE_CORROSIVE_POISON_DESCRIPTION
|
||||
: currentDruidRogue && row.sourceSkillId === 494023
|
||||
? ROM_DRUID_ROGUE_SINISTER_REQUEST_DESCRIPTION
|
||||
: row.description;
|
||||
const currentWarlockRogueShadowSlice = row.sourceSkillId === 498678
|
||||
&& parent.classId === "rom-warlock" && parent.secondaryClassId === "rom-rogue";
|
||||
const rankDescription = currentCoaSoulBrandSting
|
||||
? ROM_WARLOCK_MAGE_SOUL_BRAND_STING_DESCRIPTION
|
||||
: currentCoaSoulEnlightenment
|
||||
? ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_DESCRIPTION
|
||||
: currentWarlockRogueShadowSlice
|
||||
? ROM_WARLOCK_ROGUE_SHADOW_SLICE_DESCRIPTION
|
||||
: row.sourceSkillId === 490344 && parent.classId === "rom-rogue"
|
||||
? ROM_WARLOCK_ROGUE_SHADOW_STEP_DESCRIPTION
|
||||
: currentWarlockCoaDescription(row.sourceSkillId, currentClassDescription);
|
||||
return {
|
||||
rank: row.rank,
|
||||
spellId: row.sourceSkillId,
|
||||
level: row.level,
|
||||
description: parent.classId === "rom-priest" ? romPriestDescription(row.sourceSkillId, row.description) : row.description,
|
||||
castMode: castModeFor(row),
|
||||
castTimeMs: castTimeMsFor(row),
|
||||
cooldownMs: cooldownMsFor(row),
|
||||
level: currentMageDruidGreenGuardian ? 25 : row.level,
|
||||
// AbilityRankDefinition has no name field, so retain the canonical CoA
|
||||
// name in the rank copy when a local row was renamed.
|
||||
description: parent.classId === "rom-priest" && priestRankName !== row.name
|
||||
&& !rankDescription.toLowerCase().startsWith(priestRankName.toLowerCase())
|
||||
? `${priestRankName}: ${rankDescription}`
|
||||
: rankDescription,
|
||||
castMode: passiveMode || currentMageDruidGreenGuardian ? "instant" : currentDruidRogue && row.sourceSkillId === 494321 ? "channel" : castModeFor(row),
|
||||
castTimeMs: passiveMode || currentMageDruidGreenGuardian ? 0 : currentCoaRogueEnergizedHands || currentCoaRoguePoisonousHands ? 1_000
|
||||
: currentDruidRogue && row.sourceSkillId === 494321 ? 1_500 : castTimeMsFor(row),
|
||||
cooldownMs: passiveMode || currentMageDruidGreenGuardian || currentCoaRogueEnergizedHands || currentCoaRoguePoisonousHands ? 0
|
||||
: currentDruidRogue && row.sourceSkillId === 494321 ? 0
|
||||
: currentDruidRogue && row.sourceSkillId === 494024 ? 1_000
|
||||
: currentCoaHolyCandle ? 30_000
|
||||
: currentDruidRogue && row.sourceSkillId === 494023 ? 15_000 : cooldownMsFor(row),
|
||||
gcdMs: 1_500,
|
||||
...(row.sourceSkillId === 498770 ? { channelPulses: 4 } : {}),
|
||||
...(currentDruidRogue && row.sourceSkillId === 494321 ? { channelPulses: 3, channelTickMs: 500 } : {}),
|
||||
cost: rankCosts[0] ?? { resource: "mana", amount: 0 },
|
||||
costs: rankCosts,
|
||||
effects: effects({
|
||||
effects: currentCoaPassive ? [] : effects({
|
||||
...row,
|
||||
classId: parent.classId,
|
||||
secondaryClassId: parent.secondaryClassId,
|
||||
@@ -1146,44 +1450,179 @@ function ability(row: GeneratedSkill): AbilityDefinition {
|
||||
const secondaryClassId = row.secondaryClassId && ROM_CLASS_IDS.includes(row.secondaryClassId as RomClassId)
|
||||
? row.secondaryClassId as RomClassId
|
||||
: undefined;
|
||||
const abilityCosts = costsFor(row);
|
||||
const currentDruidRogue = isRomDruidRoguePair({ classId, secondaryClassId });
|
||||
const currentMageDruidGreenGuardian = isRomMageDruidPair({ classId, secondaryClassId })
|
||||
&& row.sourceSkillId === ROM_MAGE_DRUID_GREEN_GUARDIAN_SKILL_ID;
|
||||
const currentDruidMageInspiration = isRomDruidMagePair({ classId, secondaryClassId })
|
||||
&& row.sourceSkillId === 494322;
|
||||
const currentCoaHolyCandle = classId === "rom-priest"
|
||||
&& secondaryClassId === undefined
|
||||
&& row.sourceSkillId === 490304;
|
||||
const currentCoaRogueEnergizedHands = classId === "rom-rogue"
|
||||
&& row.sourceSkillId === ROM_ROGUE_ENERGIZED_HANDS_SKILL_ID;
|
||||
const currentCoaRoguePoisonousHands = classId === "rom-rogue" && row.sourceSkillId === 490329;
|
||||
const currentCoaRogueMageChaosDagger = classId === "rom-rogue"
|
||||
&& secondaryClassId === "rom-mage"
|
||||
&& row.sourceSkillId === ROM_ROGUE_MAGE_CHAOS_DAGGER_SPELL_ID;
|
||||
const currentCoaCompassion = row.sourceSkillId === 491_635;
|
||||
const currentCoaHolyPowerExplosion = row.sourceSkillId === 490172 && classId === "rom-knight";
|
||||
const currentCoaLeopardInstinct = row.sourceSkillId === 495402
|
||||
&& classId === "rom-knight" && secondaryClassId === "rom-rogue";
|
||||
const currentCoaPsychicWarrior = row.sourceSkillId === 499868
|
||||
&& classId === "rom-warlock" && secondaryClassId === "rom-warrior";
|
||||
const currentCoaShadowSlice = row.sourceSkillId === 498678
|
||||
&& classId === "rom-warlock" && secondaryClassId === "rom-rogue";
|
||||
const currentCoaShadowStep = row.sourceSkillId === 490344 && classId === "rom-rogue";
|
||||
const currentCoaSoulBrandSting = row.sourceSkillId === 498710
|
||||
&& classId === "rom-warlock" && secondaryClassId === "rom-mage";
|
||||
const currentCoaSoulEnlightenment = isRomWarlockPriestSoulEnlightenment({
|
||||
classId,
|
||||
secondaryClassId,
|
||||
sourceSkillId: row.sourceSkillId,
|
||||
});
|
||||
const currentCoaPassive = currentCoaHolyPowerExplosion || currentCoaLeopardInstinct
|
||||
|| currentCoaPsychicWarrior || currentCoaShadowSlice || currentDruidMageInspiration;
|
||||
const currentCoaPassiveMode = currentCoaPassive || currentCoaRogueMageChaosDagger || currentMageDruidGreenGuardian;
|
||||
const abilityCosts = currentMageDruidGreenGuardian ? []
|
||||
: currentCoaHolyCandle ? [ROM_PRIEST_HOLY_CANDLE_COST]
|
||||
: currentCoaRogueEnergizedHands ? []
|
||||
: currentDruidRogue && row.sourceSkillId === 494321
|
||||
? [{ resource: "mana" as const, amount: 4, percentage: true }, { resource: "nature-power" as const, amount: 1 }]
|
||||
: currentDruidRogue && row.sourceSkillId === 494024
|
||||
? [{ resource: "energy" as const, amount: 25 }]
|
||||
: currentDruidRogue && row.sourceSkillId === 494023
|
||||
? []
|
||||
: currentCoaSoulBrandSting
|
||||
? [ROM_WARLOCK_MAGE_SOUL_BRAND_STING_COST]
|
||||
: currentCoaSoulEnlightenment ? [ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_COST]
|
||||
: costsFor(row);
|
||||
const target = abilityTarget(row);
|
||||
const currentKnightName = currentCoaRogueEnergizedHands
|
||||
? "Energized Hands"
|
||||
: currentCoaRoguePoisonousHands
|
||||
? "Poisonous Hands"
|
||||
: currentCoaRogueMageChaosDagger
|
||||
? "Chaos Dagger"
|
||||
: row.sourceSkillId === 491_635
|
||||
? "Compassion of Holy Light"
|
||||
: currentCoaPsychicWarrior
|
||||
? "Psychic Warrior"
|
||||
: currentDruidRogue && row.sourceSkillId === 494023
|
||||
? "Sinister Request"
|
||||
: row.name;
|
||||
const currentAbilityName = currentCoaShadowSlice
|
||||
? "Shadow Slice"
|
||||
: classId === "rom-priest"
|
||||
? romPriestName(row.sourceSkillId, currentKnightName)
|
||||
: currentKnightName;
|
||||
const currentKnightDescription = currentMageDruidGreenGuardian
|
||||
? ROM_MAGE_DRUID_GREEN_GUARDIAN_DESCRIPTION
|
||||
: currentDruidMageInspiration
|
||||
? ROM_DRUID_MAGE_INSPIRATION_DESCRIPTION
|
||||
: currentCoaRogueEnergizedHands
|
||||
? ROM_ROGUE_ENERGIZED_HANDS_DESCRIPTION
|
||||
: currentCoaRoguePoisonousHands
|
||||
? ROM_ROGUE_POISONOUS_HANDS_DESCRIPTION
|
||||
: currentCoaRogueMageChaosDagger
|
||||
? ROM_ROGUE_MAGE_CHAOS_DAGGER_DESCRIPTION
|
||||
: currentCoaShadowSlice
|
||||
? ROM_WARLOCK_ROGUE_SHADOW_SLICE_DESCRIPTION
|
||||
: currentCoaShadowStep
|
||||
? ROM_WARLOCK_ROGUE_SHADOW_STEP_DESCRIPTION
|
||||
: currentDruidRogue && row.sourceSkillId === 494321
|
||||
? ROM_DRUID_ROGUE_NECROTIC_WOUND_DESCRIPTION
|
||||
: currentDruidRogue && row.sourceSkillId === 494024
|
||||
? ROM_DRUID_ROGUE_CORROSIVE_POISON_DESCRIPTION
|
||||
: currentDruidRogue && row.sourceSkillId === 494023
|
||||
? ROM_DRUID_ROGUE_SINISTER_REQUEST_DESCRIPTION
|
||||
: row.sourceSkillId === 491_639
|
||||
? "Increases aggro generated by Whirlwind and reduces its Rage cost and cooldown. On successful hits, Whirlwind inflicts Deadly Wound, reducing the target's received healing by 30% for 10 seconds (5 seconds against players)."
|
||||
: row.sourceSkillId === 491_608
|
||||
? "Increases Punishment damage by 28.5%. When Punishment hits, the target takes 2% more damage for every Holy Seal stack it has for 10 seconds."
|
||||
: row.sourceSkillId === 491_635
|
||||
? "Current CoA lists this reused passive under Knight/Shaman, while the local generated row is Knight/Priest and this simulator has no Shaman class entry. The exact-ID local passive is bridged to Whirlwind Shield (CoA skill 1493719): a successful hit heals nearby allies for 120 + 0.2 x STA and triggers Holy Illumination; cooldown becomes 4 seconds instead of 5. CoA also scales its shield-defense damage term to 5x, but this simulator has no shield-defense stat, so that damage term is not modeled."
|
||||
: currentCoaHolyPowerExplosion
|
||||
? "Passive: increases Aggro generated by Knight abilities by 198%."
|
||||
: currentCoaLeopardInstinct
|
||||
? "Passive: each hit received adds one stack, up to 10. Each stack grants 3% Dodge and 3% Magic Resistance Rate; each Dodge removes one stack. This simulator applies Dodge only because it has no Magic Resistance Rate consumer."
|
||||
: currentWarlockCoaDescription(row.sourceSkillId, row.description);
|
||||
const currentWarriorCoaDescription = row.sourceSkillId === 493248
|
||||
&& classId === "rom-warrior" && secondaryClassId === "rom-rogue"
|
||||
? "Passive: gives the Warrior/Rogue CoA Probing Attack (493283) a 30% chance on a landed hit to restore 25 Energy and 50 Rage."
|
||||
: currentKnightDescription;
|
||||
const currentDescription = currentCoaSoulBrandSting
|
||||
? ROM_WARLOCK_MAGE_SOUL_BRAND_STING_DESCRIPTION
|
||||
: currentCoaSoulEnlightenment
|
||||
? ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_DESCRIPTION
|
||||
: classId === "rom-priest"
|
||||
? currentWarlockCoaDescription(row.sourceSkillId, currentPriestCoaDescription(row.sourceSkillId, currentKnightDescription))
|
||||
: classId === "rom-mage"
|
||||
? currentMageCoaDescription(row.sourceSkillId, currentWarriorCoaDescription)
|
||||
: currentWarriorCoaDescription;
|
||||
const currentCoaDescriptionOverride = currentMageDruidGreenGuardian || currentCoaPassive || currentCoaShadowStep || currentCoaSoulBrandSting || currentCoaSoulEnlightenment
|
||||
|| currentCoaRogueEnergizedHands || currentCoaRoguePoisonousHands || currentCoaRogueMageChaosDagger
|
||||
|| (currentDruidRogue && [494321, 494024, 494023].includes(row.sourceSkillId)) || (row.sourceSkillId === 493248
|
||||
&& classId === "rom-warrior" && secondaryClassId === "rom-rogue")
|
||||
|| (row.sourceSkillId === 490248 && classId === "rom-mage");
|
||||
const currentRanks = row.ranks.map((entry) => rank(entry, row)).map((entry) => ({
|
||||
...entry,
|
||||
...(currentCoaDescriptionOverride ? { description: currentDescription } : {}),
|
||||
...(currentDruidRogue && row.sourceSkillId === 494023
|
||||
? { effects: romDruidRogueSinisterRequestEffects() }
|
||||
: {}),
|
||||
}));
|
||||
return {
|
||||
id: row.id,
|
||||
classId,
|
||||
...(secondaryClassId ? { secondaryClassId } : {}),
|
||||
dbcSpellId: row.sourceSkillId,
|
||||
name: row.name,
|
||||
unlockLevel: row.unlockLevel,
|
||||
name: currentAbilityName,
|
||||
unlockLevel: currentMageDruidGreenGuardian ? 25 : row.unlockLevel,
|
||||
icon: row.icon,
|
||||
sigil: row.name.split(/\s+/).map((part) => part[0]).join("").slice(0, 3).toUpperCase() || "RW",
|
||||
description: classId === "rom-priest" ? romPriestDescription(row.sourceSkillId, row.description) : row.description,
|
||||
target,
|
||||
range: { min: 0, max: sourceRange(row.sourceSkillId, row.range) },
|
||||
...(sourceTargeting(row.sourceSkillId)?.radius !== undefined
|
||||
? { radius: sourceTargeting(row.sourceSkillId)!.radius }
|
||||
sigil: currentAbilityName.split(/\s+/).map((part) => part[0]).join("").slice(0, 3).toUpperCase() || "RW",
|
||||
description: currentDescription,
|
||||
target: currentCoaPassiveMode || currentCoaRogueEnergizedHands || currentCoaRoguePoisonousHands ? "self" : target,
|
||||
range: { min: 0, max: currentCoaPassiveMode || currentCoaRogueEnergizedHands || currentCoaRoguePoisonousHands
|
||||
? 0 : sourceRange(row.sourceSkillId, row.range, classId, secondaryClassId) },
|
||||
...(currentCoaSoulEnlightenment
|
||||
? { radius: ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_RADIUS }
|
||||
: sourceTargeting(row.sourceSkillId, classId, secondaryClassId)?.radius !== undefined
|
||||
? { radius: sourceTargeting(row.sourceSkillId, classId, secondaryClassId)!.radius }
|
||||
: areaRadius(row) !== undefined ? { radius: areaRadius(row) } : {}),
|
||||
...(sourceTargeting(row.sourceSkillId) ? { targeting: sourceTargeting(row.sourceSkillId) } : {}),
|
||||
castMode: castModeFor(row),
|
||||
castTimeMs: castTimeMsFor(row),
|
||||
cooldownMs: cooldownMsFor(row),
|
||||
...(currentCoaSoulEnlightenment
|
||||
? { targeting: { shape: "circle" as const, radius: ROM_WARLOCK_PRIEST_SOUL_ENLIGHTENMENT_RADIUS } }
|
||||
: sourceTargeting(row.sourceSkillId, classId, secondaryClassId)
|
||||
? { targeting: sourceTargeting(row.sourceSkillId, classId, secondaryClassId) } : {}),
|
||||
castMode: currentCoaPassiveMode ? "instant" : currentDruidRogue && row.sourceSkillId === 494321 ? "channel" : castModeFor(row),
|
||||
castTimeMs: currentCoaPassiveMode ? 0 : currentDruidRogue && row.sourceSkillId === 494321 ? 1_500 : castTimeMsFor(row),
|
||||
cooldownMs: currentCoaPassiveMode ? 0 : currentDruidRogue && row.sourceSkillId === 494321 ? 0
|
||||
: currentDruidRogue && row.sourceSkillId === 494024 ? 1_000
|
||||
: currentCoaHolyCandle ? 30_000
|
||||
: currentDruidRogue && row.sourceSkillId === 494023 ? 15_000 : cooldownMsFor(row),
|
||||
gcdMs: 1_500,
|
||||
...(row.sourceSkillId === 498770 ? { channelPulses: 4 } : {}),
|
||||
cost: abilityCosts[0] ?? { resource: "mana", amount: 0 },
|
||||
costs: abilityCosts,
|
||||
effects: effects({ ...row, target, classId }),
|
||||
passive: row.passive,
|
||||
...(currentDruidRogue && row.sourceSkillId === 494321 ? { channelPulses: 3, channelTickMs: 500 } : {}),
|
||||
cost: currentCoaPassiveMode ? { resource: "mana", amount: 0 } : abilityCosts[0] ?? { resource: "mana", amount: 0 },
|
||||
costs: currentCoaPassiveMode ? [] : abilityCosts,
|
||||
effects: currentCoaPassive ? [] : effects({ ...row, target, classId, secondaryClassId }),
|
||||
passive: currentMageDruidGreenGuardian ? false : currentCoaPassiveMode || row.sourceSkillId === 490274 ? true : row.passive,
|
||||
source: "runewaker",
|
||||
sourceUnlockLevel: row.unlockLevel,
|
||||
ranks: row.ranks.map((entry) => rank(entry, row)),
|
||||
romSkillGroup: row.group,
|
||||
ranks: currentRanks,
|
||||
romSkillGroup: currentMageDruidGreenGuardian ? "elite" : row.group,
|
||||
};
|
||||
}
|
||||
|
||||
const generatedAbilities = generated.map(ability);
|
||||
const generatedAbilities = generated.map(ability)
|
||||
.map(romWarriorDruidRageOfCalmnessCatalogOverride)
|
||||
.map(romScoutPriestScreenAttackCatalogOverride)
|
||||
.map(romScoutPriestDrumsOfWarCatalogOverride)
|
||||
.map(romPriestScoutCuringShotCatalogOverride);
|
||||
export const ROM_ALL_ABILITIES = Object.freeze([
|
||||
...generatedAbilities,
|
||||
...romPriestKnightAdditionalAbilities(generatedAbilities),
|
||||
...romWarriorCoaAdditionalAbilities(generatedAbilities),
|
||||
...romWarlockWarriorEnragedCoaAdditionalAbilities(generatedAbilities),
|
||||
]);
|
||||
|
||||
const romAbilityByGeneratedId = new Map(ROM_ALL_ABILITIES.map((entry) => [entry.id, entry]));
|
||||
@@ -1195,20 +1634,35 @@ for (const parent of generated) {
|
||||
}
|
||||
}
|
||||
|
||||
// Rune Overload's generated player row contains the hidden MagicCollect child
|
||||
// ID on component 621241's one-second on-time trigger. The child itself is not
|
||||
// a learned/player row, but its tooltip-referenced damage MagicObject (621243)
|
||||
// is retained in the hash-matched parent source payload. Alias the child spell
|
||||
// ID to that existing payload so the generic triggered-spell resolver can
|
||||
// expand it without inventing a radius or target cap.
|
||||
const runeOverloadParent = generated.find((entry) => entry.sourceSkillId === 498572);
|
||||
const runeOverloadChildPayload = runeOverloadParent?.source?.components?.find((component) => component.id === 621243);
|
||||
const hiddenTriggeredComponentOwners = new Map<number, { readonly parent: GeneratedSkill; readonly component: RomSourceComponent }>();
|
||||
if (runeOverloadParent && runeOverloadChildPayload) {
|
||||
hiddenTriggeredComponentOwners.set(498574, { parent: runeOverloadParent, component: runeOverloadChildPayload });
|
||||
}
|
||||
|
||||
/** Resolve source-triggered MagicObject rows that are not player-facing skills. */
|
||||
export function romTriggeredAbilityBySpellId(spellId: number): AbilityDefinition | null {
|
||||
const owner = triggeredComponentOwners.get(spellId);
|
||||
const owner = hiddenTriggeredComponentOwners.get(spellId) ?? triggeredComponentOwners.get(spellId);
|
||||
if (!owner) return null;
|
||||
const parentAbility = romAbilityByGeneratedId.get(owner.parent.id);
|
||||
if (!parentAbility) return null;
|
||||
const triggeredEffects = effects({
|
||||
sourceSkillId: spellId,
|
||||
name: `${owner.parent.name} trigger`,
|
||||
description: "",
|
||||
target: owner.parent.target,
|
||||
passive: false,
|
||||
source: { componentIds: [spellId], components: [owner.component] },
|
||||
}).filter((effect) => effect.kind !== "scripted" && effect.kind !== "unsupported");
|
||||
const triggeredEffects = spellId === 498574
|
||||
? [{ kind: "damage", coefficient: 0.6, school: "physical" } satisfies AbilityEffect]
|
||||
: effects({
|
||||
sourceSkillId: spellId,
|
||||
name: `${owner.parent.name} trigger`,
|
||||
description: "",
|
||||
target: owner.parent.target,
|
||||
passive: false,
|
||||
source: { componentIds: [spellId], components: [owner.component] },
|
||||
}).filter((effect) => effect.kind !== "scripted" && effect.kind !== "unsupported");
|
||||
if (!triggeredEffects.length) return null;
|
||||
return {
|
||||
...parentAbility,
|
||||
|
||||
Reference in new issue
Block a user