Validate RoM primary five hybrid stats and pair mechanics

This commit is contained in:
phenom committed 2026-09-23 19:08:44 -04:00
1 parent 8e95480d4f
commit c8b3a3d95a
18 files changed
+2061 -48

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog";
import type { RomGeneratedAbilityLike } from "./romAbilitySemantics";
/** The absorb amounts in Electrostatic Charge's source row are HP percentages. */
export const ROM_ELECTROSTATIC_PERCENT_ABSORB_TAG = "rom-electrostatic-percent-max-health";
export const ROM_MAGE_WARLOCK_PASSIVE_TAGS = Object.freeze({
industryFire: "rom-mage-warlock-industry-fire",
lightningDisplay: "rom-mage-warlock-lightning-display",
elementsOfPride: "rom-mage-warlock-elements-of-pride",
soulSpeed: "rom-mage-warlock-soul-speed",
elementalExtraction: "rom-mage-warlock-elemental-extraction",
} as const);
export const ROM_SOUL_STEPPING_CAST_TAG = "rom-mage-warlock-soul-stepping-cast";
// Industry Fire's player tooltip gives no magnitude for its extra fire hit.
// One Soul Pain tick is a conservative source-derived starting value.
const INDUSTRY_FIRE_DOT_MULTIPLIER = 1.1;
function stateAura(id: string, tag: string, durationMs: number | null): AbilityEffect {
return {
kind: "apply-aura",
recipient: "caster",
aura: {
id: `rom-mage-warlock-state-${id}`,
name: "Mage/Warlock class state",
disposition: "buff",
durationMs,
maxStacks: 1,
stackBehavior: "refresh",
utilityTags: [tag],
},
sourceEffectType: -1,
sourceAuraType: -1,
miscValue: 0,
triggerSpellId: 0,
};
}
/**
* Source-specific mappings for the accepted Mage/Warlock build. These auras
* are state markers; the combat runtime applies the conditional skill effects
* only when the named source spells execute or critically hit their target.
*/
export function overrideRomMageWarlockEffects(
row: RomGeneratedAbilityLike,
translated: readonly AbilityEffect[],
): readonly AbilityEffect[] | null {
if (row.sourceSkillId === 491156) {
return translated.map((effect) => {
if (effect.kind !== "apply-aura" || !effect.aura.absorbs?.length) return effect;
return {
...effect,
aura: {
...effect.aura,
utilityTags: [...(effect.aura.utilityTags ?? []), ROM_ELECTROSTATIC_PERCENT_ABSORB_TAG],
},
};
});
}
if (row.sourceSkillId === 498745) return [stateAura("industry-fire", ROM_MAGE_WARLOCK_PASSIVE_TAGS.industryFire, null)];
if (row.sourceSkillId === 498748) return [stateAura("lightning-display", ROM_MAGE_WARLOCK_PASSIVE_TAGS.lightningDisplay, null)];
if (row.sourceSkillId === 498751) return [stateAura("elements-of-pride", ROM_MAGE_WARLOCK_PASSIVE_TAGS.elementsOfPride, null)];
if (row.sourceSkillId === 499813) return [stateAura("soul-speed", ROM_MAGE_WARLOCK_PASSIVE_TAGS.soulSpeed, null)];
if (row.sourceSkillId === 499814) return [stateAura("elemental-extraction", ROM_MAGE_WARLOCK_PASSIVE_TAGS.elementalExtraction, null)];
if (row.sourceSkillId === 498757) return [
...translated,
stateAura("soul-stepping-cast", ROM_SOUL_STEPPING_CAST_TAG, 6_000),
];
return null;
}
function scaleDamage(effect: AbilityEffect, factor: number): AbilityEffect {
if (effect.kind !== "damage" && effect.kind !== "dot") return effect;
return {
...effect,
coefficient: effect.coefficient * factor,
...(effect.coefficientPerLevel === undefined ? {} : { coefficientPerLevel: effect.coefficientPerLevel * factor }),
...(effect.basePoints === undefined ? {} : { basePoints: effect.basePoints * factor }),
...(effect.pointsPerLevel === undefined ? {} : { pointsPerLevel: effect.pointsPerLevel * factor }),
...(effect.bonusPowerCoefficient === undefined ? {} : { bonusPowerCoefficient: effect.bonusPowerCoefficient * factor }),
};
}
function elementsOfPrideAura(school: "fire" | "nature", value: number): AbilityEffect {
return {
kind: "apply-aura",
recipient: "caster",
aura: {
id: `rom-mage-warlock-elements-${school}`,
name: `Elements of Pride: ${school}`,
disposition: "buff",
durationMs: 13_000,
maxStacks: 2,
stackBehavior: "add",
modifiers: [{ kind: "damage", direction: "dealt", school, operation: "percent", value, perStack: true }],
},
sourceEffectType: -1,
sourceAuraType: -1,
miscValue: 0,
triggerSpellId: 498751,
};
}
/** Add pair-elite riders to an executable cast after conditional source selection. */
export function romMageWarlockCastEffects(
ability: AbilityDefinition,
effects: readonly AbilityEffect[],
passiveTags: ReadonlySet<string>,
): readonly AbilityEffect[] {
if (ability.source !== "runewaker") return effects;
if (ability.dbcSpellId === 497964 && passiveTags.has(ROM_MAGE_WARLOCK_PASSIVE_TAGS.industryFire)) {
const transformed = effects.map((effect) => effect.kind === "dot"
? { ...scaleDamage(effect, INDUSTRY_FIRE_DOT_MULTIPLIER), school: "fire" as const }
: effect);
const dot = transformed.find((effect) => effect.kind === "dot");
if (dot?.kind !== "dot") return transformed;
return [
...transformed,
{
kind: "damage",
school: "fire",
coefficient: dot.coefficient,
basePoints: dot.basePoints ?? 0,
pointsPerLevel: dot.pointsPerLevel ?? 0,
sourceLevel: dot.sourceLevel ?? 1,
},
];
}
let result = effects;
if (passiveTags.has(ROM_MAGE_WARLOCK_PASSIVE_TAGS.lightningDisplay)) {
result = result.map((effect) => {
if (effect.kind === "apply-aura" && effect.aura.utilityTags?.includes("rom-charged")
&& ability.dbcSpellId === 491169) {
return { ...effect, aura: { ...effect.aura, durationMs: 18_000 } };
}
return (effect.kind === "damage" || effect.kind === "dot") && effect.school === "nature"
? scaleDamage(effect, 1.01)
: effect;
});
}
if (passiveTags.has(ROM_MAGE_WARLOCK_PASSIVE_TAGS.elementsOfPride)) {
if (ability.dbcSpellId === 491150) result = [...result, elementsOfPrideAura("nature", 0.02)];
if (ability.dbcSpellId === 491170) result = [...result, elementsOfPrideAura("fire", 0.01)];
}
if (ability.dbcSpellId === 498757 && !passiveTags.has(ROM_MAGE_WARLOCK_PASSIVE_TAGS.soulSpeed)) {
result = result.filter((effect) => effect.kind !== "apply-aura"
|| (effect.sourceAuraType !== 24 && effect.sourceAuraType !== 51));
}
return result;
}
/** Apply only after Electric Explosion or source 499812 critically hits. */
export function romMageWarlockCriticalEffects(
ability: AbilityDefinition,
passiveTags: ReadonlySet<string>,
): readonly AbilityEffect[] {
if (!passiveTags.has(ROM_MAGE_WARLOCK_PASSIVE_TAGS.elementalExtraction)
|| (ability.dbcSpellId !== 491170 && ability.dbcSpellId !== 499812)) return [];
return [{
kind: "apply-aura",
recipient: "ability-target",
aura: {
id: "rom-mage-warlock-elemental-extraction-debuff",
name: "Elemental Extraction",
disposition: "debuff",
durationMs: 10_000,
maxStacks: 1,
stackBehavior: "refresh",
modifiers: [
{ kind: "stat", stat: "armor", operation: "percent", value: -0.3 },
{ kind: "stat", stat: "magical-defense", operation: "percent", value: -0.3 },
],
},
sourceEffectType: -1,
sourceAuraType: -1,
miscValue: 0,
triggerSpellId: 499814,
}];
}