forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
f90ba7d989
commit
189b94d6b7
51 files changed
+3448
-154
No files matched your search
@@ -171,7 +171,7 @@ function auraModifiers(effect: Wow335Effect): readonly AuraModifier[] {
|
||||
case 13: return [{ kind: "damage", direction: "dealt", school: schoolFromMask(effect.miscValue), operation: "flat", value: effect.basePoints }];
|
||||
case 14: return [{ kind: "damage", direction: "taken", school: schoolFromMask(effect.miscValue), operation: "flat", value: effect.basePoints }];
|
||||
case 20: return flatStat("health-regeneration");
|
||||
case 22:
|
||||
case 22: return effect.miscValue === 1 ? flatStat("armor") : flatStat(resistanceStat);
|
||||
case 83:
|
||||
case 123:
|
||||
case 143: return flatStat(resistanceStat);
|
||||
@@ -210,13 +210,13 @@ function auraModifiers(effect: Wow335Effect): readonly AuraModifier[] {
|
||||
case 49: return percentStat("dodge");
|
||||
case 50: return percentStat("critical-healing");
|
||||
case 51: return percentStat("block");
|
||||
case 52:
|
||||
case 187:
|
||||
case 188:
|
||||
case 197: return percentStat("critical-strike");
|
||||
case 54:
|
||||
case 184:
|
||||
case 185: return percentStat("hit");
|
||||
case 52: return percentStat("critical-strike");
|
||||
case 187: return [{ kind: "stat", stat: "incoming-melee-critical-strike", operation: "flat", value: effect.basePoints }];
|
||||
case 188: return [{ kind: "stat", stat: "incoming-ranged-critical-strike", operation: "flat", value: effect.basePoints }];
|
||||
case 197: return [{ kind: "stat", stat: "incoming-critical-strike", operation: "flat", value: effect.basePoints }];
|
||||
case 54: return percentStat("hit");
|
||||
case 184: return [{ kind: "stat", stat: "incoming-melee-hit", operation: "flat", value: effect.basePoints }];
|
||||
case 185: return [{ kind: "stat", stat: "incoming-ranged-hit", operation: "flat", value: effect.basePoints }];
|
||||
case 55:
|
||||
case 186:
|
||||
case 199: return percentStat("spell-hit");
|
||||
@@ -447,6 +447,14 @@ function namedAuraForEffect(
|
||||
const school = schoolFromMask(effect.miscValue);
|
||||
const auraId = `wow335:${rank.spellId}:effect:${effectIndex}:aura:${effect.auraType}`;
|
||||
const modifiers = [...auraModifiers(effect)];
|
||||
if (abilityName === "Inner Fire") {
|
||||
for (const [index, modifier] of modifiers.entries()) modifiers[index] = { ...modifier, perStack: false };
|
||||
}
|
||||
if (abilityName === "Molten Armor" && effect.auraType === 220) {
|
||||
modifiers.splice(0, modifiers.length, {
|
||||
kind: "stat", stat: "spirit-to-spell-crit-rating", operation: "flat", value: effect.basePoints / 100,
|
||||
});
|
||||
}
|
||||
if (abilityName === "Greater Blessing of Sanctuary" && effect.auraType === 4) {
|
||||
modifiers.push(
|
||||
{ kind: "damage", direction: "taken", operation: "percent", value: -0.03 },
|
||||
@@ -623,8 +631,9 @@ function namedAuraForEffect(
|
||||
? { hideFromAuraStrip: true }
|
||||
: {}),
|
||||
durationMs,
|
||||
maxStacks: 1,
|
||||
stackBehavior: "refresh",
|
||||
maxStacks: abilityName === "Inner Fire" ? 20 : 1,
|
||||
...(abilityName === "Inner Fire" ? { initialStacks: 20 } : {}),
|
||||
stackBehavior: abilityName === "Inner Fire" ? "replace" : "refresh",
|
||||
...(exclusiveGroupForAbility(classId, abilityName) || abilityName === "Light's Beacon"
|
||||
? { exclusiveGroup: abilityName === "Light's Beacon" ? "paladin:beacon" : exclusiveGroupForAbility(classId, abilityName) }
|
||||
: {}),
|
||||
@@ -758,10 +767,36 @@ function classScriptEffects(
|
||||
case "warrior:Execute": return [{ kind: "execute", coefficient: 1.8 }];
|
||||
case "warrior:Charge": return [{ kind: "status", status: "stun", durationMs: 1_500 }];
|
||||
case "warrior:Intercept": return [{ kind: "status", status: "stun", durationMs: 3_000 }];
|
||||
case "warrior:Devastate": return [
|
||||
{
|
||||
kind: "damage",
|
||||
coefficient: 1,
|
||||
bonusPerSunderStack: rank.effects.find((effect) => effect.effectType === 121)?.basePoints ?? 0,
|
||||
},
|
||||
{
|
||||
kind: "apply-aura",
|
||||
aura: {
|
||||
id: "wow335:warrior:sunder-armor", name: "Sunder Armor", disposition: "debuff", durationMs: 30_000,
|
||||
maxStacks: 5, stackBehavior: "add",
|
||||
modifiers: [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: 0.04, perStack: true }],
|
||||
},
|
||||
sourceEffectType: 6, sourceAuraType: 4, miscValue: 0, triggerSpellId: 0,
|
||||
},
|
||||
];
|
||||
case "warrior:Last Stand": return [{
|
||||
kind: "apply-aura",
|
||||
recipient: "caster",
|
||||
aura: {
|
||||
id: "wow335:warrior:last-stand", name: abilityName, disposition: "buff",
|
||||
durationMs: 20_000, maxStacks: 1, stackBehavior: "refresh",
|
||||
modifiers: [{ kind: "stat", stat: "maximum-health", operation: "percent", value: 0.3 }],
|
||||
},
|
||||
sourceEffectType: 6, sourceAuraType: 133, miscValue: 0, triggerSpellId: 0,
|
||||
}];
|
||||
case "warrior:Sunder Armor": return [{
|
||||
kind: "apply-aura",
|
||||
aura: {
|
||||
id: `wow335:${rank.spellId}:sunder-armor`, name: abilityName, disposition: "debuff", durationMs: 30_000,
|
||||
id: "wow335:warrior:sunder-armor", name: abilityName, disposition: "debuff", durationMs: 30_000,
|
||||
maxStacks: 5, stackBehavior: "add",
|
||||
modifiers: [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: 0.04, perStack: true }],
|
||||
},
|
||||
@@ -793,6 +828,11 @@ function classScriptEffects(
|
||||
case "hunter:Freezing Arrow": return [{ kind: "status", status: "freeze", durationMs: 10_000 }];
|
||||
case "hunter:Feign Death": return [{ kind: "threat", mode: "percent", amount: -100 }];
|
||||
case "hunter:Readiness": return [{ kind: "cooldown-reset", mode: "reset" }];
|
||||
case "hunter:Chimera Shot": return [{ kind: "damage", coefficient: 1.25 }];
|
||||
case "hunter:Kill Shot": return [{
|
||||
kind: "damage", coefficient: 2,
|
||||
bonusFlat: rank.effects.find((effect) => effect.effectType === 17)?.basePoints ?? 0,
|
||||
}];
|
||||
// Volley's player-facing spell is a six-second channel aura whose Arcane
|
||||
// pulses live in same-rank triggered child rows. Keep the channel/cost but
|
||||
// make the six ground-area pulses executable by players and companions.
|
||||
@@ -807,7 +847,7 @@ function classScriptEffects(
|
||||
tag: "volley",
|
||||
}] : [];
|
||||
}
|
||||
case "rogue:Shiv": return [{ kind: "damage", coefficient: 1 }];
|
||||
case "rogue:Shiv": return [{ kind: "damage", coefficient: 1 }, { kind: "combo", amount: 1 }];
|
||||
case "rogue:Killing Spree": return [{
|
||||
kind: "dot", coefficient: 0.5, ticks: 5, intervalMs: 500, tag: "killing-spree",
|
||||
}];
|
||||
@@ -843,7 +883,8 @@ function classScriptEffects(
|
||||
durationMs: rank.durationMs > 0 ? rank.durationMs : 20_000,
|
||||
maxStacks: 1,
|
||||
stackBehavior: "refresh",
|
||||
modifiers: [{ kind: "stat", stat: "critical-strike", operation: "flat", value: 1 }],
|
||||
modifiers: [{ kind: "stat", stat: "critical-strike", operation: "flat", value: 100 }],
|
||||
utilityTags: ["next-offensive-critical"],
|
||||
},
|
||||
recipient: "caster",
|
||||
sourceEffectType: 6,
|
||||
@@ -887,10 +928,17 @@ function classScriptEffects(
|
||||
// trigger spells. Keep the imported cast/cost/rank data and supply only the
|
||||
// missing executable payload.
|
||||
case "priest:Mind Flay": return [{ kind: "damage", coefficient: 0.9 }];
|
||||
case "priest:Penance": return [{ kind: "heal", coefficient: 1.8 }];
|
||||
case "priest:Penance": return [
|
||||
{ kind: "heal", coefficient: 0.6 },
|
||||
{ kind: "hot", coefficient: 0.6, ticks: 2, intervalMs: 1_000 },
|
||||
{ kind: "damage", coefficient: 0.6 },
|
||||
{ kind: "dot", coefficient: 0.6, ticks: 2, intervalMs: 1_000, tag: "penance" },
|
||||
];
|
||||
case "priest:Divine Hymn": return [{
|
||||
kind: "heal", coefficient: 1, scope: "party", center: "caster", radius: 40, maximumTargets: 3,
|
||||
}];
|
||||
case "priest:Shadowfiend": return [
|
||||
{ kind: "summon", creatureId: 19668, coefficient: 0.5, durationMs: 15_000 },
|
||||
{ kind: "resource", resource: "mana", amount: 30, percentage: true },
|
||||
];
|
||||
case "priest:Inner Focus": return [{
|
||||
kind: "apply-aura",
|
||||
@@ -971,6 +1019,18 @@ function classScriptEffects(
|
||||
case "hunter:Misdirection": return [{ kind: "threat", mode: "redirect", amount: 100 }];
|
||||
case "rogue:Tricks of the Trade": return [{ kind: "threat", mode: "redirect", amount: 100 }];
|
||||
case "mage:Blink": return [{ kind: "movement", movement: "leap", toward: "away" }];
|
||||
case "mage:Combustion": return [{
|
||||
kind: "apply-aura", recipient: "caster",
|
||||
aura: {
|
||||
id: "wow335:mage:combustion", name: abilityName, disposition: "buff",
|
||||
durationMs: null, maxStacks: 100, stackBehavior: "replace",
|
||||
utilityTags: ["fire-crit-stacking"],
|
||||
},
|
||||
sourceEffectType: 6, sourceAuraType: 4, miscValue: 0, triggerSpellId: 0,
|
||||
}];
|
||||
case "mage:Mirror Image": return Array.from({ length: 3 }, () => ({
|
||||
kind: "summon" as const, creatureId: 58836, coefficient: 0.25, durationMs: 30_000,
|
||||
}));
|
||||
// Arcane Missiles stores its per-tick payload in a server-triggered child spell
|
||||
// that is not a player-facing SkillLine entry. Model the channel payload here so
|
||||
// both players and shared-rotation consumers execute the imported spell instead
|
||||
@@ -1275,7 +1335,9 @@ function effectsForRank(classId: BaseClassId, rank: Wow335Rank, abilityName: str
|
||||
}
|
||||
const auraOnlyStance = classId === "death-knight"
|
||||
&& ["Blood Presence", "Frost Presence", "Unholy Presence"].includes(abilityName);
|
||||
if (!auraOnlyStance && !effects.some((effect) => ["damage", "heal", "dot", "hot", "shield"].includes(effect.kind))) {
|
||||
if (!auraOnlyStance && !(classId === "warrior" && abilityName === "Last Stand")
|
||||
&& !(classId === "mage" && abilityName === "Molten Armor")
|
||||
&& !effects.some((effect) => ["damage", "heal", "dot", "hot", "shield"].includes(effect.kind))) {
|
||||
if (classId === "death-knight" && abilityName === "Death Coil") {
|
||||
effects.unshift({ kind: "damage", coefficient: 1 });
|
||||
} else if (/\b(?:heals?|restore|mend|rejuvenat)\w*\b/i.test(rank.description)) {
|
||||
@@ -1293,6 +1355,18 @@ function effectsForRank(classId: BaseClassId, rank: Wow335Rank, abilityName: str
|
||||
&& (effect.kind === "damage" || effect.kind === "heal"))
|
||||
|| (classId === "priest" && abilityName === "Penance"
|
||||
&& (effect.kind === "damage" || effect.kind === "heal"))
|
||||
|| (classId === "priest" && abilityName === "Divine Hymn" && effect.kind === "heal")
|
||||
|| (classId === "warrior" && ["Devastate", "Last Stand"].includes(abilityName)
|
||||
&& (effect.kind === "damage" || effect.kind === "heal"))
|
||||
|| (classId === "hunter" && abilityName === "Chimera Shot" && effect.kind === "damage")
|
||||
|| (classId === "hunter" && abilityName === "Kill Shot" && effect.kind === "damage")
|
||||
|| (classId === "priest" && abilityName === "Shadowfiend"
|
||||
&& (effect.kind === "summon" || effect.kind === "trigger-spell"))
|
||||
|| (classId === "mage" && abilityName === "Combustion"
|
||||
&& (effect.kind === "damage" || effect.kind === "apply-aura" || effect.kind === "trigger-spell"))
|
||||
|| (classId === "mage" && abilityName === "Mirror Image"
|
||||
&& (effect.kind === "summon" || effect.kind === "trigger-spell"))
|
||||
|| (classId === "rogue" && abilityName === "Cold Blood" && effect.kind === "apply-aura")
|
||||
|| (classId === "shaman" && abilityName === "Feral Spirit" && effect.kind === "summon")
|
||||
|| (classId === "warrior" && abilityName === "Bladestorm" && effect.kind === "dot")
|
||||
|| (classId === "paladin" && abilityName === "Hammer of the Righteous" && effect.kind === "damage")
|
||||
@@ -1443,9 +1517,20 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
const sourceRanks = chain.ranks.some((rank) => rank.rank > 0)
|
||||
? chain.ranks.filter((rank) => rank.rank > 0)
|
||||
: chain.ranks;
|
||||
const ranks = sourceRanks.map((rank, index) => rankDefinition(classId, resource, rank, index, chain.name));
|
||||
// Holy Nova's DBC chain stores damage and healing as separate same-rank
|
||||
// spells. The damage spell is the cast (and pays mana); its healing partner
|
||||
// is a second payload of that same cast, not another selectable rank.
|
||||
const castRanks = classId === "priest" && chain.name === "Holy Nova"
|
||||
? sourceRanks.filter((rank) => rank.effects.some((effect) => effect.effectType === 2))
|
||||
.map((rank) => {
|
||||
const healing = sourceRanks.find((candidate) => candidate.rank === rank.rank
|
||||
&& candidate.effects.some((effect) => effect.effectType === 10));
|
||||
return healing ? { ...rank, effects: [...rank.effects, ...healing.effects] } : rank;
|
||||
})
|
||||
: sourceRanks;
|
||||
const ranks = castRanks.map((rank, index) => rankDefinition(classId, resource, rank, index, chain.name));
|
||||
const first = ranks[0];
|
||||
const firstRaw = sourceRanks[0];
|
||||
const firstRaw = castRanks[0];
|
||||
if (!first || !firstRaw) return null;
|
||||
const target = abilityNameTarget(chain.name) ?? targetForRank(firstRaw, first.effects);
|
||||
const radius = Math.max(
|
||||
@@ -1506,7 +1591,8 @@ function abilityNameRadius(abilityName: string): number {
|
||||
function abilityNameTarget(abilityName: string): AbilityTarget | undefined {
|
||||
if (["Bladestorm", "Divine Hymn", "Tranquility", "Holy Nova", "Hellfire", "Army of the Dead", "Fan of Knives"].includes(abilityName)) return "self";
|
||||
if (abilityName === "Volley") return "hostile";
|
||||
if (abilityName === "Penance") return "friendly";
|
||||
if (abilityName === "Penance") return "any";
|
||||
if (["Last Stand", "Enraged Regeneration", "Molten Armor", "Combustion"].includes(abilityName)) return "self";
|
||||
if (abilityName === "Death Coil") return "hostile";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user