forked from phenom/healer-man
Release Healer Man 0.1.6
This commit is contained in:
1 parent
55cfd43d66
commit
b12fb84859
96 files changed
+10874
-609
No files matched your search
+276
-28
@@ -12,6 +12,7 @@ import {
|
||||
translateRomAbilitySemantics,
|
||||
type RomAbilitySemantic,
|
||||
type RomGeneratedAbilityLike,
|
||||
type RomSourceComponent,
|
||||
} from "./romAbilitySemantics";
|
||||
|
||||
export type RomSkillGroup = "primary" | "general" | "elite" | "passive";
|
||||
@@ -71,9 +72,9 @@ interface GeneratedSkill {
|
||||
const generated = rawCatalog.skills as unknown as readonly GeneratedSkill[];
|
||||
|
||||
function resource(value: string): ResourceType {
|
||||
return ["mana", "rage", "energy", "runic-power", "focus", "health"].includes(value)
|
||||
return ["mana", "rage", "energy", "runic-power", "focus", "nature-power", "psi", "health"].includes(value)
|
||||
? value as ResourceType
|
||||
: "mana";
|
||||
: `custom:${slug(value)}`;
|
||||
}
|
||||
|
||||
function costs(rows: readonly GeneratedCost[]): readonly AbilityCost[] {
|
||||
@@ -109,10 +110,10 @@ function knownResource(value: string): ResourceType | undefined {
|
||||
if (value === "mp") return "mana";
|
||||
if (value === "nature's power") return "nature-power";
|
||||
if (value === "psi point" || value === "psi points") return "psi";
|
||||
if (value === "soul point" || value === "soul points") return "focus";
|
||||
if (value === "soul point" || value === "soul points") return "soul-power";
|
||||
return ["mana", "rage", "energy", "runic-power", "focus", "nature-power", "psi", "health"].includes(value)
|
||||
? value as ResourceType
|
||||
: undefined;
|
||||
: value ? `custom:${slug(value)}` : undefined;
|
||||
}
|
||||
|
||||
function auraRecipient(row: GeneratedSemanticRow, semantic: Extract<RomAbilitySemantic, { kind: "aura" | "absorb" | "proc" }>): "ability-target" | "caster" {
|
||||
@@ -123,36 +124,119 @@ function auraRecipient(row: GeneratedSemanticRow, semantic: Extract<RomAbilitySe
|
||||
return "ability-target";
|
||||
}
|
||||
|
||||
function auraModifier(semantic: Extract<RomAbilitySemantic, { kind: "aura" }>): AuraModifier | null {
|
||||
const MAGIC_SCHOOLS: readonly DamageSchool[] = ["arcane", "fire", "frost", "holy", "nature", "shadow"];
|
||||
|
||||
function percent(value: number): number {
|
||||
return value / 100;
|
||||
}
|
||||
|
||||
function magicDamageTaken(value: number): readonly AuraModifier[] {
|
||||
return MAGIC_SCHOOLS.map((school) => ({
|
||||
kind: "damage" as const,
|
||||
direction: "taken" as const,
|
||||
school,
|
||||
operation: "percent" as const,
|
||||
value,
|
||||
}));
|
||||
}
|
||||
|
||||
function auraModifiers(semantic: Extract<RomAbilitySemantic, { kind: "aura" }>): readonly AuraModifier[] {
|
||||
const value = semantic.value ?? 0;
|
||||
if (semantic.effect === "damage-immunity") {
|
||||
return { kind: "damage", direction: "taken", operation: "percent", value: -100 };
|
||||
return [{ kind: "damage", direction: "taken", operation: "percent", value: -1 }];
|
||||
}
|
||||
if (semantic.effect === "physical-damage-immunity") {
|
||||
return { kind: "damage", direction: "taken", school: "physical", operation: "percent", value: -100 };
|
||||
return [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: -1 }];
|
||||
}
|
||||
if (semantic.effect === "magic-damage-immunity") {
|
||||
return { kind: "stat", stat: "magic-damage-immunity", operation: "flat", value: 1 };
|
||||
return magicDamageTaken(-1);
|
||||
}
|
||||
if (semantic.effect === "poison-immunity") {
|
||||
return { kind: "stat", stat: "poison-immunity", operation: "flat", value: 1 };
|
||||
return [{ kind: "stat", stat: "poison-immunity", operation: "flat", value: 1 }];
|
||||
}
|
||||
if (semantic.effect === "maximum-mana") {
|
||||
return { kind: "resource", resource: "mana", property: "maximum", operation: "flat", value };
|
||||
return [{ kind: "resource", resource: "mana", property: "maximum", operation: "flat", value }];
|
||||
}
|
||||
if (semantic.effect === "mana-regeneration") {
|
||||
return { kind: "resource", resource: "mana", property: "regeneration", operation: "flat", value };
|
||||
return [{ kind: "resource", resource: "mana", property: "regeneration", operation: "flat", value }];
|
||||
}
|
||||
if (semantic.effect === "health-regeneration") {
|
||||
return { kind: "resource", resource: "health", property: "regeneration", operation: "flat", value };
|
||||
return [{ kind: "stat", stat: "health-regeneration", operation: "flat", value }];
|
||||
}
|
||||
if (["physical-attack", "magical-attack", "weapon-damage"].includes(semantic.effect)) {
|
||||
return { kind: "stat", stat: semantic.effect, operation: "flat", value };
|
||||
|
||||
const type = semantic.modifierType;
|
||||
if (type === null) return semantic.value === null
|
||||
? []
|
||||
: [{ kind: "stat", stat: semantic.effect, operation: "flat", value }];
|
||||
if (type >= 2 && type <= 22) {
|
||||
return [{ kind: "stat", stat: semantic.effect, operation: "flat", value }];
|
||||
}
|
||||
if (semantic.modifierType !== null || semantic.value !== null) {
|
||||
return { kind: "stat", stat: semantic.effect, operation: "flat", value };
|
||||
if (type === 23) return [{ kind: "stat", stat: "attack-speed", operation: "flat", value: -value }];
|
||||
if (type === 24) return [{ kind: "stat", stat: "movement-speed", operation: "percent", value: percent(value) }];
|
||||
if (type === 25) return [
|
||||
{ kind: "damage", direction: "dealt", attackKind: "melee", operation: "flat", value },
|
||||
{ kind: "damage", direction: "dealt", attackKind: "ranged", operation: "flat", value },
|
||||
];
|
||||
if (type >= 26 && type <= 32) {
|
||||
const school = ({ 27: "nature", 28: "frost", 29: "fire", 30: "nature", 31: "holy", 32: "shadow" } as Partial<Record<number, DamageSchool>>)[type];
|
||||
return [{ kind: "stat", stat: school ? `resistance:${school}` : "all-resistance", operation: "flat", value }];
|
||||
}
|
||||
return null;
|
||||
if (type === 33) return [{ kind: "resource", resource: "mana", property: "cost", operation: "percent", value: -percent(value) }];
|
||||
if (type === 37 || (type >= 56 && type <= 67) || type === 173) {
|
||||
return [{ kind: "damage", direction: "dealt", attackKind: "melee", operation: "percent", value: percent(value) }];
|
||||
}
|
||||
if (type >= 38 && type <= 43) return [{ kind: "stat", stat: "armor", operation: "percent", value: percent(value) }];
|
||||
if (type === 44) return [{ kind: "damage", direction: "dealt", attackKind: "spell", operation: "percent", value: percent(value) }];
|
||||
if (type >= 45 && type <= 50) {
|
||||
const school = (["nature", "frost", "fire", "nature", "holy", "shadow"] as const)[type - 45];
|
||||
return [{ kind: "damage", direction: "dealt", school, operation: "percent", value: percent(value) }];
|
||||
}
|
||||
if (type === 51) return [{ kind: "stat", stat: "spell-haste", operation: "flat", value: -value }];
|
||||
if (type >= 52 && type <= 55) return [{ kind: "damage", direction: "dealt", attackKind: "ranged", operation: "percent", value: percent(value) }];
|
||||
if (type >= 68 && type <= 71) return [{ kind: "stat", stat: "ranged-haste", operation: "flat", value: -value }];
|
||||
if (type >= 72 && type <= 83) return [{ kind: "stat", stat: "melee-haste", operation: "flat", value: -value }];
|
||||
if (type === 134) return [{ kind: "stat", stat: "attack-power", operation: "percent", value: percent(value) }];
|
||||
if (type === 135) return [{ kind: "stat", stat: "armor", operation: "percent", value: percent(value) }];
|
||||
if (type === 138) return [{ kind: "stat", stat: "threat", operation: "percent", value: percent(value) }];
|
||||
if (type >= 139 && type <= 141) {
|
||||
const resource = (["rage", "focus", "energy"] as const)[type - 139];
|
||||
return [{ kind: "resource", resource, property: "regeneration", operation: "percent", value: percent(value) }];
|
||||
}
|
||||
if (type === 142) return magicDamageTaken(-percent(value));
|
||||
if (type === 143) return [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: -percent(value) }];
|
||||
if (type === 144) return [{ kind: "healing", direction: "taken", operation: "percent", value: -percent(value) }];
|
||||
if (type === 148) return [{ kind: "damage", direction: "dealt", attackKind: "spell", operation: "flat", value }];
|
||||
if (type === 149) return [{ kind: "healing", direction: "done", operation: "percent", value: percent(value) }];
|
||||
if (type === 150) return [{ kind: "healing", direction: "done", operation: "flat", value }];
|
||||
if (type >= 161 && type <= 166) {
|
||||
const stat = (["strength", "stamina", "intellect", "wisdom", "dexterity", "all-primary-attributes"] as const)[type - 161];
|
||||
return [{ kind: "stat", stat, operation: "percent", value: percent(value) }];
|
||||
}
|
||||
if (type === 167) return [{ kind: "stat", stat: "maximum-health", operation: "percent", value: percent(value) }];
|
||||
if (type === 168) return [{ kind: "resource", resource: "mana", property: "maximum", operation: "percent", value: percent(value) }];
|
||||
if (type === 170) return [{ kind: "stat", stat: "magical-defense", operation: "percent", value: percent(value) }];
|
||||
if (type === 171) return [{ kind: "stat", stat: "magical-attack", operation: "percent", value: percent(value) }];
|
||||
if (type === 184 || type === 185 || type === 186 || type === 187 || type === 188 || type === 189) {
|
||||
const school = (["nature", "frost", "fire", "nature", "holy", "shadow"] as const)[type - 184];
|
||||
return [{ kind: "damage", direction: "dealt", school, operation: "flat", value }];
|
||||
}
|
||||
if (type === 191) return [{ kind: "damage", direction: "dealt", attackKind: "spell", operation: "flat", value }];
|
||||
if (type === 192 || type === 208) return [{ kind: "damage", direction: "dealt", attackKind: "spell", operation: "percent", value: percent(value) }];
|
||||
if (type === 195 || type === 199) return [{ kind: "stat", stat: "magical-hit", operation: "flat", value }];
|
||||
if (type === 197) return [{ kind: "stat", stat: "physical-hit", operation: "flat", value }];
|
||||
if (type === 198) return [{ kind: "stat", stat: "dodge", operation: "flat", value }];
|
||||
if (type === 204 || type === 205) return [{ kind: "damage", direction: "dealt", operation: "percent", value: percent(value) }];
|
||||
if (type === 206 || type === 207 || type === 209) return [{ kind: "damage", direction: "taken", operation: "percent", value: percent(value) }];
|
||||
if (type >= 214 && type <= 216) {
|
||||
const resource = (["rage", "energy", "focus"] as const)[type - 214];
|
||||
return [{ kind: "resource", resource, property: "cost", operation: "percent", value: -percent(value) }];
|
||||
}
|
||||
if (type >= 220 && type <= 226) {
|
||||
const school = ({ 221: "nature", 222: "frost", 223: "fire", 224: "nature", 225: "holy", 226: "shadow" } as Partial<Record<number, DamageSchool>>)[type];
|
||||
return [{ kind: "stat", stat: school ? `resistance:${school}` : "all-resistance", operation: "percent", value: percent(value) }];
|
||||
}
|
||||
if (type === 227) return [{ kind: "stat", stat: "parry", operation: "percent", value: percent(value) }];
|
||||
return [{ kind: "stat", stat: semantic.effect, operation: "flat", value }];
|
||||
}
|
||||
|
||||
function controlKind(effect: string): Extract<AbilityEffect, { kind: "apply-aura" }>["control"] {
|
||||
@@ -173,11 +257,30 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
const componentKey = semantic.sourceComponentIds.join("-") || "tooltip";
|
||||
const effectId = `${row.sourceSkillId}-${componentKey}-${index}`;
|
||||
if (semantic.kind === "damage" || semantic.kind === "heal") {
|
||||
const rawPower = Math.max(0, semantic.power);
|
||||
const rawFixed = Math.max(0, semantic.fixedValue);
|
||||
const coefficientCalculation = semantic.calculationType === null
|
||||
? rawPower > 0 && rawPower <= 4
|
||||
: [0, 2, 3, 10].includes(semantic.calculationType);
|
||||
const powerCoefficient = coefficientCalculation ? rawPower : 0;
|
||||
const fixedCoefficient = semantic.fixedType > 0 ? rawFixed : 0;
|
||||
const coefficientBase = powerCoefficient + fixedCoefficient;
|
||||
const coefficientPerLevel = powerCoefficient * Math.max(0, semantic.powerPerSkillLevel) / 100
|
||||
+ fixedCoefficient * Math.max(0, semantic.fixedPerSkillLevel) / 100;
|
||||
const coefficient = coefficientBase + coefficientPerLevel;
|
||||
const sourcePoints = coefficientCalculation ? 0 : rawPower;
|
||||
const pointsPerLevel = sourcePoints * Math.max(0, semantic.powerPerSkillLevel) / 100;
|
||||
const basePoints = sourcePoints + pointsPerLevel;
|
||||
return {
|
||||
kind: semantic.kind,
|
||||
coefficient: Math.max(0, semantic.power),
|
||||
basePoints: Math.max(0, semantic.fixedValue),
|
||||
pointsPerLevel: Math.max(0, semantic.fixedPerSkillLevel),
|
||||
coefficient,
|
||||
...(coefficientPerLevel > 0 ? { coefficientPerLevel, sourceLevel: 1 } : {}),
|
||||
...(basePoints > 0 ? {
|
||||
basePoints,
|
||||
pointsPerLevel,
|
||||
sourceLevel: 1,
|
||||
...(coefficient > 0 ? { bonusPowerCoefficient: coefficient } : {}),
|
||||
} : {}),
|
||||
...(semantic.kind === "heal" && semantic.percentMaxHealth !== undefined
|
||||
? { percentMaxHealth: semantic.percentMaxHealth }
|
||||
: {}),
|
||||
@@ -189,8 +292,9 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
return {
|
||||
kind: semantic.kind,
|
||||
coefficient: 0,
|
||||
basePoints: Math.max(0, semantic.basePerTick),
|
||||
pointsPerLevel: Math.max(0, semantic.perSkillLevel),
|
||||
basePoints: Math.max(0, semantic.basePerTick) * (1 + Math.max(0, semantic.perSkillLevel) / 100),
|
||||
pointsPerLevel: Math.max(0, semantic.basePerTick) * Math.max(0, semantic.perSkillLevel) / 100,
|
||||
sourceLevel: 1,
|
||||
ticks,
|
||||
intervalMs,
|
||||
...(semantic.kind === "dot" ? { tag: `rom-${effectId}` } : {}),
|
||||
@@ -214,24 +318,30 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
id: `rom-control-${effectId}-${slug(semantic.effect)}`,
|
||||
name: `${row.name}: ${semantic.effect}`,
|
||||
disposition: "debuff",
|
||||
durationMs: durationMs(semantic.durationSeconds),
|
||||
durationMs: Math.max(1_000, durationMs(semantic.durationSeconds)),
|
||||
dispelCategory: "magic",
|
||||
};
|
||||
return { kind: "apply-aura", aura, control, sourceEffectType: -1, sourceAuraType: -1, miscValue: 0, triggerSpellId: 0 };
|
||||
}
|
||||
if (semantic.kind === "interrupt") return { kind: "interrupt", lockoutMs: durationMs(semantic.lockoutSeconds || 4) };
|
||||
if (semantic.kind === "interrupt") return { kind: "interrupt", lockoutMs: Math.max(1_000, durationMs(semantic.lockoutSeconds || 4)) };
|
||||
if (semantic.kind === "aura") {
|
||||
const recipient = auraRecipient(row, semantic);
|
||||
const modifier = auraModifier(semantic);
|
||||
const modifiers = auraModifiers(semantic);
|
||||
const partyScope = /\b(?:all )?party members?\b/i.test(row.description);
|
||||
const aura: AuraDefinition = {
|
||||
id: `rom-aura-${effectId}-${slug(semantic.effect)}`,
|
||||
name: `${row.name}: ${semantic.effect}`,
|
||||
disposition: recipient === "caster" || row.target !== "hostile" ? "buff" : "debuff",
|
||||
durationMs: semantic.durationSeconds > 0 ? durationMs(semantic.durationSeconds) : null,
|
||||
dispelCategory: "magic",
|
||||
...(modifier ? { modifiers: [modifier] } : {}),
|
||||
...(modifiers.length ? { modifiers } : {}),
|
||||
...(semantic.effect === "poison-immunity"
|
||||
? { utilityTags: ["poison-immunity"] }
|
||||
: semantic.effect === "stealth"
|
||||
? { utilityTags: ["stealth"] }
|
||||
: {}),
|
||||
};
|
||||
return { kind: "apply-aura", aura, recipient, sourceEffectType: -1, sourceAuraType: semantic.modifierType ?? -1, miscValue: semantic.value ?? 0, triggerSpellId: 0 };
|
||||
return { kind: "apply-aura", aura, recipient, ...(partyScope ? { scope: "party" as const } : {}), sourceEffectType: -1, sourceAuraType: semantic.modifierType ?? -1, miscValue: semantic.value ?? 0, triggerSpellId: 0 };
|
||||
}
|
||||
if (semantic.kind === "absorb") {
|
||||
if (semantic.amount <= 0 && semantic.perSkillLevel <= 0) {
|
||||
@@ -260,6 +370,7 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
id: `rom-proc-trigger-${effectId}`,
|
||||
triggers: procTrigger(semantic.trigger),
|
||||
chance,
|
||||
...(semantic.intervalSeconds && semantic.intervalSeconds > 0 ? { internalCooldownMs: durationMs(semantic.intervalSeconds) } : {}),
|
||||
action: { kind: "custom", id: "rom-trigger-skill", data: { spellId: semantic.triggerSkillId, rank: semantic.rank ?? 0 } },
|
||||
}],
|
||||
};
|
||||
@@ -267,6 +378,23 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
}
|
||||
if (semantic.kind === "resource") {
|
||||
const resourceType = knownResource(semantic.resource);
|
||||
if (semantic.intervalSeconds && semantic.intervalSeconds > 0 && resourceType) {
|
||||
const amountPerSecond = semantic.amount / semantic.intervalSeconds;
|
||||
const aura: AuraDefinition = {
|
||||
id: `rom-resource-over-time-${effectId}`,
|
||||
name: row.name,
|
||||
disposition: semantic.operation === "drain" ? "debuff" : "buff",
|
||||
durationMs: semantic.durationSeconds && semantic.durationSeconds > 0 ? durationMs(semantic.durationSeconds) : null,
|
||||
modifiers: [{
|
||||
kind: "resource",
|
||||
resource: resourceType,
|
||||
property: "regeneration",
|
||||
operation: "flat",
|
||||
value: semantic.operation === "drain" ? -amountPerSecond : amountPerSecond,
|
||||
}],
|
||||
};
|
||||
return { kind: "apply-aura", aura, recipient: row.target === "hostile" ? "ability-target" : "caster", sourceEffectType: -1, sourceAuraType: -1, miscValue: semantic.amount, triggerSpellId: 0 };
|
||||
}
|
||||
if (semantic.operation === "drain") return {
|
||||
kind: "resource-drain",
|
||||
amount: semantic.amount,
|
||||
@@ -288,13 +416,97 @@ function semanticEffect(row: GeneratedSemanticRow, semantic: RomAbilitySemantic,
|
||||
if (semantic.kind === "trigger-spell") return { kind: "trigger-spell", spellId: semantic.spellId };
|
||||
if (semantic.kind === "pet") return { kind: "pet", action: semantic.action };
|
||||
if (semantic.kind === "custom") {
|
||||
if (semantic.mechanicId === "threat-reset") {
|
||||
return { kind: "threat", mode: "percent", amount: -100 };
|
||||
}
|
||||
return { kind: "scripted", effectType: -1, auraType: -1, miscValue: 0, triggerSpellId: 0, label: `RoM ${semantic.mechanicId}: ${semantic.summary}` };
|
||||
}
|
||||
return { kind: "scripted", effectType: -1, auraType: -1, miscValue: 0, triggerSpellId: 0, label: `RoM source mechanic` };
|
||||
}
|
||||
|
||||
function stateAuraEffect(
|
||||
row: GeneratedSemanticRow,
|
||||
stateId: string,
|
||||
name: string,
|
||||
duration: number | null,
|
||||
options: Pick<AuraDefinition, "maxStacks" | "stackBehavior" | "procs"> = {},
|
||||
): AbilityEffect {
|
||||
return {
|
||||
kind: "apply-aura",
|
||||
recipient: "caster",
|
||||
aura: {
|
||||
id: `rom-state-${stateId}`,
|
||||
name,
|
||||
disposition: "buff",
|
||||
durationMs: duration,
|
||||
utilityTags: [`rom-${stateId}`],
|
||||
...options,
|
||||
},
|
||||
sourceEffectType: -1,
|
||||
sourceAuraType: -1,
|
||||
miscValue: 0,
|
||||
triggerSpellId: row.sourceSkillId,
|
||||
};
|
||||
}
|
||||
|
||||
function specializedEffects(row: GeneratedSemanticRow): readonly AbilityEffect[] | null {
|
||||
if (row.sourceSkillId === 491163) {
|
||||
return [stateAuraEffect(row, "frost-arrow", "Frost Arrow", 600_000, {
|
||||
procs: [{
|
||||
id: "rom-frost-arrow-wind-arrow",
|
||||
triggers: "hit",
|
||||
chance: 0.5,
|
||||
action: { kind: "custom", id: "rom-frost-arrow" },
|
||||
}],
|
||||
})];
|
||||
}
|
||||
if (row.sourceSkillId === 491171) {
|
||||
return [stateAuraEffect(row, "static-field-charge", "Static Field Charge", null)];
|
||||
}
|
||||
if (row.sourceSkillId === 494038) {
|
||||
return [stateAuraEffect(row, "entling-offering", "Entling Offering", 900_000, {
|
||||
procs: [{
|
||||
id: "rom-entling-offering-shot",
|
||||
triggers: "hit",
|
||||
chance: 1,
|
||||
action: { kind: "custom", id: "rom-entling-offering" },
|
||||
}],
|
||||
})];
|
||||
}
|
||||
if (row.sourceSkillId === 495296) {
|
||||
return [stateAuraEffect(row, "holy-salvation-candle", "Holy Salvation Candle", 20_000)];
|
||||
}
|
||||
if (row.sourceSkillId === 498571) {
|
||||
return [stateAuraEffect(row, "remodeled-body", "Remodeled Body", 8_000)];
|
||||
}
|
||||
if (row.sourceSkillId === 499542) {
|
||||
return [{
|
||||
kind: "utility",
|
||||
action: "class-script",
|
||||
effectType: -1,
|
||||
auraType: -1,
|
||||
miscValue: 499542,
|
||||
triggerSpellId: 623078,
|
||||
durationMs: 0,
|
||||
}];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function effects(row: GeneratedSemanticRow): readonly AbilityEffect[] {
|
||||
return translateRomAbilitySemantics(row).map((semantic, index) => semanticEffect(row, semantic, index));
|
||||
const specialized = specializedEffects(row);
|
||||
if (specialized) return specialized;
|
||||
const translated = translateRomAbilitySemantics(row).map((semantic, index) => semanticEffect(row, semantic, index));
|
||||
if (row.sourceSkillId === 491169) {
|
||||
return [...translated, stateAuraEffect(row, "charged", "Charged", 6_000)];
|
||||
}
|
||||
return translated;
|
||||
}
|
||||
|
||||
function areaRadius(row: Pick<GeneratedSkill, "description" | "range">): number | undefined {
|
||||
if (!/\b(?:multiple targets|all targets|surrounding (?:enemies|targets)|party members within|friendly targets within|targets within (?:a )?range)\b/i.test(row.description)) return undefined;
|
||||
const sourceRange = row.description.match(/\b(?:range|radius) of\s+(\d+(?:\.\d+)?)/i)?.[1];
|
||||
return Math.max(3, Math.min(40, sourceRange ? Number(sourceRange) / 10 : row.range || 8));
|
||||
}
|
||||
|
||||
function rank(row: GeneratedRank, parent: GeneratedSkill): AbilityRankDefinition {
|
||||
@@ -337,6 +549,7 @@ function ability(row: GeneratedSkill): AbilityDefinition {
|
||||
description: row.description,
|
||||
target: row.target,
|
||||
range: { min: 0, max: row.range },
|
||||
...(areaRadius(row) !== undefined ? { radius: areaRadius(row) } : {}),
|
||||
castMode: row.castTimeMs > 0 ? "cast" : "instant",
|
||||
castTimeMs: row.castTimeMs,
|
||||
cooldownMs: row.cooldownMs,
|
||||
@@ -354,6 +567,41 @@ function ability(row: GeneratedSkill): AbilityDefinition {
|
||||
|
||||
export const ROM_ALL_ABILITIES = Object.freeze(generated.map(ability));
|
||||
|
||||
const romAbilityByGeneratedId = new Map(ROM_ALL_ABILITIES.map((entry) => [entry.id, entry]));
|
||||
const triggeredComponentOwners = new Map<number, { readonly parent: GeneratedSkill; readonly component: RomSourceComponent }>();
|
||||
for (const parent of generated) {
|
||||
const componentSets = [parent.source?.components ?? [], ...parent.ranks.map((entry) => entry.source?.components ?? [])];
|
||||
for (const components of componentSets) for (const component of components) {
|
||||
if (!triggeredComponentOwners.has(component.id)) triggeredComponentOwners.set(component.id, { parent, component });
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve source-triggered MagicObject rows that are not player-facing skills. */
|
||||
export function romTriggeredAbilityBySpellId(spellId: number): AbilityDefinition | null {
|
||||
const owner = 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");
|
||||
if (!triggeredEffects.length) return null;
|
||||
return {
|
||||
...parentAbility,
|
||||
id: `rom-triggered-${spellId}`,
|
||||
dbcSpellId: spellId,
|
||||
name: `${owner.parent.name} trigger`,
|
||||
passive: false,
|
||||
effects: triggeredEffects,
|
||||
ranks: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const ROM_ORDINARY_ABILITIES_BY_CLASS: Readonly<Record<RomClassId, readonly AbilityDefinition[]>> = Object.freeze(
|
||||
Object.fromEntries((rawCatalog.canonicalClasses as readonly { id: RomClassId }[]).map(({ id }) => [
|
||||
id,
|
||||
|
||||
Reference in new issue
Block a user