Release Healer Man 0.1.21

This commit is contained in:
phenom committed 2026-09-08 10:40:26 -04:00
1 parent 699fc858c9
commit f6f2375145
210 files changed
+976039 -6247

No files matched your search

+29 -16
View File
@@ -6,7 +6,12 @@ import { advanceDungeonPartyCombat } from "./partyRuntime";
import { useGameStore } from "./store";
import { activateManastormActiveSpellLoadout } from "./manastormSpellLoadout";
import { useManastormStore } from "./manastormStore";
import { useWailingEncounterStore } from "./wailingCavernsEncounter";
import {
activateDungeonEncounterDefinitions,
advanceDungeonEncounters,
resetDungeonEncounters,
} from "./dungeonEncounterRuntime";
import { requireDungeonDefinition } from "./dungeonRegistry";
import {
onlineSessionIsActive,
onlineSessionOwnsWorldSimulation,
@@ -21,9 +26,10 @@ import { combatSimulationTimerShouldRun } from "./combatSimulationLifecycle";
export function CombatBridge() {
const character = useShellStore((state) => state.activeCharacter);
const gameMode = useGameStore((state) => state.gameMode);
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
const activeRole = useShellStore((state) => state.activeDungeonRole);
const resetRevision = useGameStore((state) => state.resetRevision);
const mountedResetRevision = useRef(resetRevision);
const encounterResetRevision = useGameStore((state) => state.encounterResetRevision);
const mountedResetRevision = useRef(encounterResetRevision);
useEffect(() => {
if (!character) return;
@@ -37,11 +43,16 @@ export function CombatBridge() {
}, [activeRole, character?.id, gameMode]);
useEffect(() => {
if (mountedResetRevision.current === resetRevision) return;
mountedResetRevision.current = resetRevision;
const definition = requireDungeonDefinition(activeDungeonId);
activateDungeonEncounterDefinitions(activeDungeonId, definition.encounters ?? []);
}, [activeDungeonId]);
useEffect(() => {
if (mountedResetRevision.current === encounterResetRevision) return;
mountedResetRevision.current = encounterResetRevision;
useCombatStore.getState().resetEncounter();
useWailingEncounterStore.getState().reset();
}, [resetRevision]);
resetDungeonEncounters("dungeon", useGameStore.getState().activeDungeonId);
}, [encounterResetRevision]);
useEffect(() => {
if (!character) return;
@@ -113,15 +124,17 @@ export function CombatBridge() {
advanceDungeonPartyCombat(epochNow);
}
useCombatStore.getState().synchronizeActors();
if (
onlineSessionOwnsWorldSimulation()
&& game.gameMode === "dungeon"
&& game.activeDungeonId === "wailing-caverns"
) {
useWailingEncounterStore.getState().advance(
useCombatStore.getState().mobs,
epochNow,
);
if (onlineSessionOwnsWorldSimulation() && game.gameMode === "dungeon") {
const mobs = useCombatStore.getState().mobs;
const registeredEntityIds = Object.keys(mobs);
advanceDungeonEncounters(game.activeDungeonId, {
kind: "tick",
now: epochNow,
facts: {
registeredEntityIds,
deadEntityIds: registeredEntityIds.filter((id) => mobs[id].dead),
},
});
}
}
};
@@ -0,0 +1,29 @@
import { afterEach, describe, expect, it } from "vitest";
import { applyDungeonEncounterEffect } from "./DungeonEncounterEffectBridge";
import { clearPartyRuntimeRegistry, getPartyRuntimePosition, updatePartyRuntimePosition } from "./partyRuntimeRegistry";
import { usePartyStore } from "./partyStore";
import { useGameStore } from "./store";
afterEach(() => {
clearPartyRuntimeRegistry();
usePartyStore.setState({ members: [] });
});
describe("dungeon encounter effects", () => {
it("teleports the player and every current party runtime actor", () => {
usePartyStore.getState().initializeParty("encounter-effect-test", 1, 10);
const allyId = usePartyStore.getState().members[0].id;
updatePartyRuntimePosition(allyId, 0, 0, 0);
applyDungeonEncounterEffect({
dungeonId: useGameStore.getState().activeDungeonId,
encounterId: "test:transition",
effect: {
kind: "party-teleport",
actionId: "cross-threshold",
destination: [12, 3, -8],
},
}, 100);
expect(useGameStore.getState().playerPosition).toEqual([12, 3, -8]);
expect(getPartyRuntimePosition(allyId)).toEqual([12, 3, -8]);
});
});
+55
View File
@@ -0,0 +1,55 @@
import { useEffect } from "react";
import { useShellStore } from "../app/shellStore";
import {
dispatchDungeonEncounterSignal,
registerDungeonEncounterEffectHandler,
} from "./dungeonEncounterRuntime";
import { LOOT_CATALOG, rollBossLoot } from "./lootCatalog";
import { updatePartyRuntimePosition } from "./partyRuntimeRegistry";
import { usePartyStore } from "./partyStore";
import { useCombatStore } from "./combatStore";
import { useGameStore } from "./store";
import type { DungeonEncounterEffectEnvelope } from "./dungeonEncounterTypes";
import { publishOnlinePartyTeleport } from "./onlineSessionRuntime";
function grantEncounterLoot(lootTableId: string, now: number): boolean {
const table = LOOT_CATALOG.bosses.find((candidate) => candidate.lootTableId === lootTableId);
if (!table) return false;
const combat = useCombatStore.getState();
const drops = rollBossLoot(table.bossKey, combat.level, { now });
if (!drops.length) return false;
const inventory = [...combat.inventory, ...drops];
useCombatStore.setState({ inventory });
useShellStore.getState().saveActiveInventory(inventory);
return true;
}
/** Applies platform effects emitted by the renderer-neutral encounter reducer. */
export function DungeonEncounterEffectBridge() {
useEffect(() => registerDungeonEncounterEffectHandler((envelope) => {
applyDungeonEncounterEffect(envelope, Date.now());
}), []);
return null;
}
export function applyDungeonEncounterEffect(
{ effect }: DungeonEncounterEffectEnvelope,
now = Date.now(),
): void {
if (effect.kind === "signal") {
dispatchDungeonEncounterSignal(effect.signalId, now);
return;
}
if (effect.kind === "party-teleport") {
const game = useGameStore.getState();
game.relocatePlayer({ footPosition: effect.destination, yaw: game.cameraYaw }, true);
for (const member of usePartyStore.getState().members) {
updatePartyRuntimePosition(member.id, ...effect.destination);
}
publishOnlinePartyTeleport(effect.destination, effect.actionId, now);
return;
}
if (effect.kind === "loot-grant" && !grantEncounterLoot(effect.lootTableId, now)) {
useShellStore.getState().setNotice(`Loot table ${effect.lootTableId} is not packaged for this encounter.`);
}
}
+51
View File
@@ -0,0 +1,51 @@
import { useEffect, useRef } from "react";
import { useShellStore } from "../app/shellStore";
import {
exportDungeonEncounterCheckpointSnapshots,
subscribeDungeonEncounterSnapshots,
} from "./dungeonEncounterRuntime";
import { DUNGEON_RUN_CHECKPOINT_VERSION } from "./dungeonRunCheckpoint";
import { useGameStore } from "./store";
function checkpointSignature(dungeonId: string): string {
const game = useGameStore.getState();
const encounters = exportDungeonEncounterCheckpointSnapshots(dungeonId, 0);
return JSON.stringify({
dungeonId,
difficultyId: game.activeDifficultyId,
spawn: game.activeSpawn,
encounters,
});
}
/** Persists only reset-projected state when encounter progress reaches a new stable revision. */
export function DungeonRunCheckpointBridge() {
const phase = useShellStore((state) => state.phase);
const activeCharacterId = useShellStore((state) => state.activeCharacter?.id ?? null);
const lastSignature = useRef("");
useEffect(() => {
if (phase !== "game" || !activeCharacterId) return undefined;
const save = () => {
const game = useGameStore.getState();
if (game.gameMode !== "dungeon" || game.gmTestMode) return;
const signature = checkpointSignature(game.activeDungeonId);
if (signature === lastSignature.current) return;
lastSignature.current = signature;
const encounters = exportDungeonEncounterCheckpointSnapshots(game.activeDungeonId, 0);
useShellStore.getState().saveActiveDungeonRunCheckpoint({
version: DUNGEON_RUN_CHECKPOINT_VERSION,
dungeonId: game.activeDungeonId,
difficultyId: game.activeDifficultyId,
checkpointId: encounters.find((entry) => entry.checkpointId)?.checkpointId ?? "entrance",
spawn: game.activeSpawn,
encounters,
savedAt: Date.now(),
});
};
save();
return subscribeDungeonEncounterSnapshots(save);
}, [activeCharacterId, phase]);
return null;
}
+36
View File
@@ -31,6 +31,14 @@ import { useGameStore } from "./store";
import { useManastormStore } from "./manastormStore";
import { advanceManastormSession } from "./manastormSession";
import { useWailingEncounterStore } from "./wailingCavernsEncounter";
import {
exportDungeonEncounterSnapshots,
importDungeonEncounterSnapshots,
} from "./dungeonEncounterRuntime";
import {
dispatchDungeonWorldObjectInteraction,
dungeonInteractionCandidateAt,
} from "./dungeonWorldInteraction";
const SYNC_INTERVAL_MS = 100;
const WORLD_INTERVAL_MS = 200;
@@ -56,6 +64,10 @@ function sharedWorld(): OnlineSharedWorld {
}),
);
return {
dungeonId: game.activeDungeonId,
encounters: game.gameMode === "dungeon"
? exportDungeonEncounterSnapshots(game.activeDungeonId)
: [],
mobs: combat.mobs,
mobTransforms,
partyMembers: party.members,
@@ -89,6 +101,10 @@ function applySharedWorld(world: OnlineSharedWorld): void {
}
if (world.wailing) useWailingEncounterStore.setState(world.wailing as never);
if (world.manastorm) useManastormStore.setState(world.manastorm as never);
const activeDungeonId = useGameStore.getState().activeDungeonId;
if (world.dungeonId === activeDungeonId && world.encounters) {
importDungeonEncounterSnapshots(activeDungeonId, world.encounters);
}
}
function eventForRelay(
@@ -140,6 +156,26 @@ function applyRelayEvent(
useCombatStore.getState().lootMob(event.targetActorId, event.occurredAt || Date.now());
return;
}
if (event.kind === "interact" && snapshot.authority && event.targetActorId) {
const source = snapshot.players.find((player) => player.accountId === event.sourceAccountId);
const candidate = source ? dungeonInteractionCandidateAt(source.position) : null;
if (candidate?.object.id === event.targetActorId) {
dispatchDungeonWorldObjectInteraction(
candidate.object,
event.sourceActorId,
event.occurredAt || Date.now(),
);
}
return;
}
if (event.kind === "party-teleport" && !snapshot.authority && event.position) {
const game = useGameStore.getState();
game.relocatePlayer({ footPosition: event.position, yaw: game.cameraYaw }, true);
for (const member of usePartyStore.getState().members) {
updatePartyRuntimePosition(member.id, ...event.position);
}
return;
}
if (event.targetActorId === localActorId) {
if (event.kind === "damage") {
const sourceLevel = useCombatStore.getState().mobs[event.sourceActorId]?.level;
+189
View File
@@ -0,0 +1,189 @@
import { describe, expect, it } from "vitest";
import { buildBossGuideCatalog, buildDungeonBossGuide, bossGuideOverrideKey } from "./bossGuide";
import { DUNGEON_DEFINITION_SCHEMA_VERSION, type DungeonDefinition } from "./dungeonTypes";
import { DUNGEON_DEFINITIONS } from "./dungeonRegistry";
import { BOSS_GUIDE_OVERRIDES } from "./bossGuideOverrides";
const fixture = {
schemaVersion: DUNGEON_DEFINITION_SCHEMA_VERSION,
id: "guide-fixture",
title: "Guide Fixture",
mapId: 1,
lfgDungeonIds: [],
expansion: "runewaker",
environmentMode: "runewaker-ros",
difficultyVariants: [{ id: "normal", difficulty: "normal", spawnMask: 1, levelRange: [1, 2] }],
defaultDifficultyId: "normal",
availability: "available",
presentation: {
location: "Test",
theme: "Test",
summary: "Test",
loadingMessage: "Test",
unchartedAreaName: "Test",
background: "#000",
fog: { color: "#000", near: 1, far: 2 },
ambientLight: { color: "#fff", intensity: 1 },
hemisphereLight: { skyColor: "#fff", groundColor: "#000", intensity: 1 },
directionalLight: { color: "#fff", intensity: 1, offset: [0, 1, 0] },
materials: { cutoutPatterns: [], cutoutNames: [], blendPatterns: [], blendNames: [], additivePatterns: [], additiveNames: [] },
map: { width: 100, height: 100, padding: 1, contours: [] },
},
assets: { visual: [], collision: [] },
transform: { position: [0, 0, 0], rotation: [0, 0, 0], scale: 1 },
bounds: { min: [0, 0, 0], max: [1, 1, 1] },
entrance: { name: "Entrance", footPosition: [0, 0, 0], forward: [0, 0, 1], yaw: 0 },
areas: [],
entities: {
herald: {
id: "herald",
name: "The Herald",
kind: "boss",
title: "Keeper of Tests",
combat: {
attacks: [
{
id: "basic-attack",
name: "Basic Attack",
delivery: "melee",
target: "primary",
school: "physical",
animation: "attack",
range: 4,
cooldownMs: 2_000,
damageMultiplier: 1,
},
{
id: "falling-test",
name: "Falling Test",
spellId: 42,
delivery: "area",
target: "random-party",
school: "arcane",
animation: "cast",
castTimeMs: 2_000,
range: 30,
cooldownMs: 12_000,
initialCooldownMs: [8_000, 10_000],
repeatCooldownMs: [12_000, 14_000],
maximumCasts: 1,
damageMultiplier: 1.2,
telegraph: { shape: "circle", anchor: "target", radius: 6, warningMs: 2_000 },
condition: { kind: "self-health-percent", minimum: 0, maximum: 50 },
},
{
id: "source-gap-sweep",
name: "Fallback Sweep",
delivery: "area",
target: "nearby-party",
school: "physical",
animation: "attack",
castTimeMs: 1_800,
range: 8,
cooldownMs: 10_000,
damageMultiplier: 1,
telegraph: { shape: "cone", anchor: "source", length: 8, angleDegrees: 90, warningMs: 1_800 },
},
],
},
visual: { primaryColor: "#000", accentColor: "#fff", scale: 1, archetype: "humanoid" },
},
sentinel: {
id: "sentinel",
name: "Silent Sentinel",
kind: "boss",
title: "The Unscripted",
combat: { attacks: [] },
visual: { primaryColor: "#000", accentColor: "#fff", scale: 1, archetype: "humanoid" },
},
},
staticSpawns: [
{ id: "herald-spawn", entityId: "herald", position: [0, 0, 0] },
{ id: "sentinel-spawn", entityId: "sentinel", position: [1, 0, 0] },
],
roamingPacks: [],
bosses: [
{ id: "herald-spawn", name: "The Herald", position: [0, 0, 0] },
{ id: "sentinel-spawn", name: "Silent Sentinel", position: [1, 0, 0] },
],
objectiveOrder: [],
navigationLinks: [],
serverGameObjects: [],
provenance: { clientBuild: "test", clientArchiveHashes: {}, databaseAdapter: "test", coordinateTransform: "test" },
validation: { status: "green", blockers: [], warnings: [] },
} as const satisfies DungeonDefinition;
describe("boss guide model", () => {
it("derives health triggers, timing, telegraph geometry, instructions, and fidelity from runtime attacks", () => {
const guide = buildDungeonBossGuide(fixture, {
[bossGuideOverrideKey("guide-fixture", "herald", "falling-test")]: {
counterplay: "Use the authored safe spot.",
},
});
const mechanic = guide.bosses[0].mechanics[0];
expect(mechanic).toMatchObject({
id: "falling-test",
trigger: "When the boss reaches 50% health",
timing: "First: 8s–10s · Repeats: 12s–14s · Cast: 2s · Maximum uses: 1",
targetAndEffect: "Arcane damage to a random party member (1.2× boss power)",
telegraph: "6m radius circle centered on the target · 2s warning",
counterplay: "Use the authored safe spot.",
fidelity: "source",
fidelityLabel: "Source backed",
});
expect(mechanic.testInstruction).toContain("cross the threshold");
expect(guide.bosses[0].mechanics[1]).toMatchObject({
fidelity: "fallback",
fidelityLabel: "HealerMan fallback",
});
});
it("omits basic attacks and preserves an honest empty entry for a boss with no specials", () => {
const guide = buildDungeonBossGuide(fixture);
expect(guide.bosses[0].mechanics.map((mechanic) => mechanic.id)).toEqual([
"falling-test",
"source-gap-sweep",
]);
expect(guide.bosses[1].mechanics).toEqual([]);
});
it("covers every registered boss objective and every non-basic runtime mechanic", () => {
const catalog = buildBossGuideCatalog(DUNGEON_DEFINITIONS, BOSS_GUIDE_OVERRIDES);
const guideByDungeon = new Map(catalog.map((guide) => [guide.id, guide]));
for (const dungeon of DUNGEON_DEFINITIONS) {
const guide = guideByDungeon.get(dungeon.id);
expect(guide, dungeon.id).toBeDefined();
expect(guide!.bosses.length, dungeon.id).toBeGreaterThanOrEqual(dungeon.bosses.length);
for (const objective of dungeon.bosses) {
const boss = guide!.bosses.find((entry) => entry.id === objective.id);
expect(boss, `${dungeon.id}:${objective.name}`).toBeDefined();
expect(boss!.entityId, `${dungeon.id}:${objective.name}`).toBeDefined();
}
for (const boss of guide!.bosses) {
if (!boss.entityId) continue;
const expectedIds = dungeon.entities[boss.entityId]?.combat?.attacks
.filter((attack) => attack.id !== "basic-attack")
.map((attack) => attack.id) ?? [];
expect(boss.mechanics.map((mechanic) => mechanic.id), `${dungeon.id}:${boss.name}`).toEqual(expectedIds);
for (const mechanic of boss.mechanics) {
expect(mechanic.trigger, `${dungeon.id}:${boss.name}:${mechanic.name}:trigger`).not.toBe("");
expect(mechanic.timing, `${dungeon.id}:${boss.name}:${mechanic.name}:timing`).not.toBe("");
expect(mechanic.telegraph, `${dungeon.id}:${boss.name}:${mechanic.name}:telegraph`).not.toBe("");
expect(mechanic.counterplay, `${dungeon.id}:${boss.name}:${mechanic.name}:counterplay`).not.toBe("");
expect(mechanic.testInstruction, `${dungeon.id}:${boss.name}:${mechanic.name}:test`).not.toBe("");
}
}
}
const archaedas = guideByDungeon.get("uldaman")!.bosses.find((boss) => boss.name === "Archaedas")!;
expect(archaedas.mechanics.find((mechanic) => mechanic.id === "archaedas-awaken-earthen-guardians"))
.toMatchObject({
fidelityLabel: "Source backed",
counterplay: "At 70% health, the tank gathers the six awakened guardians while the party clears them together.",
});
const flowingChaos = guideByDungeon.get("forsaken-abbey")!.bosses.find((boss) => boss.name === "Flowing Chaos")!;
expect(flowingChaos.mechanics[0].fidelityLabel).toBe("HealerMan fallback");
});
});
+350
View File
@@ -0,0 +1,350 @@
import type { DungeonDefinition, DungeonExpansion } from "./dungeonTypes";
import {
normalizeEnemyAttack,
type EnemyAbilityCondition,
type EnemyAbilityEffect,
type EnemyAttackDefinition,
type EnemyAttackTarget,
type EnemyAttackTelegraph,
} from "./enemyAttacks";
import type { PopulationEntityDefinition } from "./mobPopulation";
export type BossGuideFidelity = "source" | "fallback" | "authored";
export interface BossMechanicGuideOverride {
readonly trigger?: string;
readonly timing?: string;
readonly targetAndEffect?: string;
readonly telegraph?: string;
readonly counterplay?: string;
readonly testInstruction?: string;
}
export type BossMechanicGuideOverrides = Readonly<Record<string, BossMechanicGuideOverride>>;
export interface BossMechanicGuideEntry {
readonly id: string;
readonly name: string;
readonly trigger: string;
readonly timing: string;
readonly targetAndEffect: string;
readonly telegraph: string;
readonly counterplay: string;
readonly testInstruction: string;
readonly fidelity: BossGuideFidelity;
readonly fidelityLabel: string;
readonly sourceDetail: string;
}
export interface BossGuideEntry {
readonly id: string;
readonly name: string;
readonly title?: string;
readonly entityId?: string;
readonly mechanics: readonly BossMechanicGuideEntry[];
}
export interface DungeonBossGuide {
readonly id: string;
readonly title: string;
readonly expansion: DungeonExpansion;
readonly bosses: readonly BossGuideEntry[];
}
export function bossGuideOverrideKey(
dungeonId: string,
bossEntityId: string,
mechanicId: string,
): string {
return `${dungeonId}:${bossEntityId}:${mechanicId}`;
}
function seconds(milliseconds: number): string {
const value = milliseconds / 1_000;
return Number.isInteger(value) ? `${value}s` : `${value.toFixed(1)}s`;
}
function rangeSeconds(range: readonly [number, number]): string {
return range[0] === range[1]
? seconds(range[0])
: `${seconds(range[0])}–${seconds(range[1])}`;
}
function titleCase(value: string): string {
return value.replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function describeCondition(condition: EnemyAbilityCondition | undefined): string {
if (!condition || condition.kind === "in-combat") return "While the boss is in combat";
if (condition.kind === "self-health-percent") {
if (condition.minimum > 0) {
return `While the boss is between ${Math.round(condition.minimum)}% and ${Math.round(condition.maximum)}% health`;
}
return `When the boss reaches ${Math.round(condition.maximum)}% health`;
}
if (condition.kind === "friendly-missing-health") {
return `When an ally within ${condition.range}m is missing ${Math.round(condition.amount)} health`;
}
return `While encounter ${condition.encounterId} is in: ${condition.states.join(", ")}`;
}
function describeTiming(attack: EnemyAttackDefinition): string {
const parts: string[] = [];
if (attack.initialCooldownMs) parts.push(`First: ${rangeSeconds(attack.initialCooldownMs)}`);
if (attack.repeatCooldownMs) parts.push(`Repeats: ${rangeSeconds(attack.repeatCooldownMs)}`);
else if (attack.cooldownMs > 0) parts.push(`Cooldown: ${seconds(attack.cooldownMs)}`);
if ((attack.castTimeMs ?? 0) > 0) parts.push(`Cast: ${seconds(attack.castTimeMs!)}`);
if (attack.maximumCasts) parts.push(`Maximum uses: ${attack.maximumCasts}`);
return parts.join(" · ") || "Immediate encounter action";
}
const TARGET_LABELS: Readonly<Record<EnemyAttackTarget, string>> = {
primary: "the boss's current target",
"random-party": "a random party member",
"nearby-party": "party members in the affected area",
self: "the boss",
"lowest-health-friendly": "the lowest-health enemy ally",
};
function describeEffect(effect: EnemyAbilityEffect | undefined, attack: EnemyAttackDefinition): string {
const target = TARGET_LABELS[attack.target];
if (!effect || effect.kind === "damage") {
const multiplier = effect?.multiplier ?? attack.damageMultiplier;
return `${titleCase(attack.school)} damage to ${target} (${Number(multiplier.toFixed(2))}× boss power)`;
}
if (effect.kind === "heal") return `Heals ${target} for ${effect.multiplier}× boss power`;
if (effect.kind === "aura") {
const magnitude = effect.magnitude === undefined ? "" : ` at ${effect.magnitude} strength`;
return `Applies ${titleCase(effect.aura)} to ${target} for ${seconds(effect.durationMs)}${magnitude}`;
}
if (effect.kind === "target-aura") {
return `Applies ${titleCase(effect.aura)} to ${target} for ${seconds(effect.durationMs)}`;
}
if (effect.kind === "control") {
return `${titleCase(effect.mechanic)} controls ${target} for ${seconds(effect.durationMs)}`;
}
if (effect.kind === "transform") return `Transforms ${target} into ${titleCase(effect.form)}`;
if (effect.kind === "summon") return `Summons ${effect.count} add${effect.count === 1 ? "" : "s"} (creature ${effect.creatureEntry})`;
if (effect.kind === "call-for-help") return `Calls hostile allies within ${effect.radius}m into combat`;
return `Advances encounter event “${effect.eventId}”`;
}
function describeTelegraph(telegraph: EnemyAttackTelegraph | undefined, attack: EnemyAttackDefinition): string {
if (!telegraph) {
const warning = (attack.castTimeMs ?? 0) > 0 ? ` with a ${seconds(attack.castTimeMs!)} cast warning` : "";
return attack.effect?.kind === "heal"
|| attack.effect?.kind === "aura"
|| attack.effect?.kind === "transform"
|| attack.effect?.kind === "summon"
|| attack.effect?.kind === "call-for-help"
|| attack.effect?.kind === "encounter-event"
? `No harmful ground area${warning}`
: `Targeted mechanic; no ground area${warning}`;
}
const anchor = telegraph.anchor === "source" ? "centered on the boss" : "centered on the target";
if (telegraph.shape === "circle") {
return `${telegraph.radius}m radius circle ${anchor} · ${seconds(telegraph.warningMs)} warning`;
}
if (telegraph.shape === "cone") {
return `${telegraph.length}m, ${telegraph.angleDegrees}° frontal cone · ${seconds(telegraph.warningMs)} warning`;
}
return `${telegraph.length}m × ${telegraph.width}m line from the boss · ${seconds(telegraph.warningMs)} warning`;
}
function effectFollowup(effect: EnemyAbilityEffect | undefined): string {
if (!effect) return "Heal the affected party members after it resolves.";
if (effect.kind === "control") return "Stabilize the controlled player and remove the control if an available ability allows it.";
if (effect.kind === "target-aura") return "Watch the affected player's debuff and respond with healing or removal if the effect permits it.";
if (effect.kind === "summon") return "Swap damage to the summoned adds and keep them away from vulnerable party members.";
if (effect.kind === "heal") return "Interrupt the heal when possible; otherwise plan for the boss to recover health.";
if (effect.kind === "call-for-help") return "Clear or control nearby enemies before this ability resolves.";
if (effect.kind === "transform") return "Expect the boss's behavior to change after the new form appears.";
if (effect.kind === "encounter-event") return "Watch for the encounter-state change and handle the newly activated objective.";
if (effect.kind === "aura") return "Adjust damage and healing for the aura while it remains active.";
return "Heal the affected party members after it resolves.";
}
function describeCounterplay(attack: EnemyAttackDefinition): string {
const telegraph = attack.telegraph;
let movement = "";
if (telegraph?.shape === "circle") {
movement = telegraph.anchor === "target"
? "Move out of the marked circle; if it follows a player, carry it away from the group. "
: "Leave the circle around the boss before the warning expires. ";
} else if (telegraph?.shape === "cone") {
movement = "Move beside or behind the boss; the tank should aim the frontal away from the party. ";
} else if (telegraph?.shape === "line") {
movement = "Sidestep the marked line before the warning expires. ";
} else if (attack.effect?.kind !== "heal" && attack.interruptible !== false && (attack.castTimeMs ?? 0) > 0) {
movement = "Interrupt the cast when possible. ";
}
return `${movement}${effectFollowup(attack.effect)}`.trim();
}
function describeTestInstruction(attack: EnemyAttackDefinition): string {
const checks: string[] = [];
if (attack.condition?.kind === "self-health-percent") {
checks.push(`stop just above ${Math.round(attack.condition.maximum)}% health, then cross the threshold`);
} else if (attack.initialCooldownMs) {
checks.push(attack.initialCooldownMs[1] === 0
? "engage the boss and watch the opening cast"
: `stay in combat through the ${rangeSeconds(attack.initialCooldownMs)} first-use window`);
} else {
checks.push("stay in combat until the mechanic becomes ready");
}
if (attack.telegraph) {
checks.push(`confirm the ${attack.telegraph.shape} appears for ${seconds(attack.telegraph.warningMs)}`);
} else if ((attack.castTimeMs ?? 0) > 0) {
checks.push(`confirm the ${seconds(attack.castTimeMs!)} cast warning appears`);
}
checks.push("verify the stated target/effect when it resolves");
return `${checks[0]}; ${checks.slice(1).join(", then ")}.`;
}
function mechanicFidelity(
dungeon: DungeonDefinition,
attack: EnemyAttackDefinition,
): Pick<BossMechanicGuideEntry, "fidelity" | "fidelityLabel" | "sourceDetail"> {
if (/source-gap|fallback/i.test(attack.id)) {
return {
fidelity: "fallback",
fidelityLabel: "HealerMan fallback",
sourceDetail: "Playable behavior is supplied because the original encounter evidence is incomplete.",
};
}
if (attack.source) {
return {
fidelity: "source",
fidelityLabel: "Source backed",
sourceDetail: attack.source.comment || `Server script row ${attack.source.rowId}`,
};
}
if (dungeon.expansion === "runewaker") {
return {
fidelity: "source",
fidelityLabel: "Source backed",
sourceDetail: attack.spellId
? `RuneWaker spell ${attack.spellId}`
: "RuneWaker spell or encounter-script import",
};
}
if (/azeroth|trinity/i.test(dungeon.provenance.databaseAdapter)) {
return {
fidelity: "source",
fidelityLabel: "Source backed",
sourceDetail: attack.spellId
? `AzerothCore encounter import · spell ${attack.spellId}`
: "AzerothCore encounter-script import",
};
}
return {
fidelity: "authored",
fidelityLabel: "Gameplay authored",
sourceDetail: attack.spellId ? `Spell ${attack.spellId}` : "HealerMan encounter definition",
};
}
function mechanicGuide(
dungeon: DungeonDefinition,
entityId: string,
sourceAttack: EnemyAttackDefinition,
overrides: BossMechanicGuideOverrides,
): BossMechanicGuideEntry {
const attack = normalizeEnemyAttack(sourceAttack);
const override = overrides[bossGuideOverrideKey(dungeon.id, entityId, attack.id)] ?? {};
return {
id: attack.id,
name: attack.name,
trigger: override.trigger ?? describeCondition(attack.condition),
timing: override.timing ?? describeTiming(attack),
targetAndEffect: override.targetAndEffect ?? describeEffect(attack.effect, attack),
telegraph: override.telegraph ?? describeTelegraph(attack.telegraph, attack),
counterplay: override.counterplay ?? describeCounterplay(attack),
testInstruction: override.testInstruction ?? describeTestInstruction(attack),
...mechanicFidelity(dungeon, attack),
};
}
function normalizedName(value: string): string {
return value.toLowerCase().replace(/\s+\d+$/, "").replace(/[^a-z0-9]+/g, "");
}
function bossEntityForObjective(
dungeon: DungeonDefinition,
objectiveId: string,
objectiveName: string,
): readonly [string, PopulationEntityDefinition] | undefined {
const staticSpawn = dungeon.staticSpawns.find((spawn) => spawn.id === objectiveId);
if (staticSpawn) {
const entity = dungeon.entities[staticSpawn.entityId];
if (entity) return [staticSpawn.entityId, entity];
}
for (const pack of dungeon.roamingPacks) {
const member = pack.members.find((candidate) => (
`${pack.id}:${candidate.id}` === objectiveId || candidate.id === objectiveId
));
if (member) {
const entity = dungeon.entities[member.entityId];
if (entity) return [member.entityId, entity];
}
}
const wanted = normalizedName(objectiveName);
return Object.entries(dungeon.entities).find(([, entity]) => (
entity.kind === "boss"
&& (normalizedName(entity.name) === wanted || normalizedName(entity.title) === wanted)
));
}
function bossGuide(
dungeon: DungeonDefinition,
bossId: string,
bossName: string,
resolved: readonly [string, PopulationEntityDefinition] | undefined,
overrides: BossMechanicGuideOverrides,
): BossGuideEntry {
const [entityId, entity] = resolved ?? [];
const mechanics = entityId && entity?.combat
? entity.combat.attacks
.filter((attack) => attack.id !== "basic-attack")
.map((attack) => mechanicGuide(dungeon, entityId, attack, overrides))
: [];
return {
id: bossId,
name: bossName,
...(entity?.kind === "boss" ? { title: entity.title } : {}),
...(entityId ? { entityId } : {}),
mechanics,
};
}
export function buildDungeonBossGuide(
dungeon: DungeonDefinition,
overrides: BossMechanicGuideOverrides = {},
): DungeonBossGuide {
const representedEntityIds = new Set<string>();
const objectiveBosses = dungeon.bosses.map((objective) => {
const resolved = bossEntityForObjective(dungeon, objective.id, objective.name);
if (resolved) representedEntityIds.add(resolved[0]);
return bossGuide(dungeon, objective.id, objective.name, resolved, overrides);
});
const unplacedBosses = Object.entries(dungeon.entities)
.filter(([entityId, entity]) => entity.kind === "boss" && !representedEntityIds.has(entityId))
.map(([entityId, entity]) => bossGuide(dungeon, `entity:${entityId}`, entity.name, [entityId, entity], overrides));
return {
id: dungeon.id,
title: dungeon.title,
expansion: dungeon.expansion,
bosses: [...objectiveBosses, ...unplacedBosses],
};
}
export function buildBossGuideCatalog(
dungeons: readonly DungeonDefinition[],
overrides: BossMechanicGuideOverrides = {},
): readonly DungeonBossGuide[] {
return dungeons
.map((dungeon) => buildDungeonBossGuide(dungeon, overrides))
.sort((left, right) => left.title.localeCompare(right.title));
}
+18
View File
@@ -0,0 +1,18 @@
import type { BossMechanicGuideOverrides } from "./bossGuide";
/**
* Optional wording hooks for mechanics whose encounter-specific response cannot
* be inferred from the reusable attack/effect schema. Unlisted mechanics use
* the generic guide derivation and appear automatically.
*/
export const BOSS_GUIDE_OVERRIDES: BossMechanicGuideOverrides = Object.freeze({
"uldaman:entry-2748:archaedas-awaken-earthen-custodian": {
counterplay: "Pick up the awakened custodian immediately and keep it away from the healer.",
},
"uldaman:entry-2748:archaedas-awaken-earthen-guardians": {
counterplay: "At 70% health, the tank gathers the six awakened guardians while the party clears them together.",
},
"uldaman:entry-2748:archaedas-awaken-vault-warders": {
counterplay: "At 40% health, swap to the two vault warders and keep their pressure off the healer.",
},
});
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { DUNGEON_DEFINITIONS } from "./dungeonRegistry";
import { normalizeEnemyAttack } from "./enemyAttacks";
const NON_HARMFUL_EFFECTS = new Set([
"heal",
"aura",
"transform",
"summon",
"call-for-help",
"encounter-event",
]);
describe("boss mechanic telegraph coverage", () => {
it("uses the same warned geometry for every harmful boss area mechanic", () => {
let bosses = 0;
let mechanics = 0;
for (const dungeon of DUNGEON_DEFINITIONS) {
for (const entity of Object.values(dungeon.entities)) {
if (entity.kind !== "boss" || !entity.combat) continue;
bosses += 1;
for (const rawAttack of entity.combat.attacks) {
const attack = normalizeEnemyAttack(rawAttack);
if (attack.id === "basic-attack") continue;
mechanics += 1;
const harmful = !attack.effect || !NON_HARMFUL_EFFECTS.has(attack.effect.kind);
if (harmful && (attack.delivery === "area" || attack.radius !== undefined)) {
expect(attack.telegraph, `${dungeon.id}:${entity.name}:${attack.name}`).toBeDefined();
}
if (attack.telegraph) {
expect(attack.castTimeMs ?? 0, `${dungeon.id}:${entity.name}:${attack.name}`)
.toBeGreaterThanOrEqual(attack.telegraph.warningMs);
}
}
}
}
expect(bosses).toBeGreaterThan(100);
expect(mechanics).toBeGreaterThan(300);
});
});
+20 -8
View File
@@ -128,6 +128,7 @@ import {
type TriggeredAuraProc,
} from "./combatAuras";
import { rollBossLoot } from "./lootCatalog";
import { adaptRunewakerEnemyDamage } from "./runewakerCombatFormula";
import { normalizeInventory, type InventoryItem } from "./lootTypes";
import {
PLAYER_EQUIPMENT_OWNER_ID,
@@ -185,7 +186,10 @@ import {
type AzerothCoreMechanic,
type AzerothCoreSchool,
} from "./azerothCoreReference";
import { useWailingEncounterStore } from "./wailingCavernsEncounter";
import {
dispatchDungeonEncounterSignal,
dungeonEncounterStateMatches,
} from "./dungeonEncounterRuntime";
import {
PLAYER_AGGRO_ID,
addActorThreat,
@@ -325,6 +329,7 @@ export interface MobCombatState {
readonly name: string;
readonly serverEntry: number | null;
readonly summonedBy?: string;
readonly spawnGroupId?: string;
readonly boss: boolean;
readonly lootSourceId: string | null;
readonly level: number;
@@ -1762,8 +1767,7 @@ function enemyAbilityConditionMet(
return percent >= condition.minimum && percent <= condition.maximum;
}
if (condition.kind === "encounter-state") {
if (condition.encounterId !== "wailing-caverns:naralex") return false;
return condition.states.includes(useWailingEncounterStore.getState().phase);
return dungeonEncounterStateMatches(condition.encounterId, condition.states);
}
const origin = getMobPosition(mob.id);
if (!origin) return false;
@@ -1803,6 +1807,7 @@ function summonedMobCombatState(
creatureEntry: number,
targetActorId: AggroActorId,
position: CombatPosition,
spawnGroupId?: string,
): MobCombatState {
const stats = defaultMobStats(summoner.level, false, creatureEntry);
const template = creatureTemplateReference(creatureEntry);
@@ -1817,6 +1822,7 @@ function summonedMobCombatState(
name: template?.name ?? `Summoned Creature ${creatureEntry}`,
serverEntry: creatureEntry,
summonedBy: summoner.id,
...(spawnGroupId ? { spawnGroupId } : {}),
boss: false,
lootSourceId: null,
level: summoner.level,
@@ -6540,7 +6546,13 @@ export const useCombatStore = create<CombatState>((set, get) => ({
actorPosition(runtimeState, presentationTargetId),
);
if (effect.kind === "damage") {
const attackDamage = Math.max(0, Math.round(mob.attackDamage * effect.multiplier));
const baselineDamage = Math.max(0, Math.round(mob.attackDamage * effect.multiplier));
const attackDamage = attack.runewakerFormula
? adaptRunewakerEnemyDamage({
formula: attack.runewakerFormula,
baselineDamage: mob.attackDamage,
}).amount
: baselineDamage;
for (const actorId of targets) {
incomingHits.push({
actorId,
@@ -6668,7 +6680,8 @@ export const useCombatStore = create<CombatState>((set, get) => ({
} else if (effect.kind === "summon") {
const castNumber = (abilityCastCounts[attack.id] ?? 0) + 1;
for (let summonIndex = 0; summonIndex < effect.count; summonIndex += 1) {
const summonId = `${id}:${attack.id}:summon:${castNumber}:${summonIndex + 1}`;
const summonNamespace = effect.entityId?.trim() || `${id}:${attack.id}:summon`;
const summonId = `${summonNamespace}:${castNumber}:${summonIndex + 1}`;
if (mobs[summonId]) continue;
const angle = summonIndex * 2.399963229728653;
const summonPosition: CombatPosition = [
@@ -6683,12 +6696,11 @@ export const useCombatStore = create<CombatState>((set, get) => ({
effect.creatureEntry,
resolution.targetActorId,
summonPosition,
effect.spawnGroupId,
);
}
} else if (effect.kind === "encounter-event") {
if (effect.eventId === "wailing-caverns:naralex-start") {
useWailingEncounterStore.getState().start(now);
}
dispatchDungeonEncounterSignal(effect.eventId, now);
}
attackRevision += 1;
abilityCastCounts[attack.id] = (abilityCastCounts[attack.id] ?? 0) + 1;
+2 -2
View File
@@ -1,6 +1,6 @@
import { useCombatStore } from "./combatStore";
import { useGameStore } from "./store";
import { useWailingEncounterStore } from "./wailingCavernsEncounter";
import { resetDungeonEncounters } from "./dungeonEncounterRuntime";
/**
* Atomically returns a defeated player to the dungeon entrance and restores
@@ -15,7 +15,7 @@ export function respawnDefeatedPlayer(): boolean {
document.exitPointerLock();
}
useGameStore.getState().resetAtEntrance();
useWailingEncounterStore.getState().reset();
resetDungeonEncounters("dungeon", useGameStore.getState().activeDungeonId);
const entrance = useGameStore.getState().playerPosition;
combat.resetEncounter();
useCombatStore.getState().setPlayerPosition(entrance);
+273
View File
@@ -0,0 +1,273 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
activateDungeonEncounterDefinitions,
activeDungeonEncounterRevision,
createDungeonEncounterSnapshot,
dispatchActiveDungeonInteraction,
dispatchDungeonEncounterSignal,
drainDungeonEncounterEffects,
dungeonEncounterSnapshot,
exportDungeonEncounterCheckpointSnapshots,
exportDungeonEncounterSnapshots,
importDungeonEncounterSnapshots,
reduceDungeonEncounter,
registerDungeonEncounterEffectHandler,
resetDungeonEncounterSnapshot,
subscribeDungeonEncounterSnapshots,
validateDungeonEncounterDefinition,
} from "./dungeonEncounterRuntime";
import type { DungeonEncounterDefinition } from "./dungeonEncounterTypes";
import {
WAILING_NARALEX_ENCOUNTER,
useWailingEncounterStore,
} from "./wailingCavernsEncounter";
const REDUCER_DEFINITION = {
id: "test:reducer",
dungeonId: "test-dungeon",
initialPhase: "idle",
initialState: {
flags: { temporary: false, persistent: false },
counters: { attempts: 0 },
worldObjectStates: { gate: "closed" },
},
resetPolicy: { bossWipePhase: "idle", checkpointPhase: "checkpoint-ready" },
rules: [
{
id: "pull-lever",
trigger: { kind: "interact", objectId: "lever" },
guards: [{ kind: "phase", phases: ["idle"] }],
actions: [
{ id: "enter-active", kind: "set-phase", phase: "active" },
{ id: "mark-temporary", kind: "set-flag", flagId: "temporary", value: true },
{ id: "mark-persistent", kind: "set-flag", flagId: "persistent", value: true, persistence: "checkpoint" },
{ id: "open-gate", kind: "set-world-object-state", objectId: "gate", state: "open", persistence: "checkpoint" },
{ id: "enable-route", kind: "set-transition-active", transitionId: "gate-route", active: true, persistence: "checkpoint" },
{ id: "spawn-wave", kind: "activate-spawn-group", spawnGroupId: "wave-one" },
{ id: "save-checkpoint", kind: "set-checkpoint", checkpointId: "lever", persistence: "checkpoint" },
{ id: "move-party", kind: "teleport-party", destination: [1, 2, 3] },
],
},
{
id: "count-active-ticks",
trigger: { kind: "tick" },
guards: [{ kind: "phase", phases: ["active"] }],
once: false,
actions: [{ id: "count-attempt", kind: "increment-counter", counterId: "attempts" }],
},
{
id: "finish-after-delay",
trigger: { kind: "elapsed", afterMs: 5_000 },
guards: [
{ kind: "phase", phases: ["active"] },
{ kind: "flag", flagId: "persistent", equals: true },
],
actions: [
{ id: "finish", kind: "set-phase", phase: "complete", persistence: "run" },
{ id: "complete-objective", kind: "complete-objective", objectiveId: "lever-boss", persistence: "run" },
],
},
],
} as const satisfies DungeonEncounterDefinition;
const MANAGER_A = {
id: "test:manager-a",
dungeonId: "manager-a",
initialPhase: "idle",
rules: [{
id: "shared-signal",
trigger: { kind: "signal", signalId: "test:shared" },
once: false,
actions: [{ id: "increment-a", kind: "increment-counter", counterId: "signals" }],
}],
} as const satisfies DungeonEncounterDefinition;
const MANAGER_B = {
id: "test:manager-b",
dungeonId: "manager-b",
initialPhase: "idle",
initialState: { worldObjectStates: { switch: "ready" } },
rules: [
{
id: "shared-signal",
trigger: { kind: "signal", signalId: "test:shared" },
once: false,
actions: [{ id: "increment-b", kind: "increment-counter", counterId: "signals" }],
},
{
id: "use-switch",
trigger: { kind: "interact", objectId: "switch" },
guards: [{ kind: "world-object-state", objectId: "switch", states: ["ready"] }],
actions: [
{ id: "open-switch", kind: "set-world-object-state", objectId: "switch", state: "open", persistence: "checkpoint" },
{ id: "start-group", kind: "activate-spawn-group", spawnGroupId: "switch-wave" },
{ id: "switch-cue", kind: "emit-signal", signalId: "test:switch-opened" },
],
},
],
} as const satisfies DungeonEncounterDefinition;
afterEach(() => {
activateDungeonEncounterDefinitions("wailing-caverns", [WAILING_NARALEX_ENCOUNTER]);
useWailingEncounterStore.getState().reset();
drainDungeonEncounterEffects("manager-a");
drainDungeonEncounterEffects("manager-b");
});
describe("dungeon encounter reducer", () => {
it("applies stable reusable actions deterministically and remains JSON serializable", () => {
const initial = createDungeonEncounterSnapshot(REDUCER_DEFINITION, 100);
const event = { kind: "interact", objectId: "lever", actorId: "player", now: 1_000 } as const;
const first = reduceDungeonEncounter(REDUCER_DEFINITION, initial, event);
const second = reduceDungeonEncounter(REDUCER_DEFINITION, initial, event);
expect(second).toEqual(first);
expect(first.snapshot).toMatchObject({
phase: "active",
phaseStartedAt: 1_000,
flags: { temporary: true, persistent: true },
worldObjectStates: { gate: "open" },
transitionStates: { "gate-route": true },
activeSpawnGroupIds: ["wave-one"],
checkpointId: "lever",
revision: 1,
});
expect(first.effects).toEqual(expect.arrayContaining([
{ kind: "world-object-state", actionId: "open-gate", objectId: "gate", state: "open" },
{ kind: "spawn-group", actionId: "spawn-wave", spawnGroupId: "wave-one" },
{ kind: "party-teleport", actionId: "move-party", destination: [1, 2, 3] },
]));
expect(JSON.parse(JSON.stringify(first.snapshot))).toEqual(first.snapshot);
const duplicate = reduceDungeonEncounter(REDUCER_DEFINITION, first.snapshot, event);
expect(duplicate.snapshot).toBe(first.snapshot);
expect(duplicate.effects).toEqual([]);
});
it("supports repeatable rules, elapsed transitions, and scoped resets", () => {
const started = reduceDungeonEncounter(
REDUCER_DEFINITION,
createDungeonEncounterSnapshot(REDUCER_DEFINITION),
{ kind: "interact", objectId: "lever", now: 1_000 },
).snapshot;
const tickOne = reduceDungeonEncounter(
REDUCER_DEFINITION,
started,
{ kind: "tick", now: 2_000 },
).snapshot;
const tickTwo = reduceDungeonEncounter(
REDUCER_DEFINITION,
tickOne,
{ kind: "tick", now: 3_000 },
).snapshot;
expect(tickTwo.counters.attempts).toBe(2);
const completed = reduceDungeonEncounter(
REDUCER_DEFINITION,
tickTwo,
{ kind: "tick", now: 6_000 },
).snapshot;
expect(completed.phase).toBe("complete");
expect(completed.completedObjectiveIds).toEqual(["lever-boss"]);
const checkpoint = resetDungeonEncounterSnapshot(
REDUCER_DEFINITION,
completed,
"checkpoint",
7_000,
);
expect(checkpoint).toMatchObject({
phase: "checkpoint-ready",
flags: { temporary: false, persistent: true },
counters: { attempts: 0 },
worldObjectStates: { gate: "open" },
transitionStates: { "gate-route": true },
activeSpawnGroupIds: [],
completedObjectiveIds: ["lever-boss"],
checkpointId: "lever",
});
expect(resetDungeonEncounterSnapshot(REDUCER_DEFINITION, completed, "dungeon")).toMatchObject({
phase: "idle",
flags: { temporary: false, persistent: false },
worldObjectStates: { gate: "closed" },
completedObjectiveIds: [],
checkpointId: null,
});
});
it("rejects duplicate action ids at definition validation", () => {
expect(() => validateDungeonEncounterDefinition({
id: "test:invalid",
dungeonId: "test-dungeon",
initialPhase: "idle",
rules: [{
id: "duplicate-actions",
trigger: { kind: "tick" },
actions: [
{ id: "same", kind: "set-phase", phase: "one" },
{ id: "same", kind: "set-phase", phase: "two" },
],
}],
})).toThrow(/duplicate action id same/);
});
});
describe("active dungeon encounter manager", () => {
it("isolates shared signals to the active dungeon", () => {
activateDungeonEncounterDefinitions("manager-a", [MANAGER_A]);
dispatchDungeonEncounterSignal("test:shared", 1_000);
expect(dungeonEncounterSnapshot(MANAGER_A.id)?.counters.signals).toBe(1);
activateDungeonEncounterDefinitions("manager-b", [MANAGER_B]);
dispatchDungeonEncounterSignal("test:shared", 2_000);
expect(dungeonEncounterSnapshot(MANAGER_A.id)?.counters.signals).toBe(1);
expect(dungeonEncounterSnapshot(MANAGER_B.id)?.counters.signals).toBe(1);
});
it("dispatches interactions, exposes effects, snapshots, hydration, and subscriptions", () => {
activateDungeonEncounterDefinitions("manager-b", [MANAGER_B]);
const subscriber = vi.fn();
const effectHandler = vi.fn();
const unsubscribe = subscribeDungeonEncounterSnapshots(subscriber);
const unregisterEffect = registerDungeonEncounterEffectHandler(effectHandler);
const revisionBefore = activeDungeonEncounterRevision();
const effects = dispatchActiveDungeonInteraction("switch", "player", 3_000);
expect(effects).toContainEqual({
kind: "signal",
actionId: "switch-cue",
signalId: "test:switch-opened",
});
expect(effectHandler).toHaveBeenCalled();
expect(subscriber).toHaveBeenCalled();
expect(activeDungeonEncounterRevision()).toBeGreaterThan(revisionBefore);
expect(drainDungeonEncounterEffects("manager-b")).toHaveLength(3);
expect(drainDungeonEncounterEffects("manager-b")).toEqual([]);
const exported = exportDungeonEncounterSnapshots("manager-b");
expect(exported).toHaveLength(1);
expect(exported[0]).toMatchObject({
encounterId: MANAGER_B.id,
worldObjectStates: { switch: "open" },
activeSpawnGroupIds: ["switch-wave"],
});
const checkpoint = exportDungeonEncounterCheckpointSnapshots("manager-b", 4_000);
expect(checkpoint[0]).toMatchObject({
worldObjectStates: { switch: "open" },
activeSpawnGroupIds: [],
timers: {},
});
expect(exportDungeonEncounterSnapshots("manager-b")[0].activeSpawnGroupIds).toEqual(["switch-wave"]);
const hydrated = {
...exported[0],
phase: "restored",
revision: exported[0].revision + 1,
};
expect(importDungeonEncounterSnapshots("manager-b", [hydrated])).toEqual([MANAGER_B.id]);
expect(dungeonEncounterSnapshot(MANAGER_B.id)?.phase).toBe("restored");
unregisterEffect();
unsubscribe();
});
});
+600
View File
@@ -0,0 +1,600 @@
import {
DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION,
type DungeonEncounterAction,
type DungeonEncounterDefinition,
type DungeonEncounterEffect,
type DungeonEncounterEffectEnvelope,
type DungeonEncounterEvent,
type DungeonEncounterGuard,
type DungeonEncounterReduction,
type DungeonEncounterResetScope,
type DungeonEncounterRule,
type DungeonEncounterSnapshot,
} from "./dungeonEncounterTypes";
function finiteTime(value: number): number {
return Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
}
function unique(values: readonly string[]): readonly string[] {
return Object.freeze([...new Set(values)].sort());
}
export function createDungeonEncounterSnapshot(
definition: DungeonEncounterDefinition,
now = 0,
revision = 0,
): DungeonEncounterSnapshot {
return Object.freeze({
schemaVersion: DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION,
encounterId: definition.id,
phase: definition.initialPhase,
phaseStartedAt: finiteTime(now),
flags: Object.freeze({ ...definition.initialState?.flags }),
counters: Object.freeze({ ...definition.initialState?.counters }),
timers: Object.freeze({}),
worldObjectStates: Object.freeze({ ...definition.initialState?.worldObjectStates }),
transitionStates: Object.freeze({ ...definition.initialState?.transitionStates }),
activeSpawnGroupIds: Object.freeze([]),
completedObjectiveIds: Object.freeze([]),
consumedActionIds: Object.freeze([]),
checkpointId: null,
revision,
});
}
function validateStableId(value: string, label: string): void {
if (!value.trim() || !/^[a-z0-9][a-z0-9:._-]*$/i.test(value)) {
throw new Error(`${label} must have a stable non-empty id: ${JSON.stringify(value)}`);
}
}
export function validateDungeonEncounterDefinition(definition: DungeonEncounterDefinition): void {
validateStableId(definition.id, "Encounter");
validateStableId(definition.dungeonId, `Encounter ${definition.id} dungeon`);
validateStableId(definition.initialPhase, `Encounter ${definition.id} initial phase`);
const ruleIds = new Set<string>();
const actionIds = new Set<string>();
for (const rule of definition.rules) {
validateStableId(rule.id, `Encounter ${definition.id} rule`);
if (ruleIds.has(rule.id)) throw new Error(`Encounter ${definition.id} has duplicate rule id ${rule.id}`);
ruleIds.add(rule.id);
for (const action of rule.actions) {
validateStableId(action.id, `Encounter ${definition.id} action`);
if (actionIds.has(action.id)) {
throw new Error(`Encounter ${definition.id} has duplicate action id ${action.id}`);
}
actionIds.add(action.id);
if (action.kind === "grant-loot" && action.persistence !== "run") {
throw new Error(`Encounter ${definition.id} loot action ${action.id} must use run persistence`);
}
}
}
}
function guardMatches(snapshot: DungeonEncounterSnapshot, guard: DungeonEncounterGuard): boolean {
if (guard.kind === "phase") return guard.phases.includes(snapshot.phase);
if (guard.kind === "flag") return (snapshot.flags[guard.flagId] ?? false) === guard.equals;
if (guard.kind === "objective-complete") {
return snapshot.completedObjectiveIds.includes(guard.objectiveId) === guard.equals;
}
if (guard.kind === "spawn-group-active") {
return snapshot.activeSpawnGroupIds.includes(guard.spawnGroupId) === guard.equals;
}
if (guard.kind === "world-object-state") {
return guard.states.includes(snapshot.worldObjectStates[guard.objectId] ?? "inactive");
}
const current = snapshot.counters[guard.counterId] ?? 0;
if (guard.comparison === "equal") return current === guard.value;
if (guard.comparison === "at-least") return current >= guard.value;
return current <= guard.value;
}
function triggerMatches(
snapshot: DungeonEncounterSnapshot,
trigger: DungeonEncounterRule["trigger"],
event: DungeonEncounterEvent,
): boolean {
if (trigger.kind === "tick") return event.kind === "tick";
if (trigger.kind === "signal") return event.kind === "signal" && event.signalId === trigger.signalId;
if (trigger.kind === "entity-engaged" || trigger.kind === "entity-death") {
return event.kind === trigger.kind && event.entityId === trigger.entityId;
}
if (trigger.kind === "entity-health") {
return event.kind === "entity-health"
&& event.entityId === trigger.entityId
&& event.healthPercent <= trigger.atOrBelowPercent;
}
if (trigger.kind === "interact") return event.kind === "interact" && event.objectId === trigger.objectId;
if (trigger.kind === "elapsed") {
return event.kind === "tick" && event.now - snapshot.phaseStartedAt >= trigger.afterMs;
}
if (event.kind !== "tick") return false;
const registered = new Set(event.facts?.registeredEntityIds ?? []);
const dead = new Set(event.facts?.deadEntityIds ?? []);
const allDead = trigger.entityIds.length > 0
&& trigger.entityIds.every((id) => registered.has(id) && dead.has(id));
const timedOut = trigger.timeoutMs !== undefined
&& event.now - snapshot.phaseStartedAt >= trigger.timeoutMs;
return allDead || timedOut;
}
function ruleAlreadyConsumed(snapshot: DungeonEncounterSnapshot, rule: DungeonEncounterRule): boolean {
return (rule.once ?? true)
&& rule.actions.length > 0
&& rule.actions.every((action) => snapshot.consumedActionIds.includes(action.id));
}
function actionEffect(action: DungeonEncounterAction): DungeonEncounterEffect | null {
if (action.kind === "activate-spawn-group") {
return { kind: "spawn-group", actionId: action.id, spawnGroupId: action.spawnGroupId };
}
if (action.kind === "deactivate-spawn-group") {
return { kind: "despawn-group", actionId: action.id, spawnGroupId: action.spawnGroupId };
}
if (action.kind === "set-world-object-state") {
return { kind: "world-object-state", actionId: action.id, objectId: action.objectId, state: action.state };
}
if (action.kind === "set-transition-active") {
return { kind: "transition-state", actionId: action.id, transitionId: action.transitionId, active: action.active };
}
if (action.kind === "teleport-party") {
return { kind: "party-teleport", actionId: action.id, destination: action.destination };
}
if (action.kind === "grant-loot") {
return { kind: "loot-grant", actionId: action.id, lootTableId: action.lootTableId };
}
if (action.kind === "emit-signal") {
return { kind: "signal", actionId: action.id, signalId: action.signalId };
}
return null;
}
function applyAction(
snapshot: DungeonEncounterSnapshot,
action: DungeonEncounterAction,
now: number,
consume: boolean,
): { readonly snapshot: DungeonEncounterSnapshot; readonly effect: DungeonEncounterEffect | null } {
if (consume && snapshot.consumedActionIds.includes(action.id)) return { snapshot, effect: null };
let next = snapshot;
if (action.kind === "set-phase") {
next = { ...next, phase: action.phase, phaseStartedAt: finiteTime(now) };
} else if (action.kind === "set-flag") {
next = { ...next, flags: Object.freeze({ ...next.flags, [action.flagId]: action.value }) };
} else if (action.kind === "set-counter") {
next = { ...next, counters: Object.freeze({ ...next.counters, [action.counterId]: action.value }) };
} else if (action.kind === "increment-counter") {
next = {
...next,
counters: Object.freeze({
...next.counters,
[action.counterId]: (next.counters[action.counterId] ?? 0) + (action.amount ?? 1),
}),
};
} else if (action.kind === "start-timer") {
next = { ...next, timers: Object.freeze({ ...next.timers, [action.timerId]: finiteTime(now + action.durationMs) }) };
} else if (action.kind === "clear-timer") {
const timers = { ...next.timers };
delete timers[action.timerId];
next = { ...next, timers: Object.freeze(timers) };
} else if (action.kind === "activate-spawn-group") {
next = { ...next, activeSpawnGroupIds: unique([...next.activeSpawnGroupIds, action.spawnGroupId]) };
} else if (action.kind === "deactivate-spawn-group") {
next = { ...next, activeSpawnGroupIds: Object.freeze(next.activeSpawnGroupIds.filter((id) => id !== action.spawnGroupId)) };
} else if (action.kind === "set-world-object-state") {
next = { ...next, worldObjectStates: Object.freeze({ ...next.worldObjectStates, [action.objectId]: action.state }) };
} else if (action.kind === "set-transition-active") {
next = { ...next, transitionStates: Object.freeze({ ...next.transitionStates, [action.transitionId]: action.active }) };
} else if (action.kind === "complete-objective") {
next = { ...next, completedObjectiveIds: unique([...next.completedObjectiveIds, action.objectiveId]) };
} else if (action.kind === "set-checkpoint") {
next = { ...next, checkpointId: action.checkpointId };
}
return {
snapshot: consume
? { ...next, consumedActionIds: unique([...next.consumedActionIds, action.id]) }
: next,
effect: actionEffect(action),
};
}
export function reduceDungeonEncounter(
definition: DungeonEncounterDefinition,
snapshot: DungeonEncounterSnapshot,
event: DungeonEncounterEvent,
): DungeonEncounterReduction {
if (snapshot.encounterId !== definition.id) {
throw new Error(`Snapshot ${snapshot.encounterId} does not belong to encounter ${definition.id}`);
}
let next = snapshot;
const effects: DungeonEncounterEffect[] = [];
const firedRuleIds: string[] = [];
const rules = definition.rules
.map((rule, index) => ({ rule, index }))
.sort((left, right) => (right.rule.priority ?? 0) - (left.rule.priority ?? 0) || left.index - right.index);
for (const { rule } of rules) {
if (ruleAlreadyConsumed(next, rule)) continue;
if (!triggerMatches(next, rule.trigger, event)) continue;
if (!rule.guards?.every((guard) => guardMatches(next, guard)) && rule.guards?.length) continue;
let fired = false;
for (const action of rule.actions) {
const applied = applyAction(next, action, event.now, rule.once ?? true);
if (applied.snapshot !== next) fired = true;
next = applied.snapshot;
if (applied.effect) effects.push(applied.effect);
}
if (fired || rule.actions.length === 0) firedRuleIds.push(rule.id);
}
if (next === snapshot) return { snapshot, effects: Object.freeze(effects), firedRuleIds: Object.freeze(firedRuleIds) };
return {
snapshot: Object.freeze({ ...next, revision: snapshot.revision + 1 }),
effects: Object.freeze(effects),
firedRuleIds: Object.freeze(firedRuleIds),
};
}
function retainedActionIds(
definition: DungeonEncounterDefinition,
snapshot: DungeonEncounterSnapshot,
scope: DungeonEncounterResetScope,
): readonly string[] {
if (scope === "dungeon") return [];
const retained = new Set(definition.rules.flatMap((rule) => rule.actions)
.filter((action) => action.persistence === "checkpoint" || action.persistence === "run")
.map((action) => action.id));
return Object.freeze(snapshot.consumedActionIds.filter((id) => retained.has(id)));
}
function retainedStateKeys(definition: DungeonEncounterDefinition): {
readonly flags: ReadonlySet<string>;
readonly counters: ReadonlySet<string>;
readonly worldObjects: ReadonlySet<string>;
readonly transitions: ReadonlySet<string>;
readonly objectives: ReadonlySet<string>;
readonly checkpoints: boolean;
} {
const actions = definition.rules.flatMap((rule) => rule.actions)
.filter((action) => action.persistence === "checkpoint" || action.persistence === "run");
return {
flags: new Set(actions.filter((action) => action.kind === "set-flag").map((action) => action.flagId)),
counters: new Set(actions
.filter((action) => action.kind === "set-counter" || action.kind === "increment-counter")
.map((action) => action.counterId)),
worldObjects: new Set(actions
.filter((action) => action.kind === "set-world-object-state")
.map((action) => action.objectId)),
transitions: new Set(actions
.filter((action) => action.kind === "set-transition-active")
.map((action) => action.transitionId)),
objectives: new Set(actions
.filter((action) => action.kind === "complete-objective")
.map((action) => action.objectiveId)),
checkpoints: actions.some((action) => action.kind === "set-checkpoint"),
};
}
function retainedRecord<T>(
initial: Readonly<Record<string, T>>,
current: Readonly<Record<string, T>>,
keys: ReadonlySet<string>,
): Readonly<Record<string, T>> {
const retained: Record<string, T> = { ...initial };
for (const key of keys) {
if (current[key] !== undefined) retained[key] = current[key];
}
return Object.freeze(retained);
}
export function resetDungeonEncounterSnapshot(
definition: DungeonEncounterDefinition,
snapshot: DungeonEncounterSnapshot,
scope: DungeonEncounterResetScope,
now = 0,
): DungeonEncounterSnapshot {
const initial = createDungeonEncounterSnapshot(definition, now, snapshot.revision + 1);
if (scope === "dungeon") return initial;
const phase = scope === "boss-wipe"
? definition.resetPolicy?.bossWipePhase ?? definition.initialPhase
: definition.resetPolicy?.checkpointPhase ?? definition.initialPhase;
const retained = retainedStateKeys(definition);
return Object.freeze({
...initial,
phase,
phaseStartedAt: finiteTime(now),
flags: retainedRecord(initial.flags, snapshot.flags, retained.flags),
counters: retainedRecord(initial.counters, snapshot.counters, retained.counters),
worldObjectStates: retainedRecord(
initial.worldObjectStates,
snapshot.worldObjectStates,
retained.worldObjects,
),
transitionStates: retainedRecord(
initial.transitionStates,
snapshot.transitionStates,
retained.transitions,
),
completedObjectiveIds: Object.freeze(snapshot.completedObjectiveIds
.filter((id) => retained.objectives.has(id))),
consumedActionIds: retainedActionIds(definition, snapshot, scope),
checkpointId: retained.checkpoints ? snapshot.checkpointId : null,
});
}
export interface DungeonEncounterProvider {
readonly definition: DungeonEncounterDefinition;
readonly encounterId: string;
readonly dungeonId: string;
readonly getSnapshot: () => DungeonEncounterSnapshot;
readonly dispatch: (event: DungeonEncounterEvent) => readonly DungeonEncounterEffect[];
readonly reset: (scope: DungeonEncounterResetScope, now?: number) => void;
/** Hydrates authoritative online/save state without involving renderer state. */
readonly replaceSnapshot?: (snapshot: DungeonEncounterSnapshot) => void;
}
const providers = new Map<string, DungeonEncounterProvider>();
const pendingEffects: DungeonEncounterEffectEnvelope[] = [];
const effectHandlers = new Set<(value: DungeonEncounterEffectEnvelope) => void>();
let activeDungeonId: string | null = null;
function queueProviderEffects(
provider: DungeonEncounterProvider,
effects: readonly DungeonEncounterEffect[],
): readonly DungeonEncounterEffect[] {
for (const effect of effects) {
const value = Object.freeze({
dungeonId: provider.dungeonId,
encounterId: provider.encounterId,
effect,
});
pendingEffects.push(value);
for (const handler of effectHandlers) handler(value);
}
return effects;
}
export function registerDungeonEncounterProvider(provider: DungeonEncounterProvider): () => void {
validateDungeonEncounterDefinition(provider.definition);
if (provider.definition.id !== provider.encounterId || provider.definition.dungeonId !== provider.dungeonId) {
throw new Error(`Encounter provider ${provider.encounterId} does not match its definition`);
}
const current = providers.get(provider.encounterId);
if (current && current !== provider) throw new Error(`Duplicate encounter provider ${provider.encounterId}`);
providers.set(provider.encounterId, provider);
return () => {
if (providers.get(provider.encounterId) === provider) providers.delete(provider.encounterId);
};
}
export function dungeonEncounterSnapshot(encounterId: string): DungeonEncounterSnapshot | null {
return providers.get(encounterId)?.getSnapshot() ?? null;
}
export function dungeonEncounterStateMatches(encounterId: string, states: readonly string[]): boolean {
const snapshot = dungeonEncounterSnapshot(encounterId);
return Boolean(snapshot && states.includes(snapshot.phase));
}
export function dispatchDungeonEncounterEvent(
encounterId: string,
event: DungeonEncounterEvent,
): readonly DungeonEncounterEffect[] {
const provider = providers.get(encounterId);
if (!provider) return [];
const effects = queueProviderEffects(provider, provider.dispatch(event));
notifyEncounterSubscribers();
return effects;
}
export function dispatchDungeonEncounterSignal(signalId: string, now: number): readonly DungeonEncounterEffect[] {
const effects: DungeonEncounterEffect[] = [];
for (const provider of providers.values()) {
if (activeDungeonId && provider.dungeonId !== activeDungeonId) continue;
effects.push(...queueProviderEffects(provider, provider.dispatch({ kind: "signal", signalId, now })));
}
notifyEncounterSubscribers();
return Object.freeze(effects);
}
export function dispatchActiveDungeonInteraction(
objectId: string,
actorId?: string,
now = Date.now(),
): readonly DungeonEncounterEffect[] {
if (!activeDungeonId) return [];
const effects: DungeonEncounterEffect[] = [];
for (const provider of providers.values()) {
if (provider.dungeonId !== activeDungeonId) continue;
effects.push(...queueProviderEffects(provider, provider.dispatch({
kind: "interact",
objectId,
...(actorId ? { actorId } : {}),
now,
})));
}
notifyEncounterSubscribers();
return Object.freeze(effects);
}
export function advanceDungeonEncounters(
dungeonId: string,
event: Extract<DungeonEncounterEvent, { readonly kind: "tick" }>,
): readonly DungeonEncounterEffect[] {
const effects: DungeonEncounterEffect[] = [];
for (const provider of providers.values()) {
if (provider.dungeonId === dungeonId) {
effects.push(...queueProviderEffects(provider, provider.dispatch(event)));
}
}
notifyEncounterSubscribers();
return Object.freeze(effects);
}
export function resetDungeonEncounters(
scope: DungeonEncounterResetScope,
dungeonId?: string,
now = 0,
): void {
for (const provider of providers.values()) {
if (!dungeonId || provider.dungeonId === dungeonId) provider.reset(scope, now);
}
notifyEncounterSubscribers();
}
/**
* Makes every definition in the active dungeon executable. Authored adapters
* (such as Naralex) win; otherwise a framework-neutral in-memory provider is
* created and can later be hydrated by persistence or multiplayer.
*/
export function activateDungeonEncounterDefinitions(
dungeonId: string,
definitions: readonly DungeonEncounterDefinition[],
): void {
activeDungeonId = dungeonId;
for (const definition of definitions) {
validateDungeonEncounterDefinition(definition);
if (definition.dungeonId !== dungeonId) {
throw new Error(`Encounter ${definition.id} belongs to ${definition.dungeonId}, not ${dungeonId}`);
}
const existing = providers.get(definition.id);
if (existing) {
if (existing.dungeonId !== dungeonId) throw new Error(`Encounter ${definition.id} has conflicting dungeon owners`);
continue;
}
let snapshot = createDungeonEncounterSnapshot(definition);
const provider: DungeonEncounterProvider = {
definition,
encounterId: definition.id,
dungeonId,
getSnapshot: () => snapshot,
dispatch: (event) => {
const result = reduceDungeonEncounter(definition, snapshot, event);
snapshot = result.snapshot;
return result.effects;
},
reset: (scope, now = 0) => {
snapshot = resetDungeonEncounterSnapshot(definition, snapshot, scope, now);
},
replaceSnapshot: (value) => {
if (value.encounterId !== definition.id) {
throw new Error(`Cannot hydrate ${definition.id} from ${value.encounterId}`);
}
snapshot = cloneEncounterSnapshot(value);
},
};
providers.set(definition.id, provider);
}
notifyEncounterSubscribers();
}
function cloneEncounterSnapshot(value: DungeonEncounterSnapshot): DungeonEncounterSnapshot {
if (value.schemaVersion !== DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION) {
throw new Error(`Unsupported encounter snapshot schema ${value.schemaVersion}`);
}
return Object.freeze({
...value,
phaseStartedAt: finiteTime(value.phaseStartedAt),
flags: Object.freeze({ ...value.flags }),
counters: Object.freeze({ ...value.counters }),
timers: Object.freeze({ ...value.timers }),
worldObjectStates: Object.freeze({ ...value.worldObjectStates }),
transitionStates: Object.freeze({ ...value.transitionStates }),
activeSpawnGroupIds: unique(value.activeSpawnGroupIds),
completedObjectiveIds: unique(value.completedObjectiveIds),
consumedActionIds: unique(value.consumedActionIds),
revision: Math.max(0, Math.trunc(value.revision)),
});
}
export function activeDungeonEncounterId(): string | null {
return activeDungeonId;
}
export function exportDungeonEncounterSnapshots(dungeonId = activeDungeonId): readonly DungeonEncounterSnapshot[] {
if (!dungeonId) return [];
return Object.freeze([...providers.values()]
.filter((provider) => provider.dungeonId === dungeonId)
.sort((left, right) => left.encounterId.localeCompare(right.encounterId))
.map((provider) => cloneEncounterSnapshot(provider.getSnapshot())));
}
/** Save-safe projection that strips timers and temporary waves without changing live state. */
export function exportDungeonEncounterCheckpointSnapshots(
dungeonId = activeDungeonId,
now = Date.now(),
): readonly DungeonEncounterSnapshot[] {
if (!dungeonId) return [];
return Object.freeze([...providers.values()]
.filter((provider) => provider.dungeonId === dungeonId)
.sort((left, right) => left.encounterId.localeCompare(right.encounterId))
.map((provider) => {
const live = provider.getSnapshot();
const checkpoint = resetDungeonEncounterSnapshot(
provider.definition,
live,
"checkpoint",
now,
);
return cloneEncounterSnapshot({ ...checkpoint, revision: live.revision });
}));
}
export function importDungeonEncounterSnapshots(
dungeonId: string,
snapshots: readonly DungeonEncounterSnapshot[],
): readonly string[] {
const imported: string[] = [];
for (const raw of snapshots) {
const provider = providers.get(raw.encounterId);
if (!provider || provider.dungeonId !== dungeonId || !provider.replaceSnapshot) continue;
const snapshot = cloneEncounterSnapshot(raw);
if (snapshot.revision < provider.getSnapshot().revision) continue;
provider.replaceSnapshot(snapshot);
imported.push(snapshot.encounterId);
}
if (imported.length) notifyEncounterSubscribers();
return Object.freeze(imported.sort());
}
export function drainDungeonEncounterEffects(
dungeonId = activeDungeonId,
): readonly DungeonEncounterEffectEnvelope[] {
if (!dungeonId) return [];
const drained = pendingEffects.filter((value) => value.dungeonId === dungeonId);
for (let index = pendingEffects.length - 1; index >= 0; index -= 1) {
if (pendingEffects[index].dungeonId === dungeonId) pendingEffects.splice(index, 1);
}
return Object.freeze(drained);
}
/** Immediate application hook; effects also remain drainable for frame-bound consumers. */
export function registerDungeonEncounterEffectHandler(
handler: (value: DungeonEncounterEffectEnvelope) => void,
): () => void {
effectHandlers.add(handler);
return () => effectHandlers.delete(handler);
}
const encounterSubscribers = new Set<() => void>();
function notifyEncounterSubscribers(): void {
for (const subscriber of encounterSubscribers) subscriber();
}
/** React can consume this with useSyncExternalStore without owning simulation. */
export function subscribeDungeonEncounterSnapshots(subscriber: () => void): () => void {
encounterSubscribers.add(subscriber);
return () => encounterSubscribers.delete(subscriber);
}
export function activeDungeonEncounterRevision(): number {
return exportDungeonEncounterSnapshots().reduce(
(revision, snapshot) => revision + snapshot.revision,
0,
);
}
export function activeDungeonEncounterSpawnGroupIds(): readonly string[] {
return Object.freeze([...new Set(exportDungeonEncounterSnapshots()
.flatMap((snapshot) => snapshot.activeSpawnGroupIds))].sort());
}
+23
View File
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { activeEncounterSpawnGroupSpawns } from "./dungeonEncounterSpawns";
const spawn = (id: string, spawnMask: number) => ({
id,
entityId: "test-mob",
position: [0, 0, 0] as const,
yaw: 0,
spawnMask,
});
describe("encounter spawn groups", () => {
it("adds only active, difficulty-matching, non-duplicate runtime ids", () => {
const result = activeEncounterSpawnGroupSpawns([
{ id: "wave-a", spawns: [spawn("one", 1), spawn("two", 2)] },
{ id: "wave-b", spawns: [spawn("three", 1)] },
], ["wave-a"], 1, [spawn("one", 1)]);
expect(result.map((entry) => entry.id)).toEqual([]);
expect(activeEncounterSpawnGroupSpawns([
{ id: "wave-a", spawns: [spawn("one", 1), spawn("two", 2)] },
], ["wave-a"], 2).map((entry) => entry.id)).toEqual(["two"]);
});
});
+22
View File
@@ -0,0 +1,22 @@
import { spawnMatchesDifficulty, type StaticMobSpawnDefinition } from "./mobPopulation";
import type { DungeonEncounterSpawnGroupDefinition } from "./dungeonTypes";
export function activeEncounterSpawnGroupSpawns(
groups: readonly DungeonEncounterSpawnGroupDefinition[],
activeGroupIds: readonly string[],
difficultyMask: number,
existingSpawns: readonly StaticMobSpawnDefinition[] = [],
): readonly StaticMobSpawnDefinition[] {
const active = new Set(activeGroupIds);
const existing = new Set(existingSpawns.map((spawn) => spawn.id));
const included = new Set<string>();
return groups
.filter((group) => active.has(group.id))
.flatMap((group) => group.spawns)
.filter((spawn) => {
if (existing.has(spawn.id) || included.has(spawn.id)) return false;
if (!spawnMatchesDifficulty(spawn.spawnMask, difficultyMask)) return false;
included.add(spawn.id);
return true;
});
}
+73
View File
@@ -0,0 +1,73 @@
import { create, type StoreApi, type UseBoundStore } from "zustand";
import {
createDungeonEncounterSnapshot,
reduceDungeonEncounter,
registerDungeonEncounterProvider,
resetDungeonEncounterSnapshot,
validateDungeonEncounterDefinition,
} from "./dungeonEncounterRuntime";
import type {
DungeonEncounterDefinition,
DungeonEncounterEffect,
DungeonEncounterEvent,
DungeonEncounterResetScope,
DungeonEncounterSnapshot,
} from "./dungeonEncounterTypes";
export interface DungeonEncounterStoreState extends DungeonEncounterSnapshot {
readonly dispatch: (event: DungeonEncounterEvent) => readonly DungeonEncounterEffect[];
readonly reset: (scope?: DungeonEncounterResetScope, now?: number) => void;
}
function serializableSnapshot(state: DungeonEncounterStoreState): DungeonEncounterSnapshot {
return {
schemaVersion: state.schemaVersion,
encounterId: state.encounterId,
phase: state.phase,
phaseStartedAt: state.phaseStartedAt,
flags: state.flags,
counters: state.counters,
timers: state.timers,
worldObjectStates: state.worldObjectStates,
transitionStates: state.transitionStates,
activeSpawnGroupIds: state.activeSpawnGroupIds,
completedObjectiveIds: state.completedObjectiveIds,
consumedActionIds: state.consumedActionIds,
checkpointId: state.checkpointId,
revision: state.revision,
};
}
export function createDungeonEncounterStore(
definition: DungeonEncounterDefinition,
): UseBoundStore<StoreApi<DungeonEncounterStoreState>> {
validateDungeonEncounterDefinition(definition);
const useStore = create<DungeonEncounterStoreState>((set, get) => ({
...createDungeonEncounterSnapshot(definition),
dispatch: (event) => {
const snapshot = serializableSnapshot(get());
const result = reduceDungeonEncounter(definition, snapshot, event);
if (result.snapshot !== snapshot) set(result.snapshot);
return result.effects;
},
reset: (scope = "dungeon", now = 0) => {
set(resetDungeonEncounterSnapshot(definition, serializableSnapshot(get()), scope, now));
},
}));
const provider = {
definition,
encounterId: definition.id,
dungeonId: definition.dungeonId,
getSnapshot: () => serializableSnapshot(useStore.getState()),
dispatch: (event: DungeonEncounterEvent) => useStore.getState().dispatch(event),
reset: (scope: DungeonEncounterResetScope, now = 0) => useStore.getState().reset(scope, now),
replaceSnapshot: (snapshot: DungeonEncounterSnapshot) => {
if (snapshot.encounterId !== definition.id) {
throw new Error(`Cannot hydrate ${definition.id} from ${snapshot.encounterId}`);
}
useStore.setState(snapshot);
},
};
registerDungeonEncounterProvider(provider);
return useStore;
}
+165
View File
@@ -0,0 +1,165 @@
import type { Vector3Tuple } from "./dungeonTypes";
export const DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION = 1;
export type DungeonEncounterResetScope = "boss-wipe" | "checkpoint" | "dungeon";
export type DungeonEncounterValue = string | number | boolean;
export interface DungeonEncounterFacts {
readonly registeredEntityIds?: readonly string[];
readonly deadEntityIds?: readonly string[];
}
export type DungeonEncounterEvent =
| {
readonly kind: "tick";
readonly now: number;
readonly facts?: DungeonEncounterFacts;
}
| {
readonly kind: "signal";
readonly now: number;
readonly signalId: string;
readonly sourceEntityId?: string;
}
| { readonly kind: "entity-engaged"; readonly now: number; readonly entityId: string }
| { readonly kind: "entity-death"; readonly now: number; readonly entityId: string }
| {
readonly kind: "entity-health";
readonly now: number;
readonly entityId: string;
readonly healthPercent: number;
}
| {
readonly kind: "interact";
readonly now: number;
readonly objectId: string;
readonly actorId?: string;
};
export type DungeonEncounterTrigger =
| { readonly kind: "tick" }
| { readonly kind: "signal"; readonly signalId: string }
| { readonly kind: "entity-engaged"; readonly entityId: string }
| { readonly kind: "entity-death"; readonly entityId: string }
| {
readonly kind: "entity-health";
readonly entityId: string;
readonly atOrBelowPercent: number;
}
| { readonly kind: "interact"; readonly objectId: string }
| { readonly kind: "elapsed"; readonly afterMs: number }
| {
readonly kind: "entities-dead";
readonly entityIds: readonly string[];
/** A source-authored safety timeout prevents an encounter from soft locking. */
readonly timeoutMs?: number;
};
export type DungeonEncounterGuard =
| { readonly kind: "phase"; readonly phases: readonly string[] }
| { readonly kind: "flag"; readonly flagId: string; readonly equals: boolean }
| {
readonly kind: "counter";
readonly counterId: string;
readonly comparison: "equal" | "at-least" | "at-most";
readonly value: number;
}
| { readonly kind: "objective-complete"; readonly objectiveId: string; readonly equals: boolean }
| { readonly kind: "spawn-group-active"; readonly spawnGroupId: string; readonly equals: boolean }
| { readonly kind: "world-object-state"; readonly objectId: string; readonly states: readonly string[] };
export type DungeonEncounterActionPersistence = "attempt" | "checkpoint" | "run";
interface DungeonEncounterActionBase {
/** Stable, encounter-local id used to make rule execution idempotent. */
readonly id: string;
readonly persistence?: DungeonEncounterActionPersistence;
}
export type DungeonEncounterAction =
| (DungeonEncounterActionBase & { readonly kind: "set-phase"; readonly phase: string })
| (DungeonEncounterActionBase & { readonly kind: "set-flag"; readonly flagId: string; readonly value: boolean })
| (DungeonEncounterActionBase & { readonly kind: "set-counter"; readonly counterId: string; readonly value: number })
| (DungeonEncounterActionBase & { readonly kind: "increment-counter"; readonly counterId: string; readonly amount?: number })
| (DungeonEncounterActionBase & { readonly kind: "start-timer"; readonly timerId: string; readonly durationMs: number })
| (DungeonEncounterActionBase & { readonly kind: "clear-timer"; readonly timerId: string })
| (DungeonEncounterActionBase & { readonly kind: "activate-spawn-group"; readonly spawnGroupId: string })
| (DungeonEncounterActionBase & { readonly kind: "deactivate-spawn-group"; readonly spawnGroupId: string })
| (DungeonEncounterActionBase & { readonly kind: "set-world-object-state"; readonly objectId: string; readonly state: string })
| (DungeonEncounterActionBase & { readonly kind: "set-transition-active"; readonly transitionId: string; readonly active: boolean })
| (DungeonEncounterActionBase & { readonly kind: "complete-objective"; readonly objectiveId: string })
| (DungeonEncounterActionBase & { readonly kind: "set-checkpoint"; readonly checkpointId: string })
| (DungeonEncounterActionBase & { readonly kind: "teleport-party"; readonly destination: Vector3Tuple })
| (DungeonEncounterActionBase & { readonly kind: "grant-loot"; readonly lootTableId: string })
| (DungeonEncounterActionBase & { readonly kind: "emit-signal"; readonly signalId: string });
export interface DungeonEncounterRule {
readonly id: string;
readonly trigger: DungeonEncounterTrigger;
readonly guards?: readonly DungeonEncounterGuard[];
readonly actions: readonly DungeonEncounterAction[];
/** Defaults to true. Repeatable rules must remain safe when evaluated again. */
readonly once?: boolean;
readonly priority?: number;
}
export interface DungeonEncounterInitialState {
readonly flags?: Readonly<Record<string, boolean>>;
readonly counters?: Readonly<Record<string, number>>;
readonly worldObjectStates?: Readonly<Record<string, string>>;
readonly transitionStates?: Readonly<Record<string, boolean>>;
}
export interface DungeonEncounterResetPolicy {
readonly bossWipePhase?: string;
readonly checkpointPhase?: string;
}
export interface DungeonEncounterDefinition {
readonly id: string;
readonly dungeonId: string;
readonly initialPhase: string;
readonly initialState?: DungeonEncounterInitialState;
readonly resetPolicy?: DungeonEncounterResetPolicy;
readonly rules: readonly DungeonEncounterRule[];
}
/** JSON-safe source of truth; renderer objects and callbacks never enter this state. */
export interface DungeonEncounterSnapshot {
readonly schemaVersion: typeof DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION;
readonly encounterId: string;
readonly phase: string;
readonly phaseStartedAt: number;
readonly flags: Readonly<Record<string, boolean>>;
readonly counters: Readonly<Record<string, number>>;
readonly timers: Readonly<Record<string, number>>;
readonly worldObjectStates: Readonly<Record<string, string>>;
readonly transitionStates: Readonly<Record<string, boolean>>;
readonly activeSpawnGroupIds: readonly string[];
readonly completedObjectiveIds: readonly string[];
readonly consumedActionIds: readonly string[];
readonly checkpointId: string | null;
readonly revision: number;
}
export type DungeonEncounterEffect =
| { readonly kind: "spawn-group"; readonly actionId: string; readonly spawnGroupId: string }
| { readonly kind: "despawn-group"; readonly actionId: string; readonly spawnGroupId: string }
| { readonly kind: "world-object-state"; readonly actionId: string; readonly objectId: string; readonly state: string }
| { readonly kind: "transition-state"; readonly actionId: string; readonly transitionId: string; readonly active: boolean }
| { readonly kind: "party-teleport"; readonly actionId: string; readonly destination: Vector3Tuple }
| { readonly kind: "loot-grant"; readonly actionId: string; readonly lootTableId: string }
| { readonly kind: "signal"; readonly actionId: string; readonly signalId: string };
export interface DungeonEncounterReduction {
readonly snapshot: DungeonEncounterSnapshot;
readonly effects: readonly DungeonEncounterEffect[];
readonly firedRuleIds: readonly string[];
}
export interface DungeonEncounterEffectEnvelope {
readonly dungeonId: string;
readonly encounterId: string;
readonly effect: DungeonEncounterEffect;
}
+15
View File
@@ -6,6 +6,7 @@ import * as recastPolicy from "../../scripts/dungeon-pipeline/recast-policy.mjs"
const {
nextTiledNavMeshSize,
planNavigationBuild,
shouldRetryFailedTiledBuildWithMonotone,
shouldUseMonotonePartition,
validateTiledBuild,
} = recastPolicy;
@@ -36,6 +37,14 @@ describe("dungeon Recast build policy", () => {
});
});
it("can explicitly tile compact but contour-complex source geometry", () => {
expect(planNavigationBuild([[0, 0, 0], [40, 10, 40]], 0.2, {
tileSize: 64,
maxSoloAxis: 0,
maxSoloCells: 0,
})).toMatchObject({ mode: "tiled", tileSize: 64, tileColumns: 4, tileRows: 4 });
});
it("accepts only confirmed empty-tile packing failures", () => {
const intermediates = {
buildContext: {
@@ -156,4 +165,10 @@ describe("dungeon Recast build policy", () => {
],
})).toBe(false);
});
it("retries watershed contour triangulation failures with monotone partition only", () => {
expect(shouldRetryFailedTiledBuildWithMonotone("Failed to triangulate contours", "watershed")).toBe(true);
expect(shouldRetryFailedTiledBuildWithMonotone("Failed to triangulate contours", "monotone")).toBe(false);
expect(shouldRetryFailedTiledBuildWithMonotone("Out of memory", "watershed")).toBe(false);
});
});
+8 -3
View File
@@ -6,6 +6,7 @@ import { RUNEWAKER_DUNGEON_DEFINITIONS } from "./generated/runewakerDungeonDefin
import compressedCampaign from "./generated/dungeonCampaignCatalog.compressed";
import { gunzipSync, strFromU8 } from "fflate";
import { DEFAULT_DUNGEON_ID as DEFAULT_DUNGEON_KEY } from "./dungeonDefaults";
import { withRunewakerEncounterContent } from "./runewakerEncounterCatalog";
export { DEFAULT_DUNGEON_ID } from "./dungeonDefaults";
@@ -87,15 +88,19 @@ if (authoredWailingCaverns.id !== DEFAULT_DUNGEON_KEY) {
throw new Error(`Default dungeon id mismatch: ${authoredWailingCaverns.id}`);
}
export const DUNGEON_REGISTRY: Readonly<Record<string, DungeonDefinition>> = Object.freeze(
Object.fromEntries([
const dungeonRegistryEntries: readonly (readonly [string, DungeonDefinition])[] = [
[authoredWailingCaverns.id, authoredWailingCaverns],
[FORSAKEN_ABBEY_DEFINITION.id, FORSAKEN_ABBEY_DEFINITION],
...RUNEWAKER_DUNGEON_DEFINITIONS.map((definition) => [definition.id, definition] as const),
...generatedDefinitions
.filter((definition) => definition.id !== authoredWailingCaverns.id)
.map((definition) => [definition.id, definition] as const),
]),
];
export const DUNGEON_REGISTRY: Readonly<Record<string, DungeonDefinition>> = Object.freeze(
Object.fromEntries(dungeonRegistryEntries.map(
([id, definition]) => [id, withRunewakerEncounterContent(definition)] as const,
)),
);
export type DungeonId = string;
+43
View File
@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { createDungeonEncounterSnapshot } from "./dungeonEncounterRuntime";
import type { DungeonEncounterDefinition } from "./dungeonEncounterTypes";
import {
DUNGEON_RUN_CHECKPOINT_VERSION,
normalizeDungeonRunCheckpoint,
} from "./dungeonRunCheckpoint";
const definition: DungeonEncounterDefinition = {
id: "test:boss",
dungeonId: "test",
initialPhase: "ready",
rules: [],
};
describe("dungeon run checkpoints", () => {
it("normalizes JSON-safe encounter progress and world spawn", () => {
const result = normalizeDungeonRunCheckpoint({
version: DUNGEON_RUN_CHECKPOINT_VERSION,
dungeonId: "test",
difficultyId: "normal",
checkpointId: "entrance",
spawn: { footPosition: [1, 2, 3], yaw: 0.5 },
encounters: [createDungeonEncounterSnapshot(definition)],
savedAt: 123,
});
expect(result).toMatchObject({ dungeonId: "test", checkpointId: "entrance" });
expect(result?.encounters[0]?.encounterId).toBe("test:boss");
});
it("fails closed on malformed or unbounded data", () => {
expect(normalizeDungeonRunCheckpoint({})).toBeNull();
expect(normalizeDungeonRunCheckpoint({
version: DUNGEON_RUN_CHECKPOINT_VERSION,
dungeonId: "test",
difficultyId: "normal",
checkpointId: null,
spawn: { footPosition: [Number.NaN, 2, 3], yaw: 0 },
encounters: [],
savedAt: 1,
})).toBeNull();
});
});
+124
View File
@@ -0,0 +1,124 @@
import type { DungeonEncounterSnapshot } from "./dungeonEncounterTypes";
import { DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION } from "./dungeonEncounterTypes";
import type { Vector3Tuple, WorldSpawn } from "./dungeonTypes";
export const DUNGEON_RUN_CHECKPOINT_VERSION = 1;
export interface DungeonRunCheckpoint {
readonly version: typeof DUNGEON_RUN_CHECKPOINT_VERSION;
readonly dungeonId: string;
readonly difficultyId: string;
readonly checkpointId: string | null;
readonly spawn: WorldSpawn;
readonly encounters: readonly DungeonEncounterSnapshot[];
readonly savedAt: number;
}
function finiteVector(value: unknown): Vector3Tuple | null {
if (!Array.isArray(value) || value.length !== 3 || value.some((entry) => (
typeof entry !== "number" || !Number.isFinite(entry)
))) return null;
return [value[0], value[1], value[2]];
}
function booleanRecord(value: unknown): Readonly<Record<string, boolean>> | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const entries = Object.entries(value as Record<string, unknown>);
if (entries.some(([key, entry]) => !key || typeof entry !== "boolean")) return null;
return Object.freeze(Object.fromEntries(entries) as Record<string, boolean>);
}
function numberRecord(value: unknown): Readonly<Record<string, number>> | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const entries = Object.entries(value as Record<string, unknown>);
if (entries.some(([key, entry]) => !key || typeof entry !== "number" || !Number.isFinite(entry))) return null;
return Object.freeze(Object.fromEntries(entries) as Record<string, number>);
}
function stringRecord(value: unknown): Readonly<Record<string, string>> | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const entries = Object.entries(value as Record<string, unknown>);
if (entries.some(([key, entry]) => !key || typeof entry !== "string")) return null;
return Object.freeze(Object.fromEntries(entries) as Record<string, string>);
}
function stringList(value: unknown): readonly string[] | null {
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !entry)) return null;
return Object.freeze([...new Set(value)]);
}
function normalizeSnapshot(value: unknown): DungeonEncounterSnapshot | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const input = value as Partial<DungeonEncounterSnapshot>;
if (
input.schemaVersion !== DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION
|| typeof input.encounterId !== "string"
|| !input.encounterId
|| typeof input.phase !== "string"
|| !input.phase
|| typeof input.phaseStartedAt !== "number"
|| !Number.isFinite(input.phaseStartedAt)
|| typeof input.revision !== "number"
|| !Number.isFinite(input.revision)
|| (input.checkpointId !== null && typeof input.checkpointId !== "string")
) return null;
const flags = booleanRecord(input.flags);
const counters = numberRecord(input.counters);
const timers = numberRecord(input.timers);
const worldObjectStates = stringRecord(input.worldObjectStates);
const transitionStates = booleanRecord(input.transitionStates);
const activeSpawnGroupIds = stringList(input.activeSpawnGroupIds);
const completedObjectiveIds = stringList(input.completedObjectiveIds);
const consumedActionIds = stringList(input.consumedActionIds);
if (!flags || !counters || !timers || !worldObjectStates || !transitionStates
|| !activeSpawnGroupIds || !completedObjectiveIds || !consumedActionIds) return null;
return Object.freeze({
schemaVersion: DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION,
encounterId: input.encounterId,
phase: input.phase,
phaseStartedAt: input.phaseStartedAt,
flags,
counters,
timers,
worldObjectStates,
transitionStates,
activeSpawnGroupIds,
completedObjectiveIds,
consumedActionIds,
checkpointId: input.checkpointId ?? null,
revision: Math.max(0, Math.trunc(input.revision)),
});
}
export function normalizeDungeonRunCheckpoint(value: unknown): DungeonRunCheckpoint | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const input = value as Partial<DungeonRunCheckpoint>;
const position = finiteVector(input.spawn?.footPosition);
const yaw = input.spawn?.yaw;
if (
input.version !== DUNGEON_RUN_CHECKPOINT_VERSION
|| typeof input.dungeonId !== "string"
|| !input.dungeonId
|| typeof input.difficultyId !== "string"
|| !input.difficultyId
|| !position
|| typeof yaw !== "number"
|| !Number.isFinite(yaw)
|| !Array.isArray(input.encounters)
|| input.encounters.length > 128
|| typeof input.savedAt !== "number"
|| !Number.isFinite(input.savedAt)
|| (input.checkpointId !== null && typeof input.checkpointId !== "string")
) return null;
const encounters = input.encounters.map(normalizeSnapshot);
if (encounters.some((entry) => entry === null)) return null;
return Object.freeze({
version: DUNGEON_RUN_CHECKPOINT_VERSION,
dungeonId: input.dungeonId,
difficultyId: input.difficultyId,
checkpointId: input.checkpointId ?? null,
spawn: { footPosition: position, yaw },
encounters: Object.freeze(encounters as DungeonEncounterSnapshot[]),
savedAt: input.savedAt,
});
}
+13
View File
@@ -10,6 +10,12 @@ import { dungeonCanEnter, dungeonDefinitionById, type DungeonId } from "./dungeo
import type { WorldSpawn } from "./dungeonTypes";
import { useManastormAdminStore } from "./manastormAdminStore";
import type { GmReviewTarget } from "./gmContentReview";
import {
activateDungeonEncounterDefinitions,
importDungeonEncounterSnapshots,
resetDungeonEncounters,
} from "./dungeonEncounterRuntime";
import type { DungeonEncounterSnapshot } from "./dungeonEncounterTypes";
export interface DungeonSessionOptions {
readonly gameMode?: GameMode;
@@ -18,6 +24,8 @@ export interface DungeonSessionOptions {
readonly gmReviewTarget?: GmReviewTarget | null;
/** Used by in-game encounter transitions so character progression and inventory survive. */
readonly preserveCharacter?: boolean;
/** Hydrates save-safe state after definitions have been activated and reset. */
readonly encounterSnapshots?: readonly DungeonEncounterSnapshot[];
}
/**
@@ -37,6 +45,8 @@ export function activateDungeonSession(
const previousDefinition = dungeonDefinitionById(useGameStore.getState().activeDungeonId);
if (previousDefinition) releaseDungeonGltfCache(previousDefinition);
activateDungeonEncounterDefinitions(dungeonId, definition.encounters ?? []);
resetDungeonEncounters("dungeon", dungeonId);
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
clearDungeonRuntimeCaches();
@@ -80,6 +90,9 @@ export function activateDungeonSession(
gmTestMode: options.gmTestMode,
gmReviewTarget: options.gmReviewTarget,
});
if (options.encounterSnapshots?.length) {
importDungeonEncounterSnapshots(dungeonId, options.encounterSnapshots);
}
useCombatStore.getState().setPlayerPosition(useGameStore.getState().playerPosition);
return true;
}
+11
View File
@@ -7,6 +7,8 @@ import { usePartyStore } from "./partyStore";
import { useGameStore } from "./store";
import { DEFAULT_MANASTORM_ADMIN_CONFIG } from "./manastormAdminConfig";
import { useManastormAdminStore } from "./manastormAdminStore";
import { dungeonEncounterSnapshot } from "./dungeonEncounterRuntime";
import { WAILING_NARALEX_ENCOUNTER, useWailingEncounterStore } from "./wailingCavernsEncounter";
describe("dungeon session asset eviction", () => {
afterEach(() => {
@@ -56,4 +58,13 @@ describe("dungeon session asset eviction", () => {
expect(activateDungeonSession("wailing-caverns")).toBe(true);
expect(useGameStore.getState().activeSpawn).toEqual(spawn);
});
it("hydrates a safe encounter checkpoint after the dungeon reset", () => {
const current = dungeonEncounterSnapshot(WAILING_NARALEX_ENCOUNTER.id)!;
const saved = { ...current, phase: "ready", revision: current.revision + 10 } as const;
expect(activateDungeonSession("wailing-caverns", "normal", {
encounterSnapshots: [saved],
})).toBe(true);
expect(useWailingEncounterStore.getState().phase).toBe("ready");
});
});
+12 -1
View File
@@ -4,6 +4,8 @@ import type {
PopulationDefinitionMap,
StaticMobSpawnDefinition,
} from "./mobPopulation";
import type { DungeonEncounterDefinition } from "./dungeonEncounterTypes";
import type { DungeonWorldObjectDefinition } from "./dungeonWorldObjects";
export type Vector3Tuple = MobVector3;
export type DungeonEnvironmentMode = "global-wmo" | "adt-hybrid" | "runewaker-ros";
@@ -154,6 +156,11 @@ export interface DungeonNavigationLink {
readonly authoredReason: string;
}
export interface DungeonEncounterSpawnGroupDefinition {
readonly id: string;
readonly spawns: readonly StaticMobSpawnDefinition[];
}
export interface DungeonMapPresentation {
readonly width: number;
readonly height: number;
@@ -232,7 +239,11 @@ export interface DungeonDefinition {
readonly bosses: readonly DungeonBossObjective[];
readonly objectiveOrder: readonly DungeonBossObjective[];
readonly navigationLinks: readonly DungeonNavigationLink[];
readonly serverGameObjects: readonly unknown[];
/** Serializable dungeon-wide orchestration kept separate from render state. */
readonly encounters?: readonly DungeonEncounterDefinition[];
/** Declarative groups activated by encounter actions; renderer ownership stays outside the reducer. */
readonly encounterSpawnGroups?: readonly DungeonEncounterSpawnGroupDefinition[];
readonly serverGameObjects: readonly DungeonWorldObjectDefinition[];
readonly provenance: DungeonSourceProvenance;
readonly validation: DungeonValidationStatus;
}
+65
View File
@@ -0,0 +1,65 @@
import { requireDungeonDefinition } from "./dungeonRegistry";
import {
dispatchActiveDungeonInteraction,
dispatchDungeonEncounterSignal,
exportDungeonEncounterSnapshots,
} from "./dungeonEncounterRuntime";
import { combatHasLineOfSight } from "./combatSpatial";
import {
nearestDungeonWorldInteraction,
type DungeonWorldObjectDefinition,
type DungeonWorldInteractionCandidate,
} from "./dungeonWorldObjects";
import type { MobVector3 } from "./mobPopulation";
import {
onlinePlayerActorId,
onlineSessionLocalAccountId,
requestOnlineInteraction,
} from "./onlineSessionRuntime";
import { useGameStore } from "./store";
export function activeDungeonWorldObjectStates(): Readonly<Record<string, string>> {
const game = useGameStore.getState();
const states: Record<string, string> = {};
for (const snapshot of exportDungeonEncounterSnapshots(game.activeDungeonId)) {
Object.assign(states, snapshot.worldObjectStates);
}
return states;
}
export function activeDungeonInteractionCandidate(): DungeonWorldInteractionCandidate | null {
const game = useGameStore.getState();
return dungeonInteractionCandidateAt(game.playerPosition);
}
export function dungeonInteractionCandidateAt(origin: MobVector3): DungeonWorldInteractionCandidate | null {
const game = useGameStore.getState();
if (game.gameMode !== "dungeon") return null;
const definition = requireDungeonDefinition(game.activeDungeonId);
return nearestDungeonWorldInteraction(
definition.serverGameObjects,
activeDungeonWorldObjectStates(),
origin,
combatHasLineOfSight,
);
}
export function dispatchDungeonWorldObjectInteraction(
object: DungeonWorldObjectDefinition,
actorId: string | undefined,
now: number,
): void {
dispatchActiveDungeonInteraction(object.id, actorId, now);
dispatchDungeonEncounterSignal(object.interaction!.signalId, now);
}
/** Followers request; the party leader validates and mutates the shared encounter. */
export function interactWithActiveDungeonObject(now = Date.now()): boolean {
const candidate = activeDungeonInteractionCandidate();
if (!candidate) return false;
if (requestOnlineInteraction("interact", candidate.object.id)) return true;
const localAccountId = onlineSessionLocalAccountId();
const actorId = localAccountId ? onlinePlayerActorId(localAccountId) : undefined;
dispatchDungeonWorldObjectInteraction(candidate.object, actorId, now);
return true;
}
+50
View File
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import {
dungeonWorldObjectState,
nearestDungeonWorldInteraction,
validateDungeonWorldObject,
type DungeonWorldObjectDefinition,
} from "./dungeonWorldObjects";
const door: DungeonWorldObjectDefinition = {
id: "test-door",
name: "Test Door",
kind: "door",
position: [3, 0, 0],
halfExtents: [1, 2, 0.25],
initialState: "closed",
states: [
{ id: "closed", label: "Closed", visible: true, blocksMovement: true, interactionEnabled: true },
{ id: "open", label: "Open", visible: false, blocksMovement: false, interactionEnabled: false },
],
interaction: {
prompt: "Open door",
range: 4,
signalId: "open-test-door",
allowedStates: ["closed"],
},
};
describe("dungeon world objects", () => {
it("resolves authored and fallback object states", () => {
expect(dungeonWorldObjectState(door, "open").blocksMovement).toBe(false);
expect(dungeonWorldObjectState(door, "missing").id).toBe("closed");
});
it("selects the nearest enabled visible interaction in range and line of sight", () => {
const farther = { ...door, id: "far-door", position: [3.5, 0, 0] as const };
expect(nearestDungeonWorldInteraction([farther, door], {}, [0, 0, 0])?.object.id).toBe("test-door");
expect(nearestDungeonWorldInteraction([door], { "test-door": "open" }, [0, 0, 0])).toBeNull();
expect(nearestDungeonWorldInteraction([door], {}, [0, 0, 0], () => false)).toBeNull();
});
it("rejects malformed geometry, state, and interaction contracts", () => {
expect(() => validateDungeonWorldObject(door)).not.toThrow();
expect(() => validateDungeonWorldObject({ ...door, halfExtents: [0, 2, 1] })).toThrow("invalid geometry");
expect(() => validateDungeonWorldObject({ ...door, initialState: "missing" })).toThrow("does not define");
expect(() => validateDungeonWorldObject({
...door,
interaction: { ...door.interaction!, range: 0 },
})).toThrow("invalid interaction");
});
});
+137
View File
@@ -0,0 +1,137 @@
import type { MobVector3 } from "./mobPopulation";
export type DungeonWorldObjectKind =
| "door"
| "gate"
| "bridge"
| "switch"
| "key"
| "chest"
| "npc"
| "quest-object"
| "trigger";
export type DungeonWorldObjectFidelity =
| "source-exact"
| "source-backed-adapted"
| "healer-man-authored";
export interface DungeonWorldObjectSource {
readonly fidelity: DungeonWorldObjectFidelity;
readonly files: readonly string[];
readonly templateId?: number;
readonly spawnId?: number;
readonly note?: string;
}
export interface DungeonWorldObjectStateDefinition {
readonly id: string;
readonly label: string;
readonly visible: boolean;
readonly blocksMovement: boolean;
readonly blocksSight?: boolean;
readonly interactionEnabled?: boolean;
}
export interface DungeonWorldObjectInteraction {
readonly prompt: string;
readonly range: number;
readonly signalId: string;
readonly allowedStates?: readonly string[];
readonly oneShot?: boolean;
readonly instructions?: string;
}
export interface DungeonWorldObjectVisual {
/** Optional independently packaged GLB. Procedural geometry remains the safe fallback. */
readonly assetUrl?: string;
readonly color?: string;
readonly emissive?: string;
readonly scale?: MobVector3;
}
export interface DungeonWorldObjectDefinition {
readonly id: string;
readonly name: string;
readonly kind: DungeonWorldObjectKind;
readonly position: MobVector3;
readonly yaw?: number;
/** Half-extents of the interaction/collision box in metres. */
readonly halfExtents: MobVector3;
readonly initialState: string;
readonly states: readonly DungeonWorldObjectStateDefinition[];
readonly interaction?: DungeonWorldObjectInteraction;
readonly visual?: DungeonWorldObjectVisual;
readonly source?: DungeonWorldObjectSource;
}
export interface DungeonWorldInteractionCandidate {
readonly object: DungeonWorldObjectDefinition;
readonly state: DungeonWorldObjectStateDefinition;
readonly distance: number;
}
function finiteDistance(left: MobVector3, right: MobVector3): number {
if (!left.every(Number.isFinite) || !right.every(Number.isFinite)) return Number.POSITIVE_INFINITY;
return Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]);
}
export function dungeonWorldObjectState(
object: DungeonWorldObjectDefinition,
stateId: string | undefined,
): DungeonWorldObjectStateDefinition {
const requested = stateId ?? object.initialState;
return object.states.find((state) => state.id === requested)
?? object.states.find((state) => state.id === object.initialState)
?? {
id: object.initialState,
label: object.initialState,
visible: true,
blocksMovement: false,
interactionEnabled: false,
};
}
export function validateDungeonWorldObject(object: DungeonWorldObjectDefinition): void {
if (!object.id.trim()) throw new Error("Dungeon world objects require a stable id.");
if (!object.name.trim()) throw new Error(`Dungeon world object ${object.id} requires a name.`);
if (!object.position.every(Number.isFinite) || !object.halfExtents.every((value) => Number.isFinite(value) && value > 0)) {
throw new Error(`Dungeon world object ${object.id} has invalid geometry.`);
}
const stateIds = new Set<string>();
for (const state of object.states) {
if (!state.id.trim() || stateIds.has(state.id)) {
throw new Error(`Dungeon world object ${object.id} has an invalid or duplicate state id.`);
}
stateIds.add(state.id);
}
if (!stateIds.has(object.initialState)) {
throw new Error(`Dungeon world object ${object.id} does not define initial state ${object.initialState}.`);
}
if (object.interaction && (!object.interaction.signalId.trim() || object.interaction.range <= 0)) {
throw new Error(`Dungeon world object ${object.id} has an invalid interaction.`);
}
}
export function nearestDungeonWorldInteraction(
objects: readonly DungeonWorldObjectDefinition[],
states: Readonly<Record<string, string>>,
origin: MobVector3,
hasLineOfSight: (from: MobVector3, to: MobVector3) => boolean = () => true,
): DungeonWorldInteractionCandidate | null {
let nearest: DungeonWorldInteractionCandidate | null = null;
for (const object of objects) {
const interaction = object.interaction;
if (!interaction) continue;
const state = dungeonWorldObjectState(object, states[object.id]);
if (!state.visible) continue;
if (state.interactionEnabled === false) continue;
if (interaction.allowedStates?.length && !interaction.allowedStates.includes(state.id)) continue;
const distance = finiteDistance(origin, object.position);
if (distance > interaction.range || !hasLineOfSight(origin, object.position)) continue;
if (!nearest || distance < nearest.distance || (distance === nearest.distance && object.id < nearest.object.id)) {
nearest = { object, state, distance };
}
}
return nearest;
}
+6
View File
@@ -8,6 +8,10 @@ import {
type DungeonDefinition,
} from "../dungeonTypes";
import { withWailingServerCombat } from "../wailingCavernsServer";
import {
WAILING_NARALEX_ENCOUNTER,
WAILING_NARALEX_SPAWN_GROUPS,
} from "../wailingCavernsEncounter";
export const WAILING_CAVERNS_DEFINITION = {
schemaVersion: DUNGEON_DEFINITION_SCHEMA_VERSION,
@@ -117,6 +121,8 @@ export const WAILING_CAVERNS_DEFINITION = {
authoredReason: "Golden-fixture bridge for the Recast discontinuity between upper and lower cavern route surfaces.",
},
],
encounters: [WAILING_NARALEX_ENCOUNTER],
encounterSpawnGroups: WAILING_NARALEX_SPAWN_GROUPS,
serverGameObjects: [],
provenance: {
clientBuild: "3.3.5a",
+18 -1
View File
@@ -1,3 +1,8 @@
import {
normalizeRunewakerDamageFormula,
type RunewakerDamageFormulaSource,
} from "./runewakerCombatFormula";
export type EnemyAttackAnimation = "attack" | "cast";
export type EnemyAttackDelivery = "melee" | "projectile" | "area";
export type EnemyAttackTarget =
@@ -52,7 +57,15 @@ export type EnemyAbilityEffect =
readonly durationMs: number;
}
| { readonly kind: "transform"; readonly form: string }
| { readonly kind: "summon"; readonly creatureEntry: number; readonly count: number }
| {
readonly kind: "summon";
readonly creatureEntry: number;
readonly count: number;
/** Optional stable namespace for content-authored summoned entities. */
readonly entityId?: string;
/** Optional owner group used by encounter counters and reset cleanup. */
readonly spawnGroupId?: string;
}
| { readonly kind: "call-for-help"; readonly radius: number }
| { readonly kind: "encounter-event"; readonly eventId: string };
@@ -102,6 +115,8 @@ export interface EnemyAttackDefinition {
readonly repeatCooldownMs?: readonly [minimum: number, maximum: number];
readonly maximumCasts?: number;
readonly damageMultiplier: number;
/** Source-shaped RuneWaker formula evidence for imported damage mechanics. */
readonly runewakerFormula?: RunewakerDamageFormulaSource;
readonly radius?: number;
readonly telegraph?: EnemyAttackTelegraph;
readonly effect?: EnemyAbilityEffect;
@@ -236,6 +251,7 @@ export function normalizeEnemyAttack(
Math.round(finiteAtLeast(value.castTimeMs, inferredCastTimeMs, 0)),
telegraph?.warningMs ?? 0,
);
const runewakerFormula = normalizeRunewakerDamageFormula(value.runewakerFormula);
return Object.freeze({
id: value.id.trim() || fallback.id,
name: value.name.trim() || fallback.name,
@@ -260,6 +276,7 @@ export function normalizeEnemyAttack(
? { maximumCasts: value.maximumCasts }
: {}),
damageMultiplier: finiteAtLeast(value.damageMultiplier, fallback.damageMultiplier, 0),
...(runewakerFormula ? { runewakerFormula } : {}),
...(value.radius !== undefined
? { radius: finiteAtLeast(value.radius, 0, 0) }
: {}),
+42
View File
@@ -172,6 +172,48 @@ describe("server-backed enemy ability runtime", () => {
expect(summonIds.every((id) => getMobPosition(id) === null)).toBe(true);
});
it("uses content-authored summon namespaces and spawn-group ownership", () => {
useCombatStore.getState().registerMob("wave-controller", {
name: "Wave Controller",
maxHealth: 1_000,
attacks: [{
id: "release-wave",
name: "Release Wave",
delivery: "area",
target: "self",
school: "shadow",
animation: "cast",
range: 0,
cooldownMs: 60_000,
initialCooldownMs: [0, 0],
repeatCooldownMs: [60_000, 60_000],
maximumCasts: 1,
damageMultiplier: 0,
effect: {
kind: "summon",
creatureEntry: 5048,
count: 2,
entityId: "forsaken-abbey:necromage:add",
spawnGroupId: "forsaken-abbey:necromage:wave-one",
},
}],
});
setMobPosition("wave-controller", [2, 0, 0]);
useCombatStore.getState().damageMob("wave-controller", 1, 1_000);
useCombatStore.getState().advanceMobCombat(1_000, [0, 0, 0]);
useCombatStore.getState().advanceMobCombat(2_500, [0, 0, 0]);
const summonIds = Object.keys(useCombatStore.getState().mobs)
.filter((id) => id.startsWith("forsaken-abbey:necromage:add:"));
expect(summonIds).toEqual([
"forsaken-abbey:necromage:add:1:1",
"forsaken-abbey:necromage:add:1:2",
]);
expect(summonIds.every((id) => (
useCombatStore.getState().mobs[id].spawnGroupId === "forsaken-abbey:necromage:wave-one"
))).toBe(true);
});
it("applies a modular target aura as a dispellable player debuff", () => {
useCombatStore.getState().registerMob("runewaker-debuffer", {
name: "RuneWaker Debuffer",
@@ -124,7 +124,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495702,
"componentId": 501006,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -224,7 +242,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495702,
"componentId": 501006,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -284,7 +320,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498065,
"componentId": 621360,
"spellLevel": 25,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-aeternal-circle-spell-498605",
@@ -304,7 +358,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498605,
"componentId": 621360,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -364,7 +436,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498065,
"componentId": 621360,
"spellLevel": 25,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-aeternal-circle-spell-498607",
@@ -384,7 +474,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498607,
"componentId": 621361,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -444,7 +552,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498065,
"componentId": 621360,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-aeternal-circle-spell-498607",
@@ -464,7 +590,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498607,
"componentId": 621361,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -524,7 +668,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498845,
"componentId": 621361,
"spellLevel": 25,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -624,7 +786,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498609,
"componentId": 621361,
"spellLevel": 25,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -724,7 +904,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498851,
"componentId": 621378,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -784,7 +982,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498852,
"componentId": 621378,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-aeternal-circle-spell-498065",
@@ -804,7 +1020,25 @@ export const AETERNAL_CIRCLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498065,
"componentId": 621360,
"spellLevel": 25,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -2303,6 +2537,6 @@ export const AETERNAL_CIRCLE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const BARREN_CAVES_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492289,
"componentId": 502503,
"spellLevel": 6,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -104,7 +122,25 @@ export const BARREN_CAVES_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492290,
"componentId": 502505,
"spellLevel": 6,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -244,7 +280,25 @@ export const BARREN_CAVES_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490629,
"componentId": 501086,
"spellLevel": 22,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-barren-caves-spell-490630",
@@ -264,7 +318,25 @@ export const BARREN_CAVES_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490630,
"componentId": 501087,
"spellLevel": 9,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -324,7 +396,25 @@ export const BARREN_CAVES_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492291,
"componentId": 501045,
"spellLevel": 3,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -398,6 +488,24 @@ export const BARREN_CAVES_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492292,
"componentId": 502507,
"spellLevel": 3,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -426,6 +534,24 @@ export const BARREN_CAVES_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492293,
"componentId": 502509,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1062,6 +1188,6 @@ export const BARREN_CAVES_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -84,7 +84,25 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494168,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -144,7 +162,25 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494172,
"componentId": 500972,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -295,7 +331,25 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494249,
"componentId": 504612,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -80,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-cave-water-dragon-102623-spell-494250",
@@ -316,7 +370,25 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494250,
"componentId": 504753,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -50,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-cave-water-dragon-102623-spell-494253",
@@ -337,7 +409,25 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494253,
"componentId": 504771,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -3500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-cave-water-dragon-102623-spell-494254",
@@ -365,6 +455,24 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 10
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494254,
"componentId": 504772,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -3000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -536,6 +644,24 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493996,
"componentId": 502444,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -564,6 +690,24 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493999,
"componentId": 502444,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -592,6 +736,24 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494000,
"componentId": 502444,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1769,6 +1931,6 @@ export const CAVE_WATER_DRAGON_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -48,6 +48,6 @@ export const DEMON_STRONGHOLD_125_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495054,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -184,7 +202,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495007,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-495008",
@@ -204,7 +240,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495008,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -264,7 +318,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495007,
"componentId": 500972,
"spellLevel": 2,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-495008",
@@ -284,7 +356,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495008,
"componentId": 500972,
"spellLevel": 2,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-494887",
@@ -304,7 +394,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494887,
"componentId": 505891,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -364,7 +472,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494918,
"componentId": 505891,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -464,7 +590,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494727,
"componentId": 503019,
"spellLevel": 40,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-494710",
@@ -484,7 +628,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 2100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-495014",
@@ -504,7 +666,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495014,
"componentId": 500972,
"spellLevel": 3,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -564,7 +744,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494708,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-demon-stronghold-spell-493195",
@@ -584,7 +782,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493195,
"componentId": 500972,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -949,6 +1165,24 @@ export const DEMON_STRONGHOLD_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494875,
"componentId": 505593,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -4000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -970,7 +1204,25 @@ export const DEMON_STRONGHOLD_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494876,
"componentId": 505768,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -3059,6 +3311,6 @@ export const DEMON_STRONGHOLD_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -164,7 +164,25 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495464,
"componentId": 506443,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -2000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -264,7 +282,25 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494765,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -444,7 +480,25 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495274,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -504,7 +558,25 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494239,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -604,7 +676,25 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494239,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1567,6 +1657,6 @@ export const DUNGEON_OF_DALANIS_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -1,4 +1,4 @@
/* This file is generated by scripts/runewaker-pipeline/build-forsaken-population.mjs. */
/* Generated by scripts/runewaker-pipeline/build-population.mjs. */
import type { DungeonBossObjective } from "../dungeonTypes";
import type { PopulationDefinitionMap, StaticMobSpawnDefinition } from "../mobPopulation";
@@ -699,7 +699,25 @@ export const FORSAKEN_ABBEY_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490471,
"componentId": 500913,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-fa-100074-spell-490489",
@@ -727,6 +745,24 @@ export const FORSAKEN_ABBEY_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490489,
"componentId": 501010,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -2679,13 +2715,20 @@ export const FORSAKEN_ABBEY_BOSSES = [
export const FORSAKEN_ABBEY_OBJECTIVE_ORDER = FORSAKEN_ABBEY_BOSSES;
export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
"schemaVersion": 1,
"schemaVersion": 2,
"dungeonId": "forsaken-abbey",
"zoneId": 102,
"sourceRowCount": 251,
"importedSpawnCount": 150,
"excludedRowCount": 101,
"bossSpawnCount": 8,
"objectiveOrder": {
"authoritative": false,
"kind": "healerMan-distance-derived",
"evidence": [
"scripts/runewaker-pipeline/build-population.mjs"
]
},
"sourceSnapshot": "scripts/runewaker-pipeline/snapshots/forsaken-abbey-zone-102.json",
"sourceContentSha256": "13ef6edd3df6e71389aa92d01201889148c0794cf9ecaafdbaf90d3145d2bf7b",
"sourceHashes": {
@@ -2699,7 +2742,7 @@ export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
"Resource/data/string_enus.db": "4fce021ba9ed110a213e08d3228b2de57cec66ddbbc8cdcf5753204874d9ab75"
}
},
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(0.1,0.1,-0.1)",
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(meters,meters,-meters)",
"navmeshProjection": {
"maximumAllowedDistance": 2,
"maximumObservedDistance": 2.5965,
@@ -2707,5 +2750,8 @@ export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
"deferredRowsBeyondCombatLimit": 1
},
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry."
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -124,7 +124,25 @@ export const GRAFU_CASTLE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497025,
"componentId": 508731,
"spellLevel": 12,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-grafu-castle-spell-490537",
@@ -144,7 +162,25 @@ export const GRAFU_CASTLE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490537,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -204,7 +240,25 @@ export const GRAFU_CASTLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.5,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497026,
"componentId": 508733,
"spellLevel": 1,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.5,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.5,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-grafu-castle-spell-497504",
@@ -224,7 +278,25 @@ export const GRAFU_CASTLE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497504,
"componentId": 509463,
"spellLevel": 12,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -284,7 +356,25 @@ export const GRAFU_CASTLE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497508,
"componentId": 509469,
"spellLevel": 65,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-grafu-castle-spell-497038",
@@ -304,7 +394,25 @@ export const GRAFU_CASTLE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497038,
"componentId": 500962,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -364,7 +472,25 @@ export const GRAFU_CASTLE_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497020,
"componentId": 508727,
"spellLevel": 50,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 100,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-grafu-castle-spell-497021",
@@ -384,7 +510,25 @@ export const GRAFU_CASTLE_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497021,
"componentId": 508728,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -10,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -484,7 +628,25 @@ export const GRAFU_CASTLE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497508,
"componentId": 509469,
"spellLevel": 65,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-grafu-castle-spell-497038",
@@ -504,7 +666,25 @@ export const GRAFU_CASTLE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497038,
"componentId": 500962,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1306,6 +1486,6 @@ export const GRAFU_CASTLE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
File diff suppressed because it is too large. Load diff
+644 -32
View File
@@ -44,7 +44,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493201,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -104,7 +122,25 @@ export const HOTO_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493196,
"componentId": 502444,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493192",
@@ -124,7 +160,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493192,
"componentId": 502444,
"spellLevel": 900,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -224,7 +278,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493198,
"componentId": 500240,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -25,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -284,7 +356,25 @@ export const HOTO_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493197,
"componentId": 502444,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493192",
@@ -304,7 +394,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493192,
"componentId": 502444,
"spellLevel": 900,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -404,7 +512,25 @@ export const HOTO_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492864,
"componentId": 500972,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-492865",
@@ -424,7 +550,25 @@ export const HOTO_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492865,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -484,7 +628,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493201,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-492724",
@@ -504,7 +666,25 @@ export const HOTO_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492724,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -564,7 +744,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493195,
"componentId": 500972,
"spellLevel": 6,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -624,7 +822,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493218,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -684,7 +900,25 @@ export const HOTO_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493111,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493108",
@@ -704,7 +938,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493108,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493205",
@@ -724,7 +976,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493205,
"componentId": 500972,
"spellLevel": 6,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -784,7 +1054,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493223,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -884,7 +1172,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493217,
"componentId": 503083,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493218",
@@ -904,7 +1210,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493218,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1124,6 +1448,24 @@ export const HOTO_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493215,
"componentId": 502838,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1145,7 +1487,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493307,
"componentId": 503418,
"spellLevel": 800,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-102021-aura-493308",
@@ -1243,6 +1603,24 @@ export const HOTO_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493237,
"componentId": 503618,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1264,7 +1642,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493239,
"componentId": 500972,
"spellLevel": 14,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1421,6 +1817,24 @@ export const HOTO_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493210,
"componentId": 502871,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1567,7 +1981,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493580,
"componentId": 503928,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-102040-aura-493595",
@@ -1692,7 +2124,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493201,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493195",
@@ -1712,7 +2162,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493195,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1779,7 +2247,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493231,
"componentId": 503925,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-102041-spell-493232",
@@ -1800,7 +2286,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493232,
"componentId": 503002,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-102041-aura-493233",
@@ -1944,7 +2448,25 @@ export const HOTO_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493189,
"componentId": 503409,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -3000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-102063-spell-493221",
@@ -1972,6 +2494,24 @@ export const HOTO_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493221,
"componentId": 503410,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -2057,7 +2597,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493195,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493205",
@@ -2077,7 +2635,25 @@ export const HOTO_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493205,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -2137,7 +2713,25 @@ export const HOTO_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493226,
"componentId": 500972,
"spellLevel": 12,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-hoto-spell-493227",
@@ -2157,7 +2751,25 @@ export const HOTO_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493227,
"componentId": 502443,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -5646,6 +6258,6 @@ export const HOTO_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
File diff suppressed because it is too large. Load diff
@@ -44,7 +44,25 @@ export const KAL_TUROK_HIVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491020,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kal-turok-hive-spell-490517",
@@ -64,7 +82,25 @@ export const KAL_TUROK_HIVE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490517,
"componentId": 501023,
"spellLevel": 5,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kal-turok-hive-spell-490634",
@@ -84,7 +120,25 @@ export const KAL_TUROK_HIVE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490634,
"componentId": 501091,
"spellLevel": 210,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kal-turok-hive-spell-490489",
@@ -104,7 +158,25 @@ export const KAL_TUROK_HIVE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490489,
"componentId": 501010,
"spellLevel": 200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -181,6 +253,6 @@ export const KAL_TUROK_HIVE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -124,7 +124,25 @@ export const KALINS_SHRINE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491027,
"componentId": 501451,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -184,7 +202,25 @@ export const KALINS_SHRINE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491027,
"componentId": 501451,
"spellLevel": 350,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -244,7 +280,25 @@ export const KALINS_SHRINE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491027,
"componentId": 501451,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -304,7 +358,25 @@ export const KALINS_SHRINE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490581,
"componentId": 501045,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-spell-490584",
@@ -324,7 +396,25 @@ export const KALINS_SHRINE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490584,
"componentId": 501048,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -384,7 +474,25 @@ export const KALINS_SHRINE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490553,
"componentId": 501029,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-spell-490551",
@@ -404,7 +512,25 @@ export const KALINS_SHRINE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490551,
"componentId": 501027,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -464,7 +590,25 @@ export const KALINS_SHRINE_ENTITIES = {
8000,
8000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492371,
"componentId": 500919,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-spell-492373",
@@ -484,7 +628,25 @@ export const KALINS_SHRINE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492373,
"componentId": 500919,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -544,7 +706,25 @@ export const KALINS_SHRINE_ENTITIES = {
8000,
8000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492371,
"componentId": 500919,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-spell-492372",
@@ -564,7 +744,25 @@ export const KALINS_SHRINE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492372,
"componentId": 500919,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -639,6 +837,24 @@ export const KALINS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492371,
"componentId": 500919,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -660,7 +876,25 @@ export const KALINS_SHRINE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492372,
"componentId": 500919,
"spellLevel": 25,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-101507-spell-492373",
@@ -681,7 +915,25 @@ export const KALINS_SHRINE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492373,
"componentId": 500919,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-101507-aura-492374",
@@ -775,7 +1027,25 @@ export const KALINS_SHRINE_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492375,
"componentId": 502444,
"spellLevel": 300,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-101502-debuff-492376",
@@ -1130,7 +1400,25 @@ export const KALINS_SHRINE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kalins-shrine-101500-spell-492367",
@@ -1158,6 +1446,24 @@ export const KALINS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492367,
"componentId": 502045,
"spellLevel": 14,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1284,6 +1590,24 @@ export const KALINS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 10
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492379,
"componentId": 503044,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1344,6 +1668,24 @@ export const KALINS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492381,
"componentId": 501107,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1372,6 +1714,24 @@ export const KALINS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492382,
"componentId": 503044,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -3324,6 +3684,6 @@ export const KALINS_SHRINE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const KAWAKS_TOMB_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490478,
"componentId": 501008,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -104,7 +122,25 @@ export const KAWAKS_TOMB_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495472,
"componentId": 506644,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -164,7 +200,25 @@ export const KAWAKS_TOMB_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496297,
"componentId": 507788,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -401,6 +455,24 @@ export const KAWAKS_TOMB_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496027,
"componentId": 507420,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -50,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -429,6 +501,24 @@ export const KAWAKS_TOMB_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496028,
"componentId": 507422,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -18000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -450,7 +540,25 @@ export const KAWAKS_TOMB_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496030,
"componentId": 507425,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kawaks-tomb-104228-spell-496031",
@@ -478,6 +586,24 @@ export const KAWAKS_TOMB_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496031,
"componentId": 507427,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -10,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -762,7 +888,25 @@ export const KAWAKS_TOMB_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496302,
"componentId": 507792,
"spellLevel": 10,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -822,7 +966,25 @@ export const KAWAKS_TOMB_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496297,
"componentId": 507788,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -922,7 +1084,25 @@ export const KAWAKS_TOMB_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496296,
"componentId": 507788,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kawaks-tomb-spell-496048",
@@ -942,7 +1122,25 @@ export const KAWAKS_TOMB_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496048,
"componentId": 507434,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -2465,6 +2663,6 @@ export const KAWAKS_TOMB_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498441,
"componentId": 620906,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -104,7 +122,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498441,
"componentId": 620906,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -164,7 +200,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -241,6 +295,24 @@ export const KULECH_BONES_NEST_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496328,
"componentId": 506500,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -1500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -262,7 +334,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496404,
"componentId": 506501,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -750,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kulech-bones-nest-107306-spell-496405",
@@ -290,6 +380,24 @@ export const KULECH_BONES_NEST_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496405,
"componentId": 506503,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -2,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -361,7 +469,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496409,
"componentId": 507955,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -421,7 +547,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499434,
"componentId": 622862,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kulech-bones-nest-spell-499435",
@@ -441,7 +585,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499435,
"componentId": 622881,
"spellLevel": 10,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -501,7 +663,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
8000,
8000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499420,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -561,7 +741,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499437,
"componentId": 621378,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kulech-bones-nest-spell-499438",
@@ -581,7 +779,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499438,
"componentId": 622862,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -641,7 +857,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498308,
"componentId": 620886,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kulech-bones-nest-spell-499440",
@@ -661,7 +895,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499440,
"componentId": 621378,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -761,7 +1013,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499459,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-kulech-bones-nest-spell-499461",
@@ -781,7 +1051,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499461,
"componentId": 622931,
"spellLevel": 9,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -841,7 +1129,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498307,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -901,7 +1207,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499464,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -961,7 +1285,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498307,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1021,7 +1363,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498307,
"componentId": 622862,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1081,7 +1441,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499445,
"componentId": 622862,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1141,7 +1519,25 @@ export const KULECH_BONES_NEST_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 499441,
"componentId": 621378,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -2911,6 +3307,6 @@ export const KULECH_BONES_NEST_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494440,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-lair-demon-dragon-spell-494441",
@@ -64,7 +82,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494441,
"componentId": 504966,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -2000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -124,7 +160,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494445,
"componentId": 504976,
"spellLevel": 1,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -2000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -224,7 +278,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494452,
"componentId": 502683,
"spellLevel": 1600,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -284,7 +356,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494435,
"componentId": 500972,
"spellLevel": 2,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -344,7 +434,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494447,
"componentId": 502439,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -524,7 +632,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494480,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -584,7 +710,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490478,
"componentId": 501008,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -684,7 +828,25 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494433,
"componentId": 502683,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1923,6 +2085,6 @@ export const LAIR_DEMON_DRAGON_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -55,7 +55,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490476,
"componentId": 500917,
"spellLevel": 200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-100611-debuff-490480",
@@ -108,6 +126,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490489,
"componentId": 501010,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -218,6 +254,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491104,
"componentId": 501479,
"spellLevel": 350,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -278,7 +332,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490517,
"componentId": 501023,
"spellLevel": 8,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -338,7 +410,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490476,
"componentId": 500917,
"spellLevel": 250,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -398,7 +488,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490582,
"componentId": 501046,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490586",
@@ -418,7 +526,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490586,
"componentId": 501050,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -497,6 +623,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490489,
"componentId": 501010,
"spellLevel": 300,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -525,6 +669,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490633,
"componentId": 501090,
"spellLevel": 300,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -578,7 +740,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490637,
"componentId": 501093,
"spellLevel": 120,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-100154-aura-490652",
@@ -745,7 +925,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 80,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -805,7 +1003,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490520,
"componentId": 500962,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490521",
@@ -825,7 +1041,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490521,
"componentId": 500963,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490477",
@@ -845,7 +1079,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -905,7 +1157,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490521,
"componentId": 500963,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-491020",
@@ -925,7 +1195,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491020,
"componentId": 500972,
"spellLevel": 15,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490522",
@@ -945,7 +1233,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.7,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490522,
"componentId": 500964,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.7,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.7,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1005,7 +1311,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491020,
"componentId": 500972,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1065,7 +1389,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491020,
"componentId": 500972,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490525",
@@ -1085,7 +1427,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490525,
"componentId": 500967,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-spell-490520",
@@ -1105,7 +1465,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490520,
"componentId": 500962,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1217,7 +1595,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 70,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-100138-spell-490478",
@@ -1238,7 +1634,25 @@ export const MYSTIC_ALTAR_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490478,
"componentId": 501008,
"spellLevel": 70,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-mystic-altar-100138-spell-490520",
@@ -1266,6 +1680,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490520,
"componentId": 500962,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1294,6 +1726,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490522,
"componentId": 500964,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.7,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.7,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1347,6 +1797,24 @@ export const MYSTIC_ALTAR_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 8
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490525,
"componentId": 500967,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -4259,6 +4727,6 @@ export const MYSTIC_ALTAR_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -61,6 +61,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491903,
"componentId": 502081,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -15,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -114,6 +132,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491988,
"componentId": 502203,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -900,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -337,6 +373,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492232,
"componentId": 502431,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -493,7 +547,25 @@ export const PASPERS_SHRINE_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490529,
"componentId": 500972,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -553,7 +625,25 @@ export const PASPERS_SHRINE_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492027,
"componentId": 502234,
"spellLevel": 700,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -613,7 +703,25 @@ export const PASPERS_SHRINE_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492027,
"componentId": 502234,
"spellLevel": 700,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -689,6 +797,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"warningMs": 1800,
"length": 6,
"angleDegrees": 95
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490521,
"componentId": 500963,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -885,6 +1011,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 491903,
"componentId": 502081,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -15,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -995,6 +1139,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492232,
"componentId": 502431,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -1247,7 +1409,25 @@ export const PASPERS_SHRINE_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490530,
"componentId": 500972,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1347,7 +1527,25 @@ export const PASPERS_SHRINE_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492027,
"componentId": 502234,
"spellLevel": 700,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1407,7 +1605,25 @@ export const PASPERS_SHRINE_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490594,
"componentId": 500972,
"spellLevel": 33,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1534,6 +1750,24 @@ export const PASPERS_SHRINE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492232,
"componentId": 502431,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -4206,6 +4440,6 @@ export const PASPERS_SHRINE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -244,7 +244,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496048,
"componentId": 507434,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -304,7 +322,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496049,
"componentId": 507435,
"spellLevel": 7,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -404,7 +440,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496052,
"componentId": 507438,
"spellLevel": 70,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -464,7 +518,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496053,
"componentId": 507439,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": -20,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-raksha-temple-spell-496054",
@@ -484,7 +556,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496054,
"componentId": 507440,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -624,7 +714,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496048,
"componentId": 507434,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -684,7 +792,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496049,
"componentId": 507435,
"spellLevel": 7,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -784,7 +910,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496052,
"componentId": 507438,
"spellLevel": 70,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -844,7 +988,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496053,
"componentId": 507439,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": -20,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-raksha-temple-spell-496054",
@@ -864,7 +1026,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496054,
"componentId": 507440,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1004,7 +1184,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492872,
"componentId": 500963,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1144,7 +1342,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496053,
"componentId": 507439,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": -20,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-raksha-temple-spell-496054",
@@ -1164,7 +1380,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496054,
"componentId": 507440,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1264,7 +1498,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496049,
"componentId": 507435,
"spellLevel": 7,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1364,7 +1616,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496052,
"componentId": 507438,
"spellLevel": 70,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1424,7 +1694,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496053,
"componentId": 507439,
"spellLevel": 30,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": -20,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-raksha-temple-spell-496054",
@@ -1444,7 +1732,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496054,
"componentId": 507440,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1584,7 +1890,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496058,
"componentId": 507443,
"spellLevel": 30,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1644,7 +1968,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
2500,
2500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496057,
"componentId": 507443,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1784,7 +2126,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496062,
"componentId": 507432,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1884,7 +2244,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.2,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496062,
"componentId": 507432,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -2039,6 +2417,24 @@ export const RAKSHA_TEMPLE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495545,
"componentId": 506802,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -2067,6 +2463,24 @@ export const RAKSHA_TEMPLE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495591,
"componentId": 506803,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -2095,6 +2509,24 @@ export const RAKSHA_TEMPLE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 495996,
"componentId": 507367,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -15,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -2116,7 +2548,25 @@ export const RAKSHA_TEMPLE_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 496112,
"componentId": 507524,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -3,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 3,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -3545,6 +3995,6 @@ export const RAKSHA_TEMPLE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -1131,10 +1131,10 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"report": "src/assets/game/dungeons/treasure-trove/instance-validation-report.json",
"blockers": [],
"warnings": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"All valid static WDB ROS placements are assembled; filename-only dynamic candidates remain static until script evidence maps their behavior.",
"Collision initially includes decorative primary-ROS geometry.",
"Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work.",
"Source-anchor support pads preserve combat and entrance coordinates where modular WDB sections or primary-ROS collision leave navmesh gaps; full secondary placement assembly remains a visual follow-up.",
"The full WDB static assembly replaces the former source-anchor support pads; source-backed switch and drawbridge behavior is represented by encounter world objects pending original object-mesh animation mapping.",
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
]
}
@@ -2080,7 +2080,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"fog": {
"color": "#0b0b10",
"near": 34,
"far": 684
"far": 1195
},
"ambientLight": {
"color": "#9d96a7",
@@ -2109,8 +2109,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"additiveNames": []
},
"map": {
"width": 494,
"height": 634,
"width": 1013,
"height": 974,
"padding": 24,
"contours": []
}
@@ -2120,26 +2120,124 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
{
"id": "visual-000",
"fileName": "the-origin-visual.glb",
"checksum": "a6db29db31f88a45bd082d27130725d70b2fa1db82030530750492a54b3f9bf8",
"size": 251132,
"triangleCount": 2664
"checksum": "d1ded0316b2cbaa07458c56864c47c01afaa85e4f33dd13f5d95984d7305fb92",
"size": 28683180,
"triangleCount": 1176881
}
],
"collision": [
{
"id": "collision-000",
"fileName": "the-origin-collision.glb",
"checksum": "7cc2e9dfc649742d546a7771fb836cde3e5f708f2a7088bfc111457eb516c17a",
"size": 72976,
"triangleCount": 2664
"fileName": "the-origin-wdb-collision-000.glb",
"checksum": "7eb6e18cf11ac9a15b24c6300f8c62773df9d2cb045b16c9c4732e18016b18e7",
"size": 2703604,
"triangleCount": 78675
},
{
"id": "collision-001",
"fileName": "the-origin-wdb-collision-001.glb",
"checksum": "31ec6603c7c100af9c0177cbbae3f13f029266a13c5b9690731089d8193deeb2",
"size": 2757368,
"triangleCount": 83558
},
{
"id": "collision-002",
"fileName": "the-origin-wdb-collision-002.glb",
"checksum": "ccce739574edbeef58ed1bc502d9be77cb0565213c381113bcdb9944323b0be0",
"size": 2538872,
"triangleCount": 84659
},
{
"id": "collision-003",
"fileName": "the-origin-wdb-collision-003.glb",
"checksum": "7f83d131a71bee2fb3f47e7c2fe94c39cb3f3f9bd7f333a994627528465e07d1",
"size": 2438228,
"triangleCount": 61945
},
{
"id": "collision-004",
"fileName": "the-origin-wdb-collision-004.glb",
"checksum": "044bc594aa521c7b4a441b477c369b03083189068dbe6b17572851beb23531b1",
"size": 3127712,
"triangleCount": 87835
},
{
"id": "collision-005",
"fileName": "the-origin-wdb-collision-005.glb",
"checksum": "8cd5228f6443dceb983e5e8c457ed97fdfdac2083049dce036157f3573a0f9ff",
"size": 2827072,
"triangleCount": 88513
},
{
"id": "collision-006",
"fileName": "the-origin-wdb-collision-006.glb",
"checksum": "abca4c48c25d495e40399dfd5a9fbcab8941a056af1b8159ff0b4d3f453b0b52",
"size": 3210756,
"triangleCount": 86404
},
{
"id": "collision-007",
"fileName": "the-origin-wdb-collision-007.glb",
"checksum": "9693440956d31489d888e30fd97de0427e9bf65bd02174a6bbad34b78a8b6a29",
"size": 2364920,
"triangleCount": 66883
},
{
"id": "collision-008",
"fileName": "the-origin-wdb-collision-008.glb",
"checksum": "89f95739e8ac2384d0c125c1ab013e1803031f67f11d35fa94355719a4db2251",
"size": 2811712,
"triangleCount": 87544
},
{
"id": "collision-009",
"fileName": "the-origin-wdb-collision-009.glb",
"checksum": "2bbddf84d5754100c3499d5e35cb1da45dfa920940c59a3007c0848b975d609d",
"size": 3077192,
"triangleCount": 84937
},
{
"id": "collision-010",
"fileName": "the-origin-wdb-collision-010.glb",
"checksum": "73012e2dcb9892ad3aeccd3536e0c36f4365276226b265f82326a25e45d4ec4e",
"size": 2989496,
"triangleCount": 88994
},
{
"id": "collision-011",
"fileName": "the-origin-wdb-collision-011.glb",
"checksum": "d19124247096c475e344ae467274be6a0320192cf2292f891970b102eb4fc1be",
"size": 3128952,
"triangleCount": 89430
},
{
"id": "collision-012",
"fileName": "the-origin-wdb-collision-012.glb",
"checksum": "23870416ad12c179e576959df6f72370e209365e03d3510ceb3b1745f30fe930",
"size": 2825584,
"triangleCount": 89857
},
{
"id": "collision-013",
"fileName": "the-origin-wdb-collision-013.glb",
"checksum": "5c9bb9f4b5d118c834f3b1d0b515b30d553a1e21423767c9e7dafcbd6d78d1c2",
"size": 2856020,
"triangleCount": 85511
},
{
"id": "collision-014",
"fileName": "the-origin-wdb-collision-014.glb",
"checksum": "0743835882534516c770766cbbf3585386e05e96e95a2bdc24e3ca5d90482c57",
"size": 533188,
"triangleCount": 12136
}
],
"navigation": {
"id": "navigation",
"fileName": "the-origin-navigation.glb",
"checksum": "a9b97bc8cc62279486292d42aad3ae0a8e70638e7dc223dd0c392f32b3811781",
"size": 54768,
"triangleCount": 1213
"checksum": "70c0a78073d7c3cd7a95e0dc3531c25c0de9815376001f9b80cfbb59a72596cd",
"size": 4667144,
"triangleCount": 97214
}
},
"transform": {
@@ -2158,22 +2256,22 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
},
"bounds": {
"min": [
-386.52997,
-46.51506,
-324.07977
-630.039165,
-94.763691,
-431.593932
],
"max": [
107.21951,
11.46503,
309.25803
382.64336,
260.724523,
541.440422
]
},
"entrance": {
"name": "The Origin Entrance",
"footPosition": [
-380.368111,
-7.734726,
-292.973182
-377.445404,
-13.802109,
-267.742004
],
"forward": [
-0.292372,
@@ -2187,9 +2285,9 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"id": "entrance",
"name": "The Origin Entrance",
"center": [
-380.368111,
-7.734726,
-292.973182
-377.445404,
-13.802109,
-267.742004
],
"radius": 24
}
@@ -2217,10 +2315,10 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"report": "src/assets/game/dungeons/the-origin/instance-validation-report.json",
"blockers": [],
"warnings": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"All valid static WDB ROS placements are assembled; filename-only dynamic candidates remain static until script evidence maps their behavior.",
"Collision initially includes decorative primary-ROS geometry.",
"Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work.",
"Source-anchor support pads preserve combat and entrance coordinates where modular WDB sections or primary-ROS collision leave navmesh gaps; full secondary placement assembly remains a visual follow-up.",
"The full static WDB collision requires a tiled monotone Recast build because the solo watershed contour triangulator rejects this source geometry.",
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
]
}
@@ -4155,7 +4253,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"fog": {
"color": "#0b0b10",
"near": 34,
"far": 635
"far": 1461
},
"ambientLight": {
"color": "#9d96a7",
@@ -4184,8 +4282,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"additiveNames": []
},
"map": {
"width": 377,
"height": 644,
"width": 1162,
"height": 1266,
"padding": 24,
"contours": []
}
@@ -4195,26 +4293,68 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
{
"id": "visual-000",
"fileName": "raksha-temple-visual.glb",
"checksum": "3439e64f2a49d0545651bac35276867af83c3eb57b3c859f767de0c49c3dce66",
"size": 937772,
"triangleCount": 4302
"checksum": "3a61098e772a0c656aa25d88aee357f78b84c738e45150fca6fd5059c2b675f7",
"size": 23554528,
"triangleCount": 605820
}
],
"collision": [
{
"id": "collision-000",
"fileName": "raksha-temple-collision.glb",
"checksum": "a0b19ff9c1c5ef6abfbaee6080bec91bdcdc8eb3d2b8f6b3e3fad092fd71e548",
"size": 161552,
"triangleCount": 4302
"fileName": "raksha-temple-wdb-collision-000.glb",
"checksum": "22cfdc239b638f906f3cb77c9fb7b94c66eb483cfcbbe59e059bf8d5cbaad8a0",
"size": 2584136,
"triangleCount": 87109
},
{
"id": "collision-001",
"fileName": "raksha-temple-wdb-collision-001.glb",
"checksum": "3a3f4448aa92276faa94c8b99ae49aa48660423fb42edf0f2403615e5e3b3453",
"size": 2785392,
"triangleCount": 86228
},
{
"id": "collision-002",
"fileName": "raksha-temple-wdb-collision-002.glb",
"checksum": "b4b9e92646bbaf290263b601e7af7d6547f85a7b3fdae4b931ab930a263c2a36",
"size": 2788332,
"triangleCount": 89719
},
{
"id": "collision-003",
"fileName": "raksha-temple-wdb-collision-003.glb",
"checksum": "846ac1301549d8e05c8efb59b9ddfc0ebec2fb36cf216a0d5c143ab3eb5ae423",
"size": 2676988,
"triangleCount": 89776
},
{
"id": "collision-004",
"fileName": "raksha-temple-wdb-collision-004.glb",
"checksum": "100f27d9a2c46f1d30b0e395d60d99a3b62dfabc82474ed574f137c1513fb1bb",
"size": 2328332,
"triangleCount": 85897
},
{
"id": "collision-005",
"fileName": "raksha-temple-wdb-collision-005.glb",
"checksum": "cb22cb168e559fa8ed6983109bdc02bc4fd11f7d8834649bfab1d9cd0448ba87",
"size": 2960700,
"triangleCount": 89896
},
{
"id": "collision-006",
"fileName": "raksha-temple-wdb-collision-006.glb",
"checksum": "d8f6bd33ada7b6e0ee1b7cb4aacdf1c32e8ceb614f45d60ac2f24c9f1e0cef18",
"size": 2345864,
"triangleCount": 77195
}
],
"navigation": {
"id": "navigation",
"fileName": "raksha-temple-navigation.glb",
"checksum": "21e0aa6d58f8958fc6a21912cf72ec86188c13e87d70695504cee9e910b754b0",
"size": 60088,
"triangleCount": 1287
"checksum": "1e1d1ad26907dc8c4fd0a917dba40b52d2aaaea4a0335af06871ee28230eddf1",
"size": 8686564,
"triangleCount": 180952
}
},
"transform": {
@@ -4233,22 +4373,22 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
},
"bounds": {
"min": [
-327.82031,
-15.38933,
-321.33469
-764.460151,
-118.862537,
-658.700334
],
"max": [
49.02461,
51.30486,
322.06534
397.479134,
368.792163,
606.421358
]
},
"entrance": {
"name": "Raksha Temple Entrance",
"footPosition": [
-0.020312,
21.013313,
11.334656
0.021786,
10.664467,
17.560761
],
"forward": [
-0.173648,
@@ -4262,9 +4402,9 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"id": "entrance",
"name": "Raksha Temple Entrance",
"center": [
-0.020312,
21.013313,
11.334656
0.021786,
10.664467,
17.560761
],
"radius": 24
}
@@ -4292,10 +4432,10 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
"report": "src/assets/game/dungeons/raksha-temple/instance-validation-report.json",
"blockers": [],
"warnings": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"All valid static WDB ROS placements are assembled; filename-only dynamic candidates remain static until script evidence maps their behavior.",
"Collision initially includes decorative primary-ROS geometry.",
"Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work.",
"Source-anchor support pads preserve combat and entrance coordinates where modular WDB sections or primary-ROS collision leave navmesh gaps; full secondary placement assembly remains a visual follow-up.",
"The full WDB static assembly replaces the former source-anchor support pads; entrance and combat anchors still require live collision/navigation review.",
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
]
}
File diff suppressed because it is too large. Load diff
@@ -59,6 +59,24 @@ export const SAND_VORTEX_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 3
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494642,
"componentId": 502512,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -87,6 +105,24 @@ export const SAND_VORTEX_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 8
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494643,
"componentId": 502514,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -231,6 +267,6 @@ export const SAND_VORTEX_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -164,7 +164,25 @@ export const SARDO_CASTLE_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497667,
"componentId": 509726,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-sardo-castle-spell-497668",
@@ -184,7 +202,25 @@ export const SARDO_CASTLE_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497668,
"componentId": 509727,
"spellLevel": 10,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -244,7 +280,25 @@ export const SARDO_CASTLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497620,
"componentId": 509674,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-sardo-castle-spell-497619",
@@ -264,7 +318,25 @@ export const SARDO_CASTLE_ENTITIES = {
14000,
14000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497619,
"componentId": 509673,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -324,7 +396,25 @@ export const SARDO_CASTLE_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497622,
"componentId": 509718,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-sardo-castle-spell-497621",
@@ -344,7 +434,25 @@ export const SARDO_CASTLE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497621,
"componentId": 509716,
"spellLevel": 70,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -404,7 +512,25 @@ export const SARDO_CASTLE_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497622,
"componentId": 509718,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-sardo-castle-spell-497621",
@@ -424,7 +550,25 @@ export const SARDO_CASTLE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497621,
"componentId": 509716,
"spellLevel": 70,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -484,7 +628,25 @@ export const SARDO_CASTLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497672,
"componentId": 509733,
"spellLevel": 35,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -584,7 +746,25 @@ export const SARDO_CASTLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497673,
"componentId": 509734,
"spellLevel": 35,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -738,6 +918,24 @@ export const SARDO_CASTLE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497652,
"componentId": 509747,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -766,6 +964,24 @@ export const SARDO_CASTLE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497653,
"componentId": 509748,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -10,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -851,7 +1067,25 @@ export const SARDO_CASTLE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497620,
"componentId": 509674,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-sardo-castle-spell-497619",
@@ -871,7 +1105,25 @@ export const SARDO_CASTLE_ENTITIES = {
14000,
14000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497619,
"componentId": 509673,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1826,6 +2078,6 @@ export const SARDO_CASTLE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493446,
"componentId": 503354,
"spellLevel": 80,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-493447",
@@ -64,7 +82,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.6,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493447,
"componentId": 503679,
"spellLevel": 6,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.6,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.6,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -124,7 +160,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492756,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-492755",
@@ -144,7 +198,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492755,
"componentId": 503019,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -244,7 +316,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492755,
"componentId": 503019,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-492756",
@@ -264,7 +354,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492756,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -324,7 +432,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493504,
"componentId": 502443,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-493488",
@@ -344,7 +470,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493488,
"componentId": 502443,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -404,7 +548,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.6,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493447,
"componentId": 503679,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.6,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.6,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -464,7 +626,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493446,
"componentId": 503354,
"spellLevel": 40,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -524,7 +704,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490475,
"componentId": 500916,
"spellLevel": 1,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490471",
@@ -544,7 +742,25 @@ export const THE_ORIGIN_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490471,
"componentId": 500913,
"spellLevel": 1,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -644,7 +860,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492756,
"componentId": 500972,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-493446",
@@ -664,7 +898,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493446,
"componentId": 503354,
"spellLevel": 10,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -724,7 +976,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493488,
"componentId": 502443,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -784,7 +1054,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493488,
"componentId": 502443,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-493504",
@@ -804,7 +1092,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493504,
"componentId": 502443,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -864,7 +1170,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493446,
"componentId": 503354,
"spellLevel": 15,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-492756",
@@ -884,7 +1208,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492756,
"componentId": 500972,
"spellLevel": 15,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1024,7 +1366,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493463,
"componentId": 500972,
"spellLevel": 50,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490524",
@@ -1044,7 +1404,25 @@ export const THE_ORIGIN_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490524,
"componentId": 500966,
"spellLevel": 10,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1104,7 +1482,25 @@ export const THE_ORIGIN_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490582,
"componentId": 501046,
"spellLevel": 15,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490586",
@@ -1124,7 +1520,25 @@ export const THE_ORIGIN_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490586,
"componentId": 501050,
"spellLevel": 15,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1184,7 +1598,25 @@ export const THE_ORIGIN_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490556,
"componentId": 501032,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490557",
@@ -1204,7 +1636,25 @@ export const THE_ORIGIN_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 1.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490557,
"componentId": 501033,
"spellLevel": 20,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490637",
@@ -1224,7 +1674,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490637,
"componentId": 501093,
"spellLevel": 40,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1324,7 +1792,25 @@ export const THE_ORIGIN_ENTITIES = {
3000,
3000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490629,
"componentId": 501086,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-493462",
@@ -1344,7 +1830,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493462,
"componentId": 501089,
"spellLevel": 1300,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-spell-490630",
@@ -1364,7 +1868,25 @@ export const THE_ORIGIN_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490630,
"componentId": 501087,
"spellLevel": 1200,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1551,7 +2073,25 @@ export const THE_ORIGIN_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493663,
"componentId": 503912,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-102349-spell-493664",
@@ -1572,7 +2112,25 @@ export const THE_ORIGIN_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493664,
"componentId": 503913,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -6500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-102349-aura-493665",
@@ -1707,7 +2265,25 @@ export const THE_ORIGIN_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493590,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-102307-aura-493592",
@@ -1753,7 +2329,25 @@ export const THE_ORIGIN_ENTITIES = {
6500,
6500
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493649,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-the-origin-102307-aura-493650",
@@ -1831,6 +2425,24 @@ export const THE_ORIGIN_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493652,
"componentId": 504022,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -8000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -3984,6 +4596,6 @@ export const THE_ORIGIN_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498254,
"componentId": 508181,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497622",
@@ -64,7 +82,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497622,
"componentId": 509718,
"spellLevel": 80,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497397",
@@ -84,7 +120,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497397,
"componentId": 509285,
"spellLevel": 200,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -144,7 +198,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497397,
"componentId": 509285,
"spellLevel": 180,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497399",
@@ -164,7 +236,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497399,
"componentId": 509293,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -224,7 +314,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497401,
"componentId": 509335,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497397",
@@ -244,7 +352,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497397,
"componentId": 509285,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -304,7 +430,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497405,
"componentId": 509342,
"spellLevel": 220,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497406",
@@ -324,7 +468,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497406,
"componentId": 508181,
"spellLevel": 50,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -384,7 +546,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497405,
"componentId": 509342,
"spellLevel": 220,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497585",
@@ -404,7 +584,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
11000,
11000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497585,
"componentId": 620105,
"spellLevel": 200,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -464,7 +662,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497405,
"componentId": 509342,
"spellLevel": 230,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497586",
@@ -484,7 +700,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497586,
"componentId": 620105,
"spellLevel": 130,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -584,7 +818,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497405,
"componentId": 509342,
"spellLevel": 400,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498256",
@@ -604,7 +856,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
9000,
9000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498256,
"componentId": 620813,
"spellLevel": 120,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498257",
@@ -624,7 +894,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498257,
"componentId": 620814,
"spellLevel": 120,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -684,7 +972,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497405,
"componentId": 509342,
"spellLevel": 410,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-497586",
@@ -704,7 +1010,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497586,
"componentId": 620105,
"spellLevel": 410,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -804,7 +1128,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498261,
"componentId": 620818,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498482",
@@ -824,7 +1166,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
11000,
11000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498482,
"componentId": 620820,
"spellLevel": 150,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -924,7 +1284,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498264,
"componentId": 620822,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498266",
@@ -944,7 +1322,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498266,
"componentId": 620845,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498267",
@@ -964,7 +1360,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498267,
"componentId": 620845,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498268",
@@ -984,7 +1398,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
11000,
11000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498268,
"componentId": 620848,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1044,7 +1476,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498266,
"componentId": 620845,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1104,7 +1554,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498264,
"componentId": 620822,
"spellLevel": 100,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1164,7 +1632,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498271,
"componentId": 620852,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1264,7 +1750,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498254,
"componentId": 508181,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1324,7 +1828,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
12000,
12000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 497025,
"componentId": 508731,
"spellLevel": 40,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -500,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-tomb-seven-heroes-spell-498295",
@@ -1344,7 +1866,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498295,
"componentId": 620867,
"spellLevel": 400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1404,7 +1944,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498254,
"componentId": 508181,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1464,7 +2022,25 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 498254,
"componentId": 508181,
"spellLevel": 100,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -4330,6 +4906,6 @@ export const TOMB_SEVEN_HEROES_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -112,7 +130,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 15,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-101533-aura-492431",
@@ -183,7 +219,25 @@ export const TREASURE_TROVE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492434,
"componentId": 503002,
"spellLevel": 25,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -308,6 +362,24 @@ export const TREASURE_TROVE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492438,
"componentId": 501098,
"spellLevel": 24,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -393,7 +465,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492857,
"componentId": 503019,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-490477",
@@ -413,7 +503,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492858",
@@ -433,7 +541,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492858,
"componentId": 500963,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -493,7 +619,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492857,
"componentId": 503019,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-490477",
@@ -513,7 +657,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-490521",
@@ -533,7 +695,25 @@ export const TREASURE_TROVE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490521,
"componentId": 500963,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -593,7 +773,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492839,
"componentId": 501106,
"spellLevel": 30,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-490477",
@@ -613,7 +811,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492861",
@@ -633,7 +849,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492861,
"componentId": 500963,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -693,7 +927,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492857,
"componentId": 503019,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-490477",
@@ -713,7 +965,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -773,7 +1043,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492839,
"componentId": 501106,
"spellLevel": 30,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492863",
@@ -793,7 +1081,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492863,
"componentId": 503210,
"spellLevel": 9,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492862",
@@ -813,7 +1119,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492862,
"componentId": 500963,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -873,7 +1197,25 @@ export const TREASURE_TROVE_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490478,
"componentId": 501008,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492872",
@@ -893,7 +1235,25 @@ export const TREASURE_TROVE_ENTITIES = {
13000,
13000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492872,
"componentId": 500963,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -953,7 +1313,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492864,
"componentId": 500972,
"spellLevel": 13,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492865",
@@ -973,7 +1351,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492865,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492863",
@@ -993,7 +1389,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492863,
"componentId": 503210,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1053,7 +1467,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1113,7 +1545,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1173,7 +1623,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492863,
"componentId": 503210,
"spellLevel": 20,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492862",
@@ -1193,7 +1661,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492862,
"componentId": 500963,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1253,7 +1739,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1313,7 +1817,25 @@ export const TREASURE_TROVE_ENTITIES = {
6000,
6000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490478,
"componentId": 501008,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-spell-492858",
@@ -1333,7 +1855,25 @@ export const TREASURE_TROVE_ENTITIES = {
15000,
15000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492858,
"componentId": 500963,
"spellLevel": 10,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1401,7 +1941,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 50,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-101534-spell-492428",
@@ -1422,7 +1980,25 @@ export const TREASURE_TROVE_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492428,
"componentId": 502580,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.2,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-treasure-trove-101534-debuff-492429",
@@ -1482,6 +2058,24 @@ export const TREASURE_TROVE_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492430,
"componentId": 503002,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -1000,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -1582,7 +2176,25 @@ export const TREASURE_TROVE_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492893,
"componentId": 502439,
"spellLevel": 1000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1642,7 +2254,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1702,7 +2332,25 @@ export const TREASURE_TROVE_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 1,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490477,
"componentId": 501006,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -7303,6 +7951,6 @@ export const TREASURE_TROVE_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -101,6 +101,6 @@ export const WARNORKEN_ARENA_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -56,7 +56,25 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
20000,
20000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490634,
"componentId": 501091,
"spellLevel": 150,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-ystra-labyrinth-boss1-102195-aura-491008",
@@ -109,6 +127,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492307,
"componentId": 502523,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -10,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -137,6 +173,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492308,
"componentId": 502524,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -50,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -343,6 +397,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490539,
"componentId": 500962,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -389,7 +461,25 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492310,
"componentId": 501091,
"spellLevel": 150,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-ystra-labyrinth-boss1-101432-spell-492312",
@@ -417,6 +507,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492312,
"componentId": 502439,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -470,6 +578,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492314,
"componentId": 502439,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -498,6 +624,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492334,
"componentId": 501097,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -569,7 +713,25 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490524,
"componentId": 500966,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-ystra-labyrinth-boss1-101433-aura-491008",
@@ -672,6 +834,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492323,
"componentId": 500962,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -725,6 +905,24 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
"warningMs": 1800,
"length": 30,
"width": 2
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492327,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -852,6 +1050,6 @@ export const YSTRA_LABYRINTH_BOSS1_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -62,6 +62,24 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490539,
"componentId": 500962,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -108,7 +126,25 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492310,
"componentId": 501091,
"spellLevel": 150,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-ystra-labyrinth-boss2-102196-spell-492312",
@@ -136,6 +172,24 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492312,
"componentId": 502439,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -189,6 +243,24 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 7.5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492314,
"componentId": 502439,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -217,6 +289,24 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 15
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492334,
"componentId": 501097,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -5,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -304,6 +394,6 @@ export const YSTRA_LABYRINTH_BOSS2_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -55,7 +55,25 @@ export const YSTRA_LABYRINTH_BOSS3_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 490524,
"componentId": 500966,
"spellLevel": 0,
"calculationPath": "magic",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-ystra-labyrinth-boss3-102197-aura-491008",
@@ -158,6 +176,24 @@ export const YSTRA_LABYRINTH_BOSS3_ENTITIES = {
"anchor": "source",
"warningMs": 1800,
"radius": 5
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492323,
"componentId": 500962,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -1,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 1,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
@@ -211,6 +247,24 @@ export const YSTRA_LABYRINTH_BOSS3_ENTITIES = {
"warningMs": 1800,
"length": 30,
"width": 2
},
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 492327,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
@@ -298,6 +352,6 @@ export const YSTRA_LABYRINTH_BOSS3_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -48,6 +48,6 @@ export const ZURHIDON_STRONGHOLD_124_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
@@ -44,7 +44,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494435,
"componentId": 500972,
"spellLevel": 2,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -104,7 +122,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494447,
"componentId": 502439,
"spellLevel": 1400,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -204,7 +240,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 2000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494733",
@@ -224,7 +278,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
8000,
8000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494733,
"componentId": 505375,
"spellLevel": 2000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -284,7 +356,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494709,
"componentId": 505354,
"spellLevel": 5500,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -50,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494710",
@@ -304,7 +394,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 2500,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -364,7 +472,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 2500,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494709",
@@ -384,7 +510,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494709,
"componentId": 505354,
"spellLevel": 5000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -50,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -484,7 +628,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494701,
"componentId": 500972,
"spellLevel": 3,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494702",
@@ -504,7 +666,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494702,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -764,7 +944,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.4,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494703,
"componentId": 503083,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.4,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.4,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494704",
@@ -784,7 +982,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494704,
"componentId": 500972,
"spellLevel": 5,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494705",
@@ -804,7 +1020,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494705,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -904,7 +1138,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
8000,
8000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494733,
"componentId": 505375,
"spellLevel": 1000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -964,7 +1216,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494727,
"componentId": 503019,
"spellLevel": 49,
"calculationPath": "physical",
"powerKind": "source-points",
"damagePower": -100,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494710",
@@ -984,7 +1254,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 2000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1044,7 +1332,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494708,
"componentId": 500972,
"spellLevel": 1,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-493195",
@@ -1064,7 +1370,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
10000,
10000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 493195,
"componentId": 500972,
"spellLevel": 8,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1164,7 +1488,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494704,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1224,7 +1566,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494706,
"componentId": 500972,
"spellLevel": 0,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
},
{
"id": "rw-zurhidon-stronghold-spell-494702",
@@ -1244,7 +1604,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
5000,
5000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.8,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494702,
"componentId": 500972,
"spellLevel": 3,
"calculationPath": "physical",
"powerKind": "source-ratio",
"damagePower": -0.8,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.8,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1344,7 +1722,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494710,
"componentId": 502683,
"spellLevel": 1000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -1404,7 +1800,25 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
7000,
7000
],
"damageMultiplier": 0.85
"damageMultiplier": 0.85,
"runewakerFormula": {
"schemaVersion": 1,
"spellId": 494712,
"componentId": 505357,
"spellLevel": 1000,
"calculationPath": "magic",
"powerKind": "source-points",
"damagePower": -20,
"fixedValue": 0,
"dotBase": 0,
"runtimeBaselineMultiplier": 0.85,
"provenance": {
"sourceFile": "SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp",
"physicalFunctionLine": 3366,
"magicFunctionLine": 3651,
"magicObjectDatabase": "Resource/data/magicobject.db"
}
}
}
]
},
@@ -3193,6 +3607,6 @@ export const ZURHIDON_STRONGHOLD_POPULATION_PROVENANCE = {
"runeWakerIdsAreNamespaced": true,
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
"sourceSpellCatalogSha256": "36bb578bbdda61af6ab864084907b347ff9a4f4c2fff671a2cb6586d535666ec",
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; source fields are preserved and live damage uses the labeled HealerMan baseline adapter when native stats are unavailable."
} as const;
+6 -2
View File
@@ -5,6 +5,7 @@ import {
type WeaponStats,
} from "./itemStats";
import type { InventoryItem, ItemQuality } from "./lootTypes";
import { rollRunewakerBossLoot, runewakerBossHasLoot } from "./runewakerLootCatalog";
export interface LootCatalogItem {
readonly itemId: number;
@@ -58,7 +59,8 @@ export function bossLootTable(bossKey: string): BossLootTable | null {
}
export function bossHasLoot(bossKey: string): boolean {
return (bossLootByKey.get(bossKey)?.items.length ?? 0) > 0;
return (bossLootByKey.get(bossKey)?.items.length ?? 0) > 0
|| runewakerBossHasLoot(bossKey);
}
function boundedRandom(random: () => number): number {
@@ -98,7 +100,9 @@ export function rollBossLoot(
options: LootRollOptions = {},
): readonly InventoryItem[] {
const table = bossLootTable(bossKey);
if (!table?.items.length) return [];
if (!table?.items.length) {
return rollRunewakerBossLoot(bossKey, playerLevel, options);
}
const random = options.random ?? Math.random;
const acquiredAt = options.now ?? Date.now();
const rolls = randomInteger(table.rollsMin, table.rollsMax, random);
+15
View File
@@ -9,6 +9,7 @@ import {
onlineSessionOwnsWorldSimulation,
onlineSynchronizedMobTransform,
requestOnlineInteraction,
publishOnlinePartyTeleport,
} from "./onlineSessionRuntime";
const remotePlayer: OnlineSessionPlayer = {
@@ -75,4 +76,18 @@ describe("online session runtime", () => {
]);
expect(drainOnlineInteractions()).toEqual([]);
});
it("queues party transitions only for the activity authority", () => {
configureOnlineSessionRuntime({ active: true, authority: false, localAccountId: "local" });
expect(publishOnlinePartyTeleport([1, 2, 3], "cross-door", 50)).toBe(false);
configureOnlineSessionRuntime({ active: true, authority: true, localAccountId: "local" });
expect(publishOnlinePartyTeleport([1, 2, 3], "cross-door", 50)).toBe(true);
expect(drainOnlineInteractions()).toEqual([
expect.objectContaining({
kind: "party-teleport",
targetActorId: "party",
position: [1, 2, 3],
}),
]);
});
});
+20 -1
View File
@@ -117,7 +117,7 @@ export function onlineSynchronizedMobTransform(id: string): OnlineMobTransform |
return active && !authority ? mobTransforms.get(id) ?? null : null;
}
export function requestOnlineInteraction(kind: "loot" | "portal", targetActorId = ""): boolean {
export function requestOnlineInteraction(kind: "loot" | "portal" | "interact", targetActorId = ""): boolean {
if (!active || authority) return false;
interactionSequence += 1;
interactions.push({
@@ -130,6 +130,25 @@ export function requestOnlineInteraction(kind: "loot" | "portal", targetActorId
return true;
}
/** Queues a leader-only party transition for remote human members. */
export function publishOnlinePartyTeleport(
position: readonly [number, number, number],
actionId: string,
now = Date.now(),
): boolean {
if (!active || !authority || !localAccountId || !position.every(Number.isFinite)) return false;
interactionSequence += 1;
interactions.push({
clientEventId: `party-teleport:${actionId}:${now}:${interactionSequence}`,
kind: "party-teleport",
sourceActorId: onlinePlayerActorId(localAccountId),
targetActorId: "party",
position,
occurredAt: now,
});
return true;
}
export function drainOnlineInteractions(): readonly OnlineRelayEventInput[] {
const pending = interactions;
interactions = [];
+5 -2
View File
@@ -110,8 +110,11 @@ describe("party navigation fleet safety", () => {
}
}
expect(enabled).toHaveLength(31);
expect(fallback).toHaveLength(53);
// Raksha Temple and The Origin now ship their full source-assembled
// navigation geometry, which correctly moves both above the synchronous
// graph-build ceiling and onto the breadcrumb fallback path.
expect(enabled).toHaveLength(29);
expect(fallback).toHaveLength(55);
});
it("covers every Manastorm package and keeps actual synchronous geometry under budget", () => {
+86
View File
@@ -0,0 +1,86 @@
import { describe, expect, it } from "vitest";
import {
adaptRunewakerEnemyDamage,
resolveRunewakerNativeResolvedPower,
runewakerAttackDefenseRate,
runewakerOrgPower,
type RunewakerDamageFormulaSource,
} from "./runewakerCombatFormula";
function formula(
overrides: Partial<RunewakerDamageFormulaSource> = {},
): RunewakerDamageFormulaSource {
return {
schemaVersion: 1,
spellId: 492_289,
componentId: 502_503,
spellLevel: 6,
calculationPath: "physical",
powerKind: "source-ratio",
damagePower: -0.7,
fixedValue: 0,
dotBase: 0,
runtimeBaselineMultiplier: 0.85,
provenance: {
sourceFile: "MagicProcess.cpp",
physicalFunctionLine: 3366,
magicFunctionLine: 3651,
magicObjectDatabase: "magicobject.db",
},
...overrides,
};
}
describe("RuneWaker combat formula adapter", () => {
it("preserves source ratio powers against HealerMan's explicit baseline", () => {
expect(adaptRunewakerEnemyDamage({ formula: formula(), baselineDamage: 100 })).toMatchObject({
amount: 70,
fidelity: "source-ratio-adapter",
appliedMultiplier: 0.7,
});
});
it("labels point-valued powers as scaled adapters instead of native damage", () => {
const result = adaptRunewakerEnemyDamage({
formula: formula({ powerKind: "source-points", damagePower: -300 }),
baselineDamage: 100,
});
expect(result).toMatchObject({ amount: 85, fidelity: "scaled-point-adapter" });
expect(result.unavailableSourceInputs).toContain("RuneWaker ATK or MATK");
expect(result.unavailableSourceInputs).toContain("RuneWaker global NPC/PvP/AoE/target damage rates");
});
it("fails closed when no damage family was proven", () => {
expect(adaptRunewakerEnemyDamage({
formula: formula({ powerKind: "unresolved", runtimeBaselineMultiplier: 0 }),
baselineDamage: 100,
})).toMatchObject({ amount: 0, fidelity: "unsupported" });
});
it("implements the preserved org-power and attack-defense expressions", () => {
expect(runewakerOrgPower(-100, 5, 10)).toBe(-150);
expect(runewakerAttackDefenseRate(400, 300)).toBe(1);
expect(runewakerAttackDefenseRate(0, 10_000)).toBe(0.2);
});
it("requires every native rate in the source-shaped resolved-power path", () => {
expect(resolveRunewakerNativeResolvedPower({
power: -100,
fixedValue: -0.5,
fixedAttributeValue: 20,
fixedDamageSkillLevelPercent: 10,
spellLevel: 5,
attackerPower: 400,
defenderPower: 300,
criticalMultiplier: 2,
levelRate: 1,
resistanceRate: 0.8,
pvpOrNpcDamageRate: 1,
areaPowerRate: 1,
areaDefenseRate: 1,
attackTargetRate: 1,
attackerOriginDefenseRate: 1,
sharedDamageRate: 1,
})).toBe(184);
});
});
+188
View File
@@ -0,0 +1,188 @@
/**
* Portable evidence attached to an imported RuneWaker damage mechanic.
*
* `runtimeBaselineMultiplier` is deliberately named as an adapter value: it
* is not presented as a source coefficient when the original combat inputs
* are unavailable in HealerMan.
*/
export interface RunewakerDamageFormulaSource {
readonly schemaVersion: 1;
readonly spellId: number;
readonly componentId: number;
readonly spellLevel: number;
readonly calculationPath: "physical" | "magic" | "unknown";
readonly powerKind: "source-ratio" | "source-points" | "dot-only" | "unresolved";
readonly damagePower: number;
readonly fixedValue: number;
readonly dotBase: number;
readonly runtimeBaselineMultiplier: number;
readonly provenance: {
readonly sourceFile: string;
readonly physicalFunctionLine: number;
readonly magicFunctionLine: number;
readonly magicObjectDatabase: string;
};
}
export interface RunewakerNativeResolvedPowerInput {
/** The already-derived `Power` argument passed to CalPhyDamage/CalMagicDamage. */
readonly power: number;
readonly fixedValue: number;
readonly fixedAttributeValue: number;
readonly fixedDamageSkillLevelPercent: number;
readonly spellLevel: number;
readonly attackerPower: number;
readonly defenderPower: number;
readonly criticalMultiplier: number;
readonly levelRate: number;
readonly resistanceRate: number;
readonly pvpOrNpcDamageRate: number;
readonly areaPowerRate: number;
readonly areaDefenseRate: number;
readonly attackTargetRate: number;
readonly attackerOriginDefenseRate: number;
readonly sharedDamageRate: number;
}
export interface RunewakerDamageAdapterInput {
readonly formula: RunewakerDamageFormulaSource;
/** HealerMan's level-scaled enemy attack budget before an ability multiplier. */
readonly baselineDamage: number;
}
export interface RunewakerDamageAdapterResult {
readonly amount: number;
readonly fidelity: "source-ratio-adapter" | "scaled-point-adapter" | "unsupported";
readonly appliedMultiplier: number;
readonly mappedInputs: readonly string[];
readonly unavailableSourceInputs: readonly string[];
}
const MISSING_NATIVE_INPUTS = Object.freeze([
"RuneWaker ATK or MATK",
"RuneWaker DEF or MDEF",
"RuneWaker weapon or magic damage",
"RuneWaker FixType attribute value",
"RuneWaker level-difference rate table",
"RuneWaker resistance calculation",
"RuneWaker critical-power modifiers",
"RuneWaker global NPC/PvP/AoE/target damage rates",
] as const);
function finite(value: number, fallback = 0): number {
return Number.isFinite(value) ? value : fallback;
}
function nonNegative(value: number): number {
return Math.max(0, finite(value));
}
/** Exact ATK/DEF or MATK/MDEF ratio shared by the preserved source functions. */
export function runewakerAttackDefenseRate(attackerPower: number, defenderPower: number): number {
const attack = nonNegative(attackerPower) + 100;
const defense = nonNegative(defenderPower) + 200;
return Math.max(0.2, 1 + (attack - defense) / Math.max(attack, defense));
}
/** Exact source expression for Atk.DmgPower and its per-skill-level argument. */
export function runewakerOrgPower(
damagePower: number,
skillLevel: number,
damagePowerSkillLevelPercent: number,
): number {
const power = finite(damagePower);
return power + power * finite(damagePowerSkillLevelPercent) * nonNegative(skillLevel) / 100;
}
/**
* Evaluates the source-shaped tail of CalPhyDamage/CalMagicDamage when every
* native-only input is explicitly supplied. No global rate silently defaults
* to one here, which prevents an "exact" result from hiding missing data.
*/
export function resolveRunewakerNativeResolvedPower(
input: RunewakerNativeResolvedPowerInput,
): number {
let power = finite(input.power)
+ finite(input.fixedValue)
* finite(input.fixedAttributeValue)
* (1 + nonNegative(input.spellLevel) * finite(input.fixedDamageSkillLevelPercent) / 100);
if (power >= 0) return 0;
power *= nonNegative(input.levelRate);
power *= nonNegative(input.resistanceRate);
power *= nonNegative(input.pvpOrNpcDamageRate);
power *= nonNegative(input.criticalMultiplier);
power *= runewakerAttackDefenseRate(input.attackerPower, input.defenderPower);
power *= nonNegative(input.areaPowerRate);
power *= nonNegative(input.areaDefenseRate);
power *= nonNegative(input.attackTargetRate);
power *= nonNegative(input.attackerOriginDefenseRate);
power *= nonNegative(input.sharedDamageRate);
return Math.max(0, Math.trunc(-power));
}
export function normalizeRunewakerDamageFormula(
value: RunewakerDamageFormulaSource | undefined,
): RunewakerDamageFormulaSource | undefined {
if (!value || value.schemaVersion !== 1) return undefined;
if (!Number.isInteger(value.spellId) || value.spellId <= 0) return undefined;
if (!Number.isInteger(value.componentId) || value.componentId <= 0) return undefined;
const calculationPath = ["physical", "magic", "unknown"].includes(value.calculationPath)
? value.calculationPath
: "unknown";
const powerKind = ["source-ratio", "source-points", "dot-only", "unresolved"].includes(value.powerKind)
? value.powerKind
: "unresolved";
return Object.freeze({
schemaVersion: 1,
spellId: value.spellId,
componentId: value.componentId,
spellLevel: Math.max(0, Math.trunc(finite(value.spellLevel))),
calculationPath,
powerKind,
damagePower: finite(value.damagePower),
fixedValue: finite(value.fixedValue),
dotBase: finite(value.dotBase),
runtimeBaselineMultiplier: Math.min(4, nonNegative(value.runtimeBaselineMultiplier)),
provenance: Object.freeze({ ...value.provenance }),
});
}
/**
* Maps imported source evidence to HealerMan's scaled combat budget. Ratio-like
* source powers remain ratios. Point-valued powers use the separately labeled
* runtime adapter multiplier because the native stat universe is unavailable.
* WotLK-shaped hit, armor, resistance, crit, and aura processing happens later
* in the existing combat-result pipeline and is not misreported as RuneWaker.
*/
export function adaptRunewakerEnemyDamage(
input: RunewakerDamageAdapterInput,
): RunewakerDamageAdapterResult {
const formula = normalizeRunewakerDamageFormula(input.formula);
const baselineDamage = nonNegative(input.baselineDamage);
if (!formula || formula.powerKind === "unresolved" || formula.runtimeBaselineMultiplier <= 0) {
return Object.freeze({
amount: 0,
fidelity: "unsupported",
appliedMultiplier: 0,
mappedInputs: Object.freeze(["No executable source damage family"]),
unavailableSourceInputs: MISSING_NATIVE_INPUTS,
});
}
const sourceRatio = formula.powerKind === "source-ratio"
? Math.min(4, Math.max(0.2, Math.abs(formula.damagePower)))
: null;
const appliedMultiplier = sourceRatio ?? formula.runtimeBaselineMultiplier;
return Object.freeze({
amount: Math.max(0, Math.round(baselineDamage * appliedMultiplier)),
fidelity: sourceRatio === null ? "scaled-point-adapter" : "source-ratio-adapter",
appliedMultiplier,
mappedInputs: Object.freeze([
"HealerMan level-scaled enemy baseline damage",
sourceRatio === null
? "Explicit runtimeBaselineMultiplier for source point values"
: "Absolute RuneWaker Atk.DmgPower ratio",
"HealerMan combat-result mitigation after this adapter",
]),
unavailableSourceInputs: MISSING_NATIVE_INPUTS,
});
}
+1 -1
View File
@@ -301,7 +301,7 @@ describe("RuneWaker dungeon creature animation coverage", () => {
}
}
expect(runtimeCounts, "runtime proxies: " + runtimeProxyIds.join(", ")).toEqual({
native: 435,
native: 436,
poseOnly: 1,
proxy: 0,
});
+237
View File
@@ -0,0 +1,237 @@
import { describe, expect, it } from "vitest";
import {
createDungeonEncounterSnapshot,
reduceDungeonEncounter,
resetDungeonEncounterSnapshot,
validateDungeonEncounterDefinition,
} from "./dungeonEncounterRuntime";
import type {
DungeonEncounterAction,
DungeonEncounterGuard,
DungeonEncounterTrigger,
} from "./dungeonEncounterTypes";
import { DUNGEON_REGISTRY } from "./dungeonRegistry";
import { validateDungeonWorldObject } from "./dungeonWorldObjects";
import {
RUNEWAKER_ENCOUNTER_CONTENT_CATALOG,
RUNEWAKER_ENCOUNTER_CONTENT_LEDGER,
} from "./runewakerEncounterCatalog";
import type { DungeonDefinition } from "./dungeonTypes";
function runtimeSpawnIdList(definition: DungeonDefinition): readonly string[] {
return [
...definition.staticSpawns.map((spawn) => spawn.id),
...definition.roamingPacks.flatMap((pack) => pack.members.map((member) => `${pack.id}:${member.id}`)),
...(definition.encounterSpawnGroups ?? []).flatMap((group) => group.spawns.map((spawn) => spawn.id)),
];
}
function expectTriggerReferences(
trigger: DungeonEncounterTrigger,
spawnIds: ReadonlySet<string>,
objectIds: ReadonlySet<string>,
): void {
if (trigger.kind === "entity-death" || trigger.kind === "entity-engaged" || trigger.kind === "entity-health") {
expect(spawnIds, `trigger entity ${trigger.entityId}`).toContain(trigger.entityId);
}
if (trigger.kind === "entities-dead") {
for (const entityId of trigger.entityIds) expect(spawnIds, `trigger entity ${entityId}`).toContain(entityId);
}
if (trigger.kind === "interact") expect(objectIds, `trigger object ${trigger.objectId}`).toContain(trigger.objectId);
}
function expectGuardReferences(
guard: DungeonEncounterGuard,
spawnGroupIds: ReadonlySet<string>,
objectIds: ReadonlySet<string>,
objectiveIds: ReadonlySet<string>,
): void {
if (guard.kind === "spawn-group-active") {
expect(spawnGroupIds, `guard spawn group ${guard.spawnGroupId}`).toContain(guard.spawnGroupId);
}
if (guard.kind === "world-object-state") {
expect(objectIds, `guard object ${guard.objectId}`).toContain(guard.objectId);
}
if (guard.kind === "objective-complete") {
expect(objectiveIds, `guard objective ${guard.objectiveId}`).toContain(guard.objectiveId);
}
}
function expectActionReferences(
action: DungeonEncounterAction,
spawnGroupIds: ReadonlySet<string>,
objectsById: ReadonlyMap<string, DungeonDefinition["serverGameObjects"][number]>,
objectiveIds: ReadonlySet<string>,
): void {
if (action.kind === "activate-spawn-group" || action.kind === "deactivate-spawn-group") {
expect(spawnGroupIds, `action spawn group ${action.spawnGroupId}`).toContain(action.spawnGroupId);
}
if (action.kind === "set-world-object-state") {
const object = objectsById.get(action.objectId);
expect(object, `action object ${action.objectId}`).toBeDefined();
expect(object?.states.map((state) => state.id), `state ${action.objectId}:${action.state}`).toContain(action.state);
}
if (action.kind === "complete-objective") {
expect(objectiveIds, `action objective ${action.objectiveId}`).toContain(action.objectiveId);
}
}
describe("RuneWaker encounter content catalog", () => {
it("wires reviewed packages into the runtime registry with complete references", () => {
for (const content of RUNEWAKER_ENCOUNTER_CONTENT_CATALOG) {
const definition = DUNGEON_REGISTRY[content.dungeonId];
expect(definition, content.dungeonId).toBeDefined();
expect(definition.expansion).toBe("runewaker");
const spawnIdList = runtimeSpawnIdList(definition);
const spawnIds = new Set(spawnIdList);
const spawnGroups = definition.encounterSpawnGroups ?? [];
const spawnGroupIds = new Set(spawnGroups.map((group) => group.id));
const objectIds = new Set(definition.serverGameObjects.map((object) => object.id));
const objectsById = new Map(definition.serverGameObjects.map((object) => [object.id, object] as const));
const objectiveIds = new Set([
...definition.bosses.map((objective) => objective.id),
...definition.objectiveOrder.map((objective) => objective.id),
]);
for (const group of spawnGroups) {
for (const spawn of group.spawns) {
expect(definition.entities[spawn.entityId], `spawn entity ${spawn.entityId}`).toBeDefined();
}
}
expect(spawnIds.size).toBe(spawnIdList.length);
for (const object of definition.serverGameObjects) validateDungeonWorldObject(object);
for (const encounter of definition.encounters ?? []) {
validateDungeonEncounterDefinition(encounter);
expect(encounter.dungeonId).toBe(definition.id);
for (const rule of encounter.rules) {
expectTriggerReferences(rule.trigger, spawnIds, objectIds);
for (const guard of rule.guards ?? []) {
expectGuardReferences(guard, spawnGroupIds, objectIds, objectiveIds);
}
for (const action of rule.actions) {
expectActionReferences(action, spawnGroupIds, objectsById, objectiveIds);
}
}
}
for (const encounter of content.encounters) {
expect(definition.encounters?.map((entry) => entry.id)).toContain(encounter.id);
}
for (const group of content.spawnGroups) expect(spawnGroupIds).toContain(group.id);
for (const object of content.worldObjects) expect(objectIds).toContain(object.id);
}
});
it("activates the three Forsaken source formations and exposes the reviewed hold phase", () => {
const definition = DUNGEON_REGISTRY["forsaken-abbey"];
const encounter = definition.encounters?.find((entry) => entry.id === "forsaken-abbey:follower-patrols");
expect(encounter).toBeDefined();
const started = reduceDungeonEncounter(
encounter!,
createDungeonEncounterSnapshot(encounter!, 0),
{ kind: "tick", now: 0 },
).snapshot;
expect(started.phase).toBe("formation-hold");
expect(started.activeSpawnGroupIds).toEqual([
"forsaken-abbey:follower-a",
"forsaken-abbey:follower-b",
"forsaken-abbey:follower-c",
]);
const held = reduceDungeonEncounter(encounter!, started, { kind: "tick", now: 1_999 }).snapshot;
expect(held.phase).toBe("formation-hold");
const released = reduceDungeonEncounter(encounter!, held, { kind: "tick", now: 2_000 }).snapshot;
expect(released.phase).toBe("patrols-active");
});
it("keeps the Treasure Trove switch locked until Boulder Sidaar dies, then lowers the bridge once", () => {
const definition = DUNGEON_REGISTRY["treasure-trove"];
const encounter = definition.encounters?.find((entry) => entry.id === "treasure-trove:drawbridge-sequence");
expect(encounter).toBeDefined();
const initial = createDungeonEncounterSnapshot(encounter!, 0);
const premature = reduceDungeonEncounter(
encounter!,
initial,
{ kind: "interact", objectId: "treasure-trove:drawbridge-switch", now: 10 },
);
expect(premature.snapshot).toBe(initial);
const armed = reduceDungeonEncounter(
encounter!,
initial,
{ kind: "entity-death", entityId: "rw-treasure-trove-spawn-5952494", now: 100 },
).snapshot;
expect(armed).toMatchObject({
phase: "switch-ready",
worldObjectStates: {
"treasure-trove:drawbridge": "raised",
"treasure-trove:drawbridge-switch": "ready",
},
});
const completed = reduceDungeonEncounter(
encounter!,
armed,
{ kind: "interact", objectId: "treasure-trove:drawbridge-switch", now: 200 },
).snapshot;
expect(completed).toMatchObject({
phase: "complete",
worldObjectStates: {
"treasure-trove:drawbridge": "lowered",
"treasure-trove:drawbridge-switch": "activated",
},
});
expect(reduceDungeonEncounter(
encounter!,
completed,
{ kind: "interact", objectId: "treasure-trove:drawbridge-switch", now: 300 },
).snapshot).toBe(completed);
const reset = resetDungeonEncounterSnapshot(encounter!, completed, "boss-wipe", 400);
expect(reset.worldObjectStates).toMatchObject({
"treasure-trove:drawbridge": "lowered",
"treasure-trove:drawbridge-switch": "activated",
});
expect(reduceDungeonEncounter(encounter!, reset, { kind: "tick", now: 401 }).snapshot.phase)
.toBe("complete");
});
it("keeps the bounded review ledger arithmetically honest and traceable", () => {
const sourceRows = RUNEWAKER_ENCOUNTER_CONTENT_CATALOG.flatMap((entry) => (
entry.reviews.flatMap((review) => review.sourceRows.map((sourceRow) => `${entry.dungeonId}:${sourceRow}`))
));
const evidenceIds = RUNEWAKER_ENCOUNTER_CONTENT_CATALOG.flatMap((entry) => (
entry.reviews.flatMap((review) => review.evidenceIds)
));
expect(new Set(sourceRows).size).toBe(5);
expect(new Set(evidenceIds).size).toBe(5);
expect(RUNEWAKER_ENCOUNTER_CONTENT_LEDGER).toMatchObject({
sourceReportDeferredRows: 4_113,
mappedDeferredRows: 5,
remainingDeferredRows: 4_108,
sourceReportUnresolvedEvidence: 1_136,
reviewedEvidenceRecords: 5,
remainingUnresolvedEvidence: 1_131,
});
expect(RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.sourceReportDeferredRows
- RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.mappedDeferredRows)
.toBe(RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.remainingDeferredRows);
expect(RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.sourceReportUnresolvedEvidence
- RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.reviewedEvidenceRecords)
.toBe(RUNEWAKER_ENCOUNTER_CONTENT_LEDGER.remainingUnresolvedEvidence);
for (const entry of RUNEWAKER_ENCOUNTER_CONTENT_CATALOG) {
for (const review of entry.reviews) {
expect(review.fidelity).toBe("source-backed-adapted");
expect(review.sourceFiles.length).toBeGreaterThan(0);
expect(review.adaptations.length).toBeGreaterThan(0);
expect(review.remainingGaps.length).toBeGreaterThan(0);
for (const source of review.sourceFiles) expect(source.sha256).toMatch(/^[a-f0-9]{64}$/);
}
}
});
});
+406
View File
@@ -0,0 +1,406 @@
import type {
DungeonDefinition,
DungeonEncounterSpawnGroupDefinition,
} from "./dungeonTypes";
import type { DungeonEncounterDefinition } from "./dungeonEncounterTypes";
import type { DungeonWorldObjectDefinition } from "./dungeonWorldObjects";
import type { PopulationEntityDefinition } from "./mobPopulation";
export type RunewakerEncounterContentFidelity = "source-exact" | "source-backed-adapted";
export interface RunewakerEncounterSourceFile {
readonly path: string;
readonly sha256: string;
}
export interface RunewakerEncounterEvidenceReview {
readonly id: string;
readonly fidelity: RunewakerEncounterContentFidelity;
readonly sourceRows: readonly number[];
readonly evidenceIds: readonly string[];
readonly sourceFiles: readonly RunewakerEncounterSourceFile[];
readonly reviewedBehavior: string;
readonly adaptations: readonly string[];
readonly remainingGaps: readonly string[];
}
interface RunewakerEncounterEntityAlias {
readonly id: string;
readonly sourceEntityId: string;
readonly name: string;
}
export interface RunewakerEncounterContentPackage {
readonly dungeonId: string;
readonly encounters: readonly DungeonEncounterDefinition[];
readonly spawnGroups: readonly DungeonEncounterSpawnGroupDefinition[];
readonly worldObjects: readonly DungeonWorldObjectDefinition[];
readonly entityAliases: readonly RunewakerEncounterEntityAlias[];
readonly reviews: readonly RunewakerEncounterEvidenceReview[];
}
const FORSAKEN_PATROL_GROUP_IDS = Object.freeze([
"forsaken-abbey:follower-a",
"forsaken-abbey:follower-b",
"forsaken-abbey:follower-c",
] as const);
const FORSAKEN_PATROL_ENCOUNTER = Object.freeze({
id: "forsaken-abbey:follower-patrols",
dungeonId: "forsaken-abbey",
initialPhase: "initializing",
resetPolicy: { bossWipePhase: "initializing", checkpointPhase: "initializing" },
rules: [
{
id: "create-source-formations",
trigger: { kind: "tick" },
guards: [{ kind: "phase", phases: ["initializing"] }],
actions: [
{ id: "enter-formation-hold", kind: "set-phase", phase: "formation-hold" },
...FORSAKEN_PATROL_GROUP_IDS.map((spawnGroupId, index) => ({
id: `activate-follower-${String.fromCharCode(97 + index)}`,
kind: "activate-spawn-group" as const,
spawnGroupId,
})),
],
},
{
id: "release-source-formations",
trigger: { kind: "elapsed", afterMs: 2_000 },
guards: [{ kind: "phase", phases: ["formation-hold"] }],
actions: [{ id: "enter-patrol-phase", kind: "set-phase", phase: "patrols-active" }],
},
],
} as const satisfies DungeonEncounterDefinition);
const FORSAKEN_PATROL_SPAWN_GROUPS = Object.freeze([
{
id: FORSAKEN_PATROL_GROUP_IDS[0],
spawns: [
{
id: "forsaken-abbey:follower-a:ghoul",
entityId: "rw-fa-100653",
position: [119.523032, 4.05115, 5.524107],
yaw: 4.865415815,
spawnMask: 1,
},
{
id: "forsaken-abbey:follower-a:spider",
entityId: "rw-fa-100393",
position: [121.104334, 4.05115, 5.767995],
yaw: 4.865415815,
spawnMask: 1,
},
],
},
{
id: FORSAKEN_PATROL_GROUP_IDS[1],
spawns: [
{
id: "forsaken-abbey:follower-b:ghoul",
entityId: "rw-fa-100653",
position: [76.438701, -6.138638, 17.204093],
yaw: 4.828148071,
spawnMask: 1,
},
{
id: "forsaken-abbey:follower-b:spider",
entityId: "rw-fa-100393",
position: [78.027993, -6.138638, 17.388895],
yaw: 4.828148071,
spawnMask: 1,
},
],
},
{
id: FORSAKEN_PATROL_GROUP_IDS[2],
spawns: [
{
id: "forsaken-abbey:follower-c:ghoul",
entityId: "rw-fa-100653",
position: [129.755498, -15.077516, -74.285004],
yaw: 1.132059978,
spawnMask: 1,
},
{
id: "forsaken-abbey:follower-c:spider",
entityId: "rw-fa-100393",
position: [128.307036, -15.077516, -73.60533],
yaw: 1.132059978,
spawnMask: 1,
},
],
},
] as const satisfies readonly DungeonEncounterSpawnGroupDefinition[]);
const TREASURE_TROVE_DRAWBRIDGE_ID = "treasure-trove:drawbridge";
const TREASURE_TROVE_SWITCH_ID = "treasure-trove:drawbridge-switch";
const TREASURE_TROVE_WORLD_OBJECTS = Object.freeze([
{
id: TREASURE_TROVE_DRAWBRIDGE_ID,
name: "Drawbridge",
kind: "bridge",
position: [588.054827, -4.084724, -6.244854],
yaw: 3.024162567,
halfExtents: [3.5, 0.35, 5.5],
initialState: "raised",
states: [
{ id: "raised", label: "Raised", visible: true, blocksMovement: true, blocksSight: false },
{ id: "lowered", label: "Lowered", visible: true, blocksMovement: false, blocksSight: false },
],
visual: { color: "#6b5540", emissive: "#1b1209" },
source: {
fidelity: "source-backed-adapted",
files: [
"scripts/runewaker-pipeline/snapshots/treasure-trove-zone-108.json",
"src/assets/game/dungeons/treasure-trove/population-import-report.json",
"preserved RuneWaker Resource/luascript/01159.lua (sha256 23314a25d9654ec34a7d25de588974453f415c235804cc64e7240bf4e03423b6)",
],
templateId: 112098,
spawnId: 5952428,
note: "Source placement and state transition are preserved; the collision box and procedural visual are HealerMan adapters.",
},
},
{
id: TREASURE_TROVE_SWITCH_ID,
name: "Drawbridge Switch",
kind: "switch",
position: [585.282218, -4.084724, -6.571043],
yaw: 1.470568359,
halfExtents: [0.55, 0.8, 0.55],
initialState: "locked",
states: [
{ id: "locked", label: "Locked by Boulder Sidaar", visible: true, blocksMovement: false, interactionEnabled: false },
{ id: "ready", label: "Ready", visible: true, blocksMovement: false, interactionEnabled: true },
{ id: "activated", label: "Activated", visible: true, blocksMovement: false, interactionEnabled: false },
],
interaction: {
prompt: "Lower the drawbridge",
range: 2.5,
signalId: "treasure-trove:drawbridge-activated",
allowedStates: ["ready"],
oneShot: true,
instructions: "Defeat Boulder Sidaar, then interact to lower the nearby drawbridge.",
},
visual: { color: "#a76e2a", emissive: "#3a2108" },
source: {
fidelity: "source-backed-adapted",
files: [
"scripts/runewaker-pipeline/snapshots/treasure-trove-zone-108.json",
"src/assets/game/dungeons/treasure-trove/mechanics-audit.json",
"preserved RuneWaker Resource/luascript/01159.lua (sha256 23314a25d9654ec34a7d25de588974453f415c235804cc64e7240bf4e03423b6)",
],
templateId: 112100,
spawnId: 5952438,
note: "Source placement and boss-gated interaction are preserved; interaction range and procedural visual are HealerMan adapters.",
},
},
] as const satisfies readonly DungeonWorldObjectDefinition[]);
const TREASURE_TROVE_DRAWBRIDGE_ENCOUNTER = Object.freeze({
id: "treasure-trove:drawbridge-sequence",
dungeonId: "treasure-trove",
initialPhase: "boss-active",
initialState: {
worldObjectStates: {
[TREASURE_TROVE_DRAWBRIDGE_ID]: "raised",
[TREASURE_TROVE_SWITCH_ID]: "locked",
},
},
resetPolicy: { bossWipePhase: "boss-active", checkpointPhase: "boss-active" },
rules: [
{
id: "boulder-sidaar-defeated",
trigger: { kind: "entity-death", entityId: "rw-treasure-trove-spawn-5952494" },
guards: [{ kind: "world-object-state", objectId: TREASURE_TROVE_SWITCH_ID, states: ["locked"] }],
actions: [
{ id: "arm-drawbridge-switch", kind: "set-phase", phase: "switch-ready", persistence: "run" },
{
id: "unlock-drawbridge-switch",
kind: "set-world-object-state",
objectId: TREASURE_TROVE_SWITCH_ID,
state: "ready",
persistence: "run",
},
],
},
{
id: "activate-drawbridge-switch",
trigger: { kind: "interact", objectId: TREASURE_TROVE_SWITCH_ID },
guards: [{ kind: "world-object-state", objectId: TREASURE_TROVE_SWITCH_ID, states: ["ready"] }],
actions: [
{ id: "complete-drawbridge-sequence", kind: "set-phase", phase: "complete", persistence: "run" },
{
id: "mark-drawbridge-switch-activated",
kind: "set-world-object-state",
objectId: TREASURE_TROVE_SWITCH_ID,
state: "activated",
persistence: "run",
},
{
id: "lower-treasure-trove-drawbridge",
kind: "set-world-object-state",
objectId: TREASURE_TROVE_DRAWBRIDGE_ID,
state: "lowered",
persistence: "run",
},
],
},
{
id: "restore-ready-phase-after-reset",
trigger: { kind: "tick" },
guards: [
{ kind: "phase", phases: ["boss-active"] },
{ kind: "world-object-state", objectId: TREASURE_TROVE_SWITCH_ID, states: ["ready"] },
],
actions: [{ id: "restore-switch-ready-phase", kind: "set-phase", phase: "switch-ready" }],
},
{
id: "restore-complete-phase-after-reset",
trigger: { kind: "tick" },
guards: [
{ kind: "phase", phases: ["boss-active", "switch-ready"] },
{ kind: "world-object-state", objectId: TREASURE_TROVE_DRAWBRIDGE_ID, states: ["lowered"] },
],
actions: [{ id: "restore-drawbridge-complete-phase", kind: "set-phase", phase: "complete" }],
},
],
} as const satisfies DungeonEncounterDefinition);
const FORSAKEN_REVIEW = Object.freeze({
id: "forsaken-abbey:follower-formations-review",
fidelity: "source-backed-adapted",
sourceRows: [12230, 12236, 12238],
evidenceIds: [
"signal:forsaken-abbey:template:110426:autoplot:f355bf45c2e5",
"signal:forsaken-abbey:template:110426:autoplot:191cb880e4a9",
"signal:forsaken-abbey:template:110426:autoplot:8cb0866b5187",
],
sourceFiles: [
{ path: "scripts/runewaker-pipeline/snapshots/forsaken-abbey-zone-102.json", sha256: "3d19cf799b376b2cb2e0eccc4b509da22821e55f0a0581ce6406643759d37874" },
{ path: "src/assets/game/dungeons/forsaken-abbey/mechanics-audit.json", sha256: "e6069c60e63de77b4f32731d8888a4025c1d5b916bfa699b206c18d16738beca" },
{ path: "preserved RuneWaker Resource/luascript/00571.lua", sha256: "110efc48ad7a915ca76d269ec577a56c0e9e7b2edced0f146720a446aba33cf7" },
{ path: "preserved RuneWaker Resource/luascript/00727.lua", sha256: "10e2e3e315e54dd0d7340fb314d88602a9692985ad7a67e79099a83bef0e387a" },
],
reviewedBehavior: "Each of three source initialization triggers creates a two-member Ghoul/Carrion Spider formation, holds it for two seconds, then hands it to an endless flag patrol.",
adaptations: [
"Source sleep units are converted from tenths of a second to a 2,000 ms encounter phase.",
"Formation offsets are transformed into packaged dungeon coordinates and use projected trigger heights.",
"The source-only level-scaled Ghoul 100653 reuses the packaged canonical Ghoul 100072 visual and combat definition.",
],
remainingGaps: [
"The flag-path patrol primitive is not represented by encounter spawn groups, so the formations remain at their source start positions after the hold phase.",
"Original reset/respawn cadence is not exposed by the preserved scripts and remains dungeon-reset scoped.",
],
} as const satisfies RunewakerEncounterEvidenceReview);
const TREASURE_TROVE_REVIEW = Object.freeze({
id: "treasure-trove:drawbridge-review",
fidelity: "source-backed-adapted",
sourceRows: [5952428, 5952438],
evidenceIds: [
"signal:treasure-trove:template:101533:autoplot:3a096d804fa4",
"signal:treasure-trove:template:112100:autoplot:9520ea9af0f5",
],
sourceFiles: [
{ path: "scripts/runewaker-pipeline/snapshots/treasure-trove-zone-108.json", sha256: "aca7a3e57cbe704a6624f722c8060dc8e7be5d2014240781bc4668b162225a1e" },
{ path: "src/assets/game/dungeons/treasure-trove/mechanics-audit.json", sha256: "a496185046e28657d76bd1d64353dcae070bee45c3e65a265f927a94f0c9fd86" },
{ path: "preserved RuneWaker Resource/luascript/01159.lua", sha256: "23314a25d9654ec34a7d25de588974453f415c235804cc64e7240bf4e03423b6" },
],
reviewedBehavior: "Boulder Sidaar's death arms source switch 112100; touching that switch activates both it and nearby drawbridge 112098 exactly once.",
adaptations: [
"Source placements use the package's navmesh-projected local coordinates.",
"The source motions are represented as explicit object states with procedural collision and visuals.",
"The switch prompt is hidden until the boss death instead of showing the source failure message before it is armed.",
],
remainingGaps: [
"Original ACT motion clips and object meshes are not packaged, so raised/lowered visuals use the generic world-object proxy.",
"Black Mary's key-gated door remains deferred because the encounter guard model does not expose inventory item 203550.",
],
} as const satisfies RunewakerEncounterEvidenceReview);
export const RUNEWAKER_ENCOUNTER_CONTENT_CATALOG = Object.freeze([
Object.freeze({
dungeonId: "forsaken-abbey",
encounters: [FORSAKEN_PATROL_ENCOUNTER],
spawnGroups: FORSAKEN_PATROL_SPAWN_GROUPS,
worldObjects: [],
entityAliases: [{ id: "rw-fa-100653", sourceEntityId: "rw-fa-100072", name: "Ghoul (source summon 100653)" }],
reviews: [FORSAKEN_REVIEW],
}),
Object.freeze({
dungeonId: "treasure-trove",
encounters: [TREASURE_TROVE_DRAWBRIDGE_ENCOUNTER],
spawnGroups: [],
worldObjects: TREASURE_TROVE_WORLD_OBJECTS,
entityAliases: [],
reviews: [TREASURE_TROVE_REVIEW],
}),
] as const satisfies readonly RunewakerEncounterContentPackage[]);
export const RUNEWAKER_ENCOUNTER_CONTENT_LEDGER = Object.freeze({
sourceReportDeferredRows: 4_113,
mappedDeferredRows: RUNEWAKER_ENCOUNTER_CONTENT_CATALOG.reduce(
(count, entry) => count + entry.reviews.reduce((subtotal, review) => subtotal + review.sourceRows.length, 0),
0,
),
remainingDeferredRows: 4_108,
sourceReportUnresolvedEvidence: 1_136,
reviewedEvidenceRecords: RUNEWAKER_ENCOUNTER_CONTENT_CATALOG.reduce(
(count, entry) => count + entry.reviews.reduce((subtotal, review) => subtotal + review.evidenceIds.length, 0),
0,
),
remainingUnresolvedEvidence: 1_131,
});
const CONTENT_BY_DUNGEON: ReadonlyMap<string, RunewakerEncounterContentPackage> = new Map(
RUNEWAKER_ENCOUNTER_CONTENT_CATALOG.map((entry) => [entry.dungeonId, entry] as const),
);
function mergeUniqueById<T extends { readonly id: string }>(
label: string,
existing: readonly T[],
additions: readonly T[],
): readonly T[] {
const merged = new Map(existing.map((entry) => [entry.id, entry] as const));
for (const addition of additions) {
if (merged.has(addition.id)) throw new Error(`Duplicate ${label} ${addition.id}`);
merged.set(addition.id, addition);
}
return Object.freeze([...merged.values()]);
}
function aliasedEntities(
definition: DungeonDefinition,
aliases: readonly RunewakerEncounterEntityAlias[],
): Readonly<Record<string, PopulationEntityDefinition>> {
const result: Record<string, PopulationEntityDefinition> = { ...definition.entities };
for (const alias of aliases) {
if (result[alias.id]) throw new Error(`Duplicate encounter entity ${alias.id}`);
const source = definition.entities[alias.sourceEntityId];
if (!source) throw new Error(`Encounter entity alias ${alias.id} references missing ${alias.sourceEntityId}`);
result[alias.id] = Object.freeze({ ...source, id: alias.id, name: alias.name });
}
return Object.freeze(result);
}
/** Adds only reviewed, source-linked encounter content; generated package files remain immutable. */
export function withRunewakerEncounterContent(definition: DungeonDefinition): DungeonDefinition {
const content = CONTENT_BY_DUNGEON.get(definition.id);
if (!content) return definition;
if (definition.expansion !== "runewaker") {
throw new Error(`RuneWaker encounter content cannot attach to ${definition.expansion} dungeon ${definition.id}`);
}
return Object.freeze({
...definition,
entities: aliasedEntities(definition, content.entityAliases),
encounters: mergeUniqueById("encounter", definition.encounters ?? [], content.encounters),
encounterSpawnGroups: mergeUniqueById(
"encounter spawn group",
definition.encounterSpawnGroups ?? [],
content.spawnGroups,
),
serverGameObjects: mergeUniqueById("world object", definition.serverGameObjects, content.worldObjects),
});
}
+48
View File
@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import {
RUNEWAKER_LOOT_CATALOG,
rollRunewakerBossLoot,
runewakerBossHasLoot,
runewakerBossLootSource,
} from "./runewakerLootCatalog";
describe("RuneWaker source loot catalog", () => {
it("covers every source-classified boss template with explicit status", () => {
expect(RUNEWAKER_LOOT_CATALOG.bossCount).toBe(RUNEWAKER_LOOT_CATALOG.bosses.length);
expect(RUNEWAKER_LOOT_CATALOG.bossCount).toBeGreaterThanOrEqual(50);
expect(RUNEWAKER_LOOT_CATALOG.bosses.every((boss) => Boolean(boss.status))).toBe(true);
});
it("executes NPC slot and treasure probabilities deterministically", () => {
const sequence = [0, 0, ...Array.from({ length: 28 }, () => 0.999999)];
const drops = rollRunewakerBossLoot("rw-barren-caves-101462", 37, {
random: () => sequence.shift() ?? 0.999999,
now: 12_345,
});
expect(drops[0]).toMatchObject({
itemId: 200004,
name: "Healing Sigil",
quantity: 1,
itemLevel: 37,
sourceBossName: "Anglo",
inventorySlot: "misc",
});
expect(drops[0].stats).toMatchObject({ stamina: 0, armor: 0, meleeWeapon: null });
});
it("keeps no-source tables and unknown bosses fail closed", () => {
const noDrops = RUNEWAKER_LOOT_CATALOG.bosses.find((boss) => boss.status === "no-source-drops")!;
expect(runewakerBossHasLoot(noDrops.bossKey)).toBe(false);
expect(rollRunewakerBossLoot(noDrops.bossKey, 20, { random: () => 0 })).toEqual([]);
expect(runewakerBossLootSource("unknown")).toBeNull();
expect(rollRunewakerBossLoot("unknown", 20)).toEqual([]);
});
it("exposes blocked source paths instead of executing them", () => {
const partial = RUNEWAKER_LOOT_CATALOG.bosses.find((boss) => boss.status === "partial")!;
expect(partial.blockedOutcomeCount).toBeGreaterThan(0);
expect(partial.rolls.flatMap((roll) => roll.outcomes).some((outcome) => (
outcome.runtimeStatus === "blocked" && Boolean(outcome.blockedReason)
))).toBe(true);
});
});
+146
View File
@@ -0,0 +1,146 @@
import generatedCatalog from "./generated/runewakerLootCatalog.json";
import { EMPTY_ITEM_STATS } from "./itemStats";
import type { InventoryItem } from "./lootTypes";
export interface RunewakerLootOutcome {
readonly objectId: number;
readonly name: string;
readonly sourceCatalog: string | null;
readonly sourceObjectType?: number;
readonly runtimeStatus: "executable-generic-item" | "blocked";
readonly blockedReason: string | null;
readonly conditionalChance: number;
readonly quantity: number;
readonly sourcePath: readonly number[];
readonly minimumLevelDelta?: number;
readonly luaCheck?: string;
}
export interface RunewakerLootRoll {
readonly sourceSlot: number;
readonly sourceObjectId: number;
readonly sourceRatePer100000: number;
readonly chancePerKill: number;
readonly outcomes: readonly RunewakerLootOutcome[];
}
export interface RunewakerBossLootSource {
readonly bossKey: string;
readonly dungeonId: string;
readonly sourceNpcId: number;
readonly bossName: string;
readonly sourceLevel: number;
readonly status: "portable" | "partial" | "no-source-drops" | "blocked";
readonly blockedReasons: readonly string[];
readonly rolls: readonly RunewakerLootRoll[];
readonly executableOutcomeCount?: number;
readonly blockedOutcomeCount?: number;
}
export interface RunewakerLootCatalog {
readonly schemaVersion: 1;
readonly bossCount: number;
readonly bosses: readonly RunewakerBossLootSource[];
}
export interface RunewakerLootRollOptions {
readonly random?: () => number;
readonly now?: number;
/** Source DLv: defeated NPC level minus the highest contributing player level. */
readonly levelDelta?: number;
/** HealerMan gameplay modifier: one extra authored slot roll may be attempted. */
readonly bonusItemChance?: number;
}
export const RUNEWAKER_LOOT_CATALOG = generatedCatalog as RunewakerLootCatalog;
const bossesByKey = new Map(
RUNEWAKER_LOOT_CATALOG.bosses.map((boss) => [boss.bossKey, boss]),
);
let itemInstanceSequence = 0;
function boundedRandom(random: () => number): number {
const value = random();
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(1 - Number.EPSILON, value));
}
export function runewakerBossLootSource(bossKey: string): RunewakerBossLootSource | null {
return bossesByKey.get(bossKey) ?? null;
}
export function runewakerBossHasLoot(bossKey: string): boolean {
return (bossesByKey.get(bossKey)?.executableOutcomeCount ?? 0) > 0;
}
function selectedOutcome(
roll: RunewakerLootRoll,
random: () => number,
): RunewakerLootOutcome | null {
let cursor = boundedRandom(random);
for (const outcome of roll.outcomes) {
cursor -= Math.max(0, Math.min(1, outcome.conditionalChance));
if (cursor < 0) return outcome;
}
return null;
}
function rollSourceSlot(
boss: RunewakerBossLootSource,
roll: RunewakerLootRoll,
playerLevel: number,
levelDelta: number,
acquiredAt: number,
random: () => number,
): InventoryItem | null {
if (boundedRandom(random) >= Math.max(0, Math.min(1, roll.chancePerKill))) return null;
const outcome = selectedOutcome(roll, random);
if (!outcome || outcome.runtimeStatus !== "executable-generic-item") return null;
if (levelDelta < (outcome.minimumLevelDelta ?? Number.NEGATIVE_INFINITY)) return null;
const itemLevel = Math.max(1, Math.min(255, Math.trunc(playerLevel)));
itemInstanceSequence += 1;
return Object.freeze({
instanceId: `${boss.bossKey}:rw:${acquiredAt}:${itemInstanceSequence}`,
itemId: outcome.objectId,
itemKey: `runewaker-${outcome.objectId}`,
name: outcome.name,
quality: "common",
inventorySlot: "misc",
armorType: null,
weaponType: null,
itemLevel,
sourceItemLevel: itemLevel,
stats: EMPTY_ITEM_STATS,
quantity: Math.max(1, Math.trunc(outcome.quantity)),
sourceBossKey: boss.bossKey,
sourceBossName: boss.bossName,
acquiredAt,
});
}
/**
* Executes the imported independent NPC drop slots and mutually exclusive
* treasure outcomes. Unknown leaves and Lua-gated tables remain no-drop paths.
*/
export function rollRunewakerBossLoot(
bossKey: string,
playerLevel: number,
options: RunewakerLootRollOptions = {},
): readonly InventoryItem[] {
const boss = bossesByKey.get(bossKey);
if (!boss) return [];
const random = options.random ?? Math.random;
const acquiredAt = options.now ?? Date.now();
const levelDelta = Math.trunc(options.levelDelta ?? 0);
const drops = boss.rolls.flatMap((roll) => {
const item = rollSourceSlot(boss, roll, playerLevel, levelDelta, acquiredAt, random);
return item ? [item] : [];
});
const bonusChance = Math.max(0, Math.min(1, options.bonusItemChance ?? 0));
if (boss.rolls.length && bonusChance > 0 && boundedRandom(random) < bonusChance) {
const bonusRoll = boss.rolls[Math.floor(boundedRandom(random) * boss.rolls.length)];
const item = rollSourceSlot(boss, bonusRoll, playerLevel, levelDelta, acquiredAt, random);
if (item) drops.push(item);
}
return drops;
}
@@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest";
import { BufferGeometry, Float32BufferAttribute } from "three";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { buildPartyNavigationGraph } from "./partyNavigation";
import { auditOrderedObjectiveRoutes } from "../../scripts/runewaker-pipeline/lib/objective-route-audit.mjs";
@@ -21,4 +23,39 @@ describe("RuneWaker ordered objective route audit", () => {
{ from: "Island Boss", to: "Home Boss", reachable: false },
]);
});
it("keeps the reviewed navigation blockers deterministic and fail-closed", () => {
const report = JSON.parse(readFileSync(resolve(
"scripts/runewaker-pipeline/RUNEWAKER_NAVIGATION_GAP_REPORT.json",
), "utf8"));
expect(report.schemaVersion).toBe(2);
expect(report).not.toHaveProperty("generatedAt");
expect(report.unreachableLegs).toBe(19);
expect(report.dungeons).toHaveLength(8);
expect(report.resolutionSummary).toEqual({
sourceAuthoredFixedOrders: 0,
authoredOffMeshLinksAdded: 0,
requirementDungeonCounts: {
"dynamic-navigation-transition": 5,
"encounter-gate-state": 3,
"encounter-teleport-state": 2,
"modular-environment-assembly": 3,
"navigation-geometry-review": 1,
"nonlinear-objectives": 1,
"source-objective-review": 6,
},
});
expect(report.dungeons.every((dungeon: {
objectiveOrdering: { authoritative: boolean };
reviewedFinding: { safeAutomaticLink: boolean; requirements: string[] };
}) => (
dungeon.objectiveOrdering.authoritative === false
&& dungeon.reviewedFinding.safeAutomaticLink === false
&& dungeon.reviewedFinding.requirements.length > 0
))).toBe(true);
expect(report.dungeons.find((dungeon: { id: string }) => (
dungeon.id === "kalins-shrine"
))?.reviewedFinding.objectiveOrderStatus).toBe("source-randomized-not-linear");
});
});
+7 -1
View File
@@ -29,6 +29,7 @@ interface ExecutableAttack {
readonly range: number;
readonly cooldownMs: number;
readonly damageMultiplier: number;
readonly runewakerFormula?: { readonly spellId: number; readonly runtimeBaselineMultiplier: number };
}
const catalog = JSON.parse(readFileSync(
@@ -43,6 +44,8 @@ const catalog = JSON.parse(readFileSync(
readonly runtimeSqlDependency: boolean;
readonly unknownEffectsExecute: boolean;
readonly coefficientsAreSourceExact: boolean;
readonly pointValuedDamageUsesLabeledScaledAdapter: boolean;
readonly unavailableNativeInputsDefaulted: boolean;
};
readonly spells: readonly CatalogSpell[];
};
@@ -65,6 +68,8 @@ describe("portable RuneWaker source-spell mappings", () => {
runtimeSqlDependency: false,
unknownEffectsExecute: false,
coefficientsAreSourceExact: false,
pointValuedDamageUsesLabeledScaledAdapter: true,
unavailableNativeInputsDefaulted: false,
});
});
@@ -89,8 +94,9 @@ describe("portable RuneWaker source-spell mappings", () => {
school: source?.runtime?.school,
animation: source?.runtime?.animation,
cooldownMs: source?.runtime?.cooldownMs,
damageMultiplier: source?.runtime?.damageMultiplier,
});
expect(attack.damageMultiplier).toBeGreaterThan(0);
expect(attack.runewakerFormula).toMatchObject({ spellId: attack.spellId });
expect(["melee", "projectile", "area"]).toContain(attack.delivery);
expect(["primary", "nearby-party", "random-party"]).toContain(attack.target);
expect(attack.range).toBeGreaterThanOrEqual(source?.runtime?.range ?? 0);
+26 -1
View File
@@ -8,7 +8,7 @@ import type { GmReviewTarget } from "./gmContentReview";
export type AssetStatus = "checking" | "loading" | "ready" | "proxy" | "error";
export type InputMode = "keyboard" | "gamepad";
export type GameMode = "dungeon" | "manastorm";
export type GameOverlay = "pause" | "party" | "inventory" | "map" | "spellbook" | "talents" | "bindings" | "options" | "gm" | null;
export type GameOverlay = "pause" | "party" | "inventory" | "map" | "boss-guide" | "spellbook" | "talents" | "bindings" | "options" | "gm" | null;
interface PlayerSnapshot {
position: Vector3Tuple;
@@ -42,6 +42,10 @@ export interface GameState {
distanceTravelled: number;
hasMoved: boolean;
resetRevision: number;
/** Renderer relocation signal that does not imply a combat/encounter reset. */
relocationRevision: number;
/** Explicit local reset request; dungeon activation performs its reset before hydration. */
encounterResetRevision: number;
activateDungeon: (
dungeonId: DungeonId,
difficultyId?: string,
@@ -65,6 +69,7 @@ export interface GameState {
setActionLayer: (layer: ActionBindingLayer) => void;
setAssetStatus: (status: AssetStatus, message?: string | null) => void;
updatePlayerSnapshot: (snapshot: PlayerSnapshot) => void;
relocatePlayer: (spawn: WorldSpawn, makeActiveSpawn?: boolean) => void;
resetAtActiveSpawn: () => void;
resetAtEntrance: () => void;
}
@@ -97,6 +102,8 @@ export const useGameStore = create<GameState>((set) => ({
distanceTravelled: 0,
hasMoved: false,
resetRevision: 0,
relocationRevision: 0,
encounterResetRevision: 0,
activateDungeon: (activeDungeonId, requestedDifficultyId, options) => set((state) => {
const definition = requireDungeonDefinition(activeDungeonId);
const activeDifficultyId = definition.difficultyVariants.some((variant) => variant.id === requestedDifficultyId)
@@ -130,6 +137,7 @@ export const useGameStore = create<GameState>((set) => ({
distanceTravelled: 0,
hasMoved: false,
resetRevision: state.resetRevision + 1,
relocationRevision: state.relocationRevision + 1,
};
}),
openOverlay: (overlay) => set({
@@ -206,6 +214,19 @@ export const useGameStore = create<GameState>((set) => ({
hasMoved: state.hasMoved || distanceDelta > 0.02,
};
}),
relocatePlayer: (spawn, makeActiveSpawn = false) => set((state) => {
const definition = requireDungeonDefinition(state.activeDungeonId);
return {
...(makeActiveSpawn ? { activeSpawn: spawn } : {}),
playerPosition: spawn.footPosition,
playerNavigationPosition: spawn.footPosition,
playerGrounded: true,
cameraYaw: spawn.yaw,
areaName: resolveAreaNameForDungeon(definition, spawn.footPosition),
cameraLookActive: false,
relocationRevision: state.relocationRevision + 1,
};
}),
resetAtActiveSpawn: () => set((state) => {
const definition = requireDungeonDefinition(state.activeDungeonId);
return {
@@ -222,6 +243,8 @@ export const useGameStore = create<GameState>((set) => ({
hasMoved: false,
actionLayer: "primary",
resetRevision: state.resetRevision + 1,
relocationRevision: state.relocationRevision + 1,
encounterResetRevision: state.encounterResetRevision + 1,
};
}),
resetAtEntrance: () => set((state) => {
@@ -246,6 +269,8 @@ export const useGameStore = create<GameState>((set) => ({
hasMoved: false,
actionLayer: "primary",
resetRevision: state.resetRevision + 1,
relocationRevision: state.relocationRevision + 1,
encounterResetRevision: state.encounterResetRevision + 1,
};
}),
}));
+175 -66
View File
@@ -2,6 +2,20 @@ import { create } from "zustand";
import referenceData from "./generated/azerothCoreReference.json";
import type { MobCombatState } from "./combatStore";
import type { MobVector3, StaticMobSpawnDefinition } from "./mobPopulation";
import {
createDungeonEncounterSnapshot,
reduceDungeonEncounter,
registerDungeonEncounterProvider,
resetDungeonEncounterSnapshot,
} from "./dungeonEncounterRuntime";
import type {
DungeonEncounterDefinition,
DungeonEncounterEffect,
DungeonEncounterEvent,
DungeonEncounterResetScope,
DungeonEncounterSnapshot,
} from "./dungeonEncounterTypes";
import type { DungeonEncounterSpawnGroupDefinition } from "./dungeonTypes";
export type WailingEncounterPhase =
| "locked"
@@ -89,85 +103,181 @@ function summonGroupSpawns(
const SERPENT_SPAWNS = summonGroupSpawns(1, SERPENT_RUNTIME_PREFIX);
const NIGHTMARE_SPAWNS = summonGroupSpawns(2, NIGHTMARE_RUNTIME_PREFIX);
function allDeadOrTimedOut(
mobs: Readonly<Record<string, MobCombatState>>,
ids: readonly string[],
now: number,
phaseStartedAt: number,
timeoutMs: number,
): boolean {
const registered = ids.filter((id) => mobs[id]);
return (
registered.length === ids.length && registered.every((id) => mobs[id].dead)
) || now - phaseStartedAt >= timeoutMs;
export const WAILING_NARALEX_SPAWN_GROUPS: readonly DungeonEncounterSpawnGroupDefinition[] = Object.freeze([
Object.freeze({ id: "naralex-ravagers", spawns: RAVAGER_SPAWNS }),
Object.freeze({ id: "naralex-serpents", spawns: SERPENT_SPAWNS }),
Object.freeze({ id: "naralex-nightmares", spawns: NIGHTMARE_SPAWNS }),
]);
export const WAILING_NARALEX_ENCOUNTER = Object.freeze({
id: "wailing-caverns:naralex",
dungeonId: "wailing-caverns",
initialPhase: "locked",
resetPolicy: { bossWipePhase: "locked", checkpointPhase: "locked" },
rules: [
{
id: "fanglords-defeated",
trigger: { kind: "entities-dead", entityIds: FANG_LORD_RUNTIME_IDS },
guards: [{ kind: "phase", phases: ["locked"] }],
actions: [{ id: "unlock-naralex", kind: "set-phase", phase: "ready" }],
},
{
id: "start-escort",
trigger: { kind: "signal", signalId: "wailing-caverns:naralex-start" },
guards: [{ kind: "phase", phases: ["ready"] }],
actions: [{ id: "begin-ravager-escort", kind: "set-phase", phase: "escort-ravagers" }],
},
{
id: "reach-ravager-wave",
trigger: { kind: "elapsed", afterMs: 8_000 },
guards: [{ kind: "phase", phases: ["escort-ravagers"] }],
actions: [
{ id: "enter-ravager-wave", kind: "set-phase", phase: "ravager-wave" },
{ id: "activate-ravagers", kind: "activate-spawn-group", spawnGroupId: "naralex-ravagers" },
],
},
{
id: "ravager-wave-cleared",
trigger: { kind: "entities-dead", entityIds: RAVAGER_RUNTIME_IDS, timeoutMs: 60_000 },
guards: [{ kind: "phase", phases: ["ravager-wave"] }],
actions: [
{ id: "finish-ravagers", kind: "deactivate-spawn-group", spawnGroupId: "naralex-ravagers" },
{ id: "begin-serpent-escort", kind: "set-phase", phase: "escort-serpents" },
],
},
{
id: "reach-serpent-wave",
trigger: { kind: "elapsed", afterMs: 12_000 },
guards: [{ kind: "phase", phases: ["escort-serpents"] }],
actions: [
{ id: "enter-serpent-wave", kind: "set-phase", phase: "serpent-wave" },
{ id: "activate-serpents", kind: "activate-spawn-group", spawnGroupId: "naralex-serpents" },
],
},
{
id: "serpent-wave-cleared",
trigger: {
kind: "entities-dead",
entityIds: SERPENT_SPAWNS.map((spawn) => spawn.id),
timeoutMs: 60_000,
},
guards: [{ kind: "phase", phases: ["serpent-wave"] }],
actions: [
{ id: "finish-serpents", kind: "deactivate-spawn-group", spawnGroupId: "naralex-serpents" },
{ id: "begin-cleansing", kind: "set-phase", phase: "cleansing" },
],
},
{
id: "finish-cleansing",
trigger: { kind: "elapsed", afterMs: 32_500 },
guards: [{ kind: "phase", phases: ["cleansing"] }],
actions: [
{ id: "enter-nightmare-wave", kind: "set-phase", phase: "nightmare-wave" },
{ id: "activate-nightmares", kind: "activate-spawn-group", spawnGroupId: "naralex-nightmares" },
],
},
{
id: "nightmare-wave-cleared",
trigger: {
kind: "entities-dead",
entityIds: NIGHTMARE_SPAWNS.map((spawn) => spawn.id),
timeoutMs: 90_000,
},
guards: [{ kind: "phase", phases: ["nightmare-wave"] }],
actions: [
{ id: "finish-nightmares", kind: "deactivate-spawn-group", spawnGroupId: "naralex-nightmares" },
{ id: "summon-mutanus", kind: "set-phase", phase: "mutanus" },
],
},
{
id: "mutanus-defeated",
trigger: { kind: "entities-dead", entityIds: ["mutanus-spawn"] },
guards: [{ kind: "phase", phases: ["mutanus"] }],
actions: [
{ id: "complete-naralex", kind: "set-phase", phase: "complete", persistence: "run" },
{ id: "complete-mutanus-objective", kind: "complete-objective", objectiveId: "mutanus-spawn", persistence: "run" },
],
},
],
} as const satisfies DungeonEncounterDefinition);
function encounterSnapshot(state: WailingEncounterState): DungeonEncounterSnapshot {
return {
schemaVersion: state.schemaVersion,
encounterId: state.encounterId,
phase: state.phase,
phaseStartedAt: state.phaseStartedAt,
flags: state.flags,
counters: state.counters,
timers: state.timers,
worldObjectStates: state.worldObjectStates,
transitionStates: state.transitionStates,
activeSpawnGroupIds: state.activeSpawnGroupIds,
completedObjectiveIds: state.completedObjectiveIds,
consumedActionIds: state.consumedActionIds,
checkpointId: state.checkpointId,
revision: state.revision,
};
}
function idsWithPrefix(mobs: Readonly<Record<string, MobCombatState>>, prefix: string): string[] {
return Object.keys(mobs).filter((id) => id.startsWith(prefix));
}
function fangLordsDefeated(mobs: Readonly<Record<string, MobCombatState>>): boolean {
return FANG_LORD_RUNTIME_IDS.every((id) => mobs[id]?.dead);
}
export interface WailingEncounterState {
export interface WailingEncounterState extends Omit<DungeonEncounterSnapshot, "phase"> {
readonly phase: WailingEncounterPhase;
readonly phaseStartedAt: number;
readonly revision: number;
reset: () => void;
dispatch: (event: DungeonEncounterEvent) => readonly DungeonEncounterEffect[];
reset: (scope?: DungeonEncounterResetScope, now?: number) => void;
start: (now?: number) => boolean;
advance: (mobs: Readonly<Record<string, MobCombatState>>, now?: number) => void;
}
export const useWailingEncounterStore = create<WailingEncounterState>((set, get) => ({
...createDungeonEncounterSnapshot(WAILING_NARALEX_ENCOUNTER),
phase: "locked",
phaseStartedAt: 0,
revision: 0,
reset: () => set((state) => ({
phase: "locked",
phaseStartedAt: 0,
revision: state.revision + 1,
})),
dispatch: (event) => {
const snapshot = encounterSnapshot(get());
const result = reduceDungeonEncounter(WAILING_NARALEX_ENCOUNTER, snapshot, event);
if (result.snapshot !== snapshot) set(result.snapshot as Partial<WailingEncounterState>);
return result.effects;
},
reset: (scope = "dungeon", now = 0) => set((state) => (
resetDungeonEncounterSnapshot(
WAILING_NARALEX_ENCOUNTER,
encounterSnapshot(state),
scope,
now,
) as Partial<WailingEncounterState>
)),
start: (now = Date.now()) => {
if (get().phase !== "ready") return false;
set({ phase: "escort-ravagers", phaseStartedAt: now });
return true;
get().dispatch({ kind: "signal", signalId: "wailing-caverns:naralex-start", now });
return get().phase === "escort-ravagers";
},
advance: (mobs, now = Date.now()) => {
const registeredEntityIds = Object.keys(mobs);
get().dispatch({
kind: "tick",
now,
facts: {
registeredEntityIds,
deadEntityIds: registeredEntityIds.filter((id) => mobs[id].dead),
},
});
},
advance: (mobs, now = Date.now()) => set((state) => {
let phase = state.phase;
if (phase === "locked" && fangLordsDefeated(mobs)) phase = "ready";
else if (phase === "escort-ravagers" && now - state.phaseStartedAt >= 8_000) {
phase = "ravager-wave";
} else if (
phase === "ravager-wave"
&& allDeadOrTimedOut(mobs, RAVAGER_RUNTIME_IDS, now, state.phaseStartedAt, 60_000)
) {
phase = "escort-serpents";
} else if (phase === "escort-serpents" && now - state.phaseStartedAt >= 12_000) {
phase = "serpent-wave";
} else if (phase === "serpent-wave") {
const ids = idsWithPrefix(mobs, SERPENT_RUNTIME_PREFIX);
if (
(ids.length === SERPENT_SPAWNS.length && ids.every((id) => mobs[id].dead))
|| now - state.phaseStartedAt >= 60_000
) phase = "cleansing";
} else if (phase === "cleansing" && now - state.phaseStartedAt >= 32_500) {
phase = "nightmare-wave";
} else if (phase === "nightmare-wave") {
const ids = idsWithPrefix(mobs, NIGHTMARE_RUNTIME_PREFIX);
if (
(ids.length === NIGHTMARE_SPAWNS.length && ids.every((id) => mobs[id].dead))
|| now - state.phaseStartedAt >= 90_000
) phase = "mutanus";
} else if (phase === "mutanus" && mobs["mutanus-spawn"]?.dead) {
phase = "complete";
}
return phase === state.phase
? state
: { phase, phaseStartedAt: now, revision: state.revision + 1 };
}),
}));
registerDungeonEncounterProvider({
definition: WAILING_NARALEX_ENCOUNTER,
encounterId: WAILING_NARALEX_ENCOUNTER.id,
dungeonId: WAILING_NARALEX_ENCOUNTER.dungeonId,
getSnapshot: () => encounterSnapshot(useWailingEncounterStore.getState()),
dispatch: (event) => useWailingEncounterStore.getState().dispatch(event),
reset: (scope, now = 0) => useWailingEncounterStore.getState().reset(scope, now),
replaceSnapshot: (snapshot) => {
if (snapshot.encounterId !== WAILING_NARALEX_ENCOUNTER.id) {
throw new Error(`Cannot hydrate ${WAILING_NARALEX_ENCOUNTER.id} from ${snapshot.encounterId}`);
}
useWailingEncounterStore.setState(snapshot as Partial<WailingEncounterState>);
},
});
export function wailingEventSpawns(
phase: WailingEncounterPhase,
): readonly StaticMobSpawnDefinition[] {
@@ -227,4 +337,3 @@ export function wailingDisciplePosition(
left[2] + (right[2] - left[2]) * alpha,
];
}