forked from phenom/healer-man
Release Healer Man 0.1.14
This commit is contained in:
1 parent
67d7a8ebe7
commit
7b41b102fa
147 files changed
+169892
-1342
No files matched your search
@@ -159,6 +159,7 @@ describe("local profile repository", () => {
|
||||
rosters: { "offline-roster": [] },
|
||||
presentationSettingsByOwner: {
|
||||
"offline-roster": {
|
||||
vfxMode: "original",
|
||||
vfxQuality: "low",
|
||||
vfxEnabled: { player: true, party: false, enemy: true },
|
||||
audioEnabled: { player: true, party: true, enemy: false },
|
||||
@@ -172,12 +173,15 @@ describe("local profile repository", () => {
|
||||
});
|
||||
expect(JSON.parse(legacyV3Storage.getItem(PROFILE_STORAGE_KEYS.database)!)
|
||||
.presentationSettingsByOwner["offline-roster"].vfxMode).toBe("generated");
|
||||
expect(JSON.parse(legacyV3Storage.getItem(PROFILE_STORAGE_KEYS.database)!)
|
||||
.presentationSettingsByOwner["offline-roster"].originalMediaEnabled).toBe(false);
|
||||
|
||||
const storage = new MemoryStorage();
|
||||
const firstOwner = (await createLocalAccount("EffectsOne", "eight-plus", storage)).session!.ownerId;
|
||||
const secondOwner = (await createLocalAccount("EffectsTwo", "eight-plus", storage)).session!.ownerId;
|
||||
const customized = updateCombatPresentationSettings(firstOwner, {
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
originalMediaEnabled: true,
|
||||
vfxMode: "original",
|
||||
vfxQuality: "low",
|
||||
vfxEnabled: { player: true, party: false, enemy: true },
|
||||
|
||||
@@ -216,7 +216,13 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
|
||||
const storedMode = settings && typeof settings === "object" && !Array.isArray(settings)
|
||||
? (settings as { vfxMode?: unknown }).vfxMode
|
||||
: undefined;
|
||||
if (storedMode !== normalized.vfxMode) presentationSettingsMigrated = true;
|
||||
const storedOriginalMediaEnabled = settings && typeof settings === "object" && !Array.isArray(settings)
|
||||
? (settings as { originalMediaEnabled?: unknown }).originalMediaEnabled
|
||||
: undefined;
|
||||
if (
|
||||
storedMode !== normalized.vfxMode
|
||||
|| storedOriginalMediaEnabled !== normalized.originalMediaEnabled
|
||||
) presentationSettingsMigrated = true;
|
||||
return [ownerId, normalized];
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/aeternal-circle-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/barren-caves-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/cave-water-dragon-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/demon-stronghold-125-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/demon-stronghold-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/dungeon-of-dalanis-population.json",
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"schemaVersion": 1,
|
||||
"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": {
|
||||
@@ -26,7 +19,7 @@
|
||||
"Resource/data/string_enus.db": "4fce021ba9ed110a213e08d3228b2de57cec66ddbbc8cdcf5753204874d9ab75"
|
||||
}
|
||||
},
|
||||
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(meters,meters,-meters)",
|
||||
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(0.1,0.1,-0.1)",
|
||||
"navmeshProjection": {
|
||||
"maximumAllowedDistance": 2,
|
||||
"maximumObservedDistance": 2.5965,
|
||||
@@ -35,9 +28,6 @@
|
||||
},
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/forsaken-abbey-population.json",
|
||||
"actorManifest": "public/assets/creatures/forsaken-abbey/manifest.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/grafu-castle-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/hall-of-survivors-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/hoto-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/ice-dwarf-kingdom-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/kal-turok-hive-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/kalins-shrine-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/kawaks-tomb-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/kulech-bones-nest-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/lair-demon-dragon-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/mystic-altar-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/paspers-shrine-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/raksha-temple-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/sand-vortex-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/sardo-castle-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/the-origin-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/tomb-seven-heroes-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/treasure-trove-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/warnorken-arena-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/ystra-labyrinth-boss1-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/ystra-labyrinth-boss2-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/ystra-labyrinth-boss3-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/zurhidon-stronghold-124-population.json",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values.",
|
||||
"status": "green",
|
||||
"recipe": "scripts/runewaker-pipeline/recipes/zurhidon-stronghold-population.json",
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
} from "../app/characterCatalog";
|
||||
import type { InventoryItem } from "../game/lootTypes";
|
||||
import { resolveContentUrl } from "../content/contentManager";
|
||||
import { useContentManagerSnapshot } from "../content/useContentManager";
|
||||
import {
|
||||
selectCharacterBaseClip,
|
||||
selectCharacterEventClip,
|
||||
@@ -144,6 +145,7 @@ function useManifestEntry(identity: AvatarIdentity | null | undefined): {
|
||||
manifest: AvatarManifest | null;
|
||||
entry: AvatarManifestEntry | null;
|
||||
} {
|
||||
const contentRevision = useContentManagerSnapshot().installedRevision;
|
||||
const [state, setState] = useState<{ manifest: AvatarManifest; entry: AvatarManifestEntry } | null>(null);
|
||||
const key = identity ? avatarManifestKey(identity.raceId, identity.gender) : "";
|
||||
const raceId = identity?.raceId;
|
||||
@@ -162,7 +164,7 @@ function useManifestEntry(identity: AvatarIdentity | null | undefined): {
|
||||
if (!cancelled) console.warn("Playable-character manifest could not be loaded", error);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [gender, key, raceId]);
|
||||
}, [contentRevision, gender, key, raceId]);
|
||||
return { manifest: state?.manifest ?? null, entry: state?.entry ?? null };
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useGLTF } from "@react-three/drei";
|
||||
import { resolveContentUrl } from "../content/contentManager";
|
||||
import type { AvatarIdentity } from "./CharacterModel";
|
||||
import {
|
||||
availableAvatarManifestUrls,
|
||||
getAvatarEntry,
|
||||
loadAvatarManifest,
|
||||
} from "./characterManifest";
|
||||
@@ -16,6 +17,7 @@ const requestedModels = new Set<string>();
|
||||
export async function preloadAvatarModels(
|
||||
identities: readonly (AvatarIdentity | null | undefined)[],
|
||||
): Promise<void> {
|
||||
if (!availableAvatarManifestUrls().length) return;
|
||||
const manifest = await loadAvatarManifest();
|
||||
for (const identity of identities) {
|
||||
if (!identity) continue;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
availableAvatarManifestUrls,
|
||||
getAvatarEntry,
|
||||
loadAvatarManifestSources,
|
||||
type AvatarManifest,
|
||||
type AvatarManifestEntry,
|
||||
@@ -33,6 +35,32 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("playable-character manifest loading", () => {
|
||||
it("does not expose optional character manifests until their packs are usable", () => {
|
||||
expect(availableAvatarManifestUrls(() => false)).toEqual([]);
|
||||
expect(availableAvatarManifestUrls((packId) => packId === "core:character-basic")).toEqual([
|
||||
"/assets/characters/wow335a-v1/manifest.json",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the matching basic human when an extra race pack is unavailable", async () => {
|
||||
const loaded = await loadAvatarManifestSources(
|
||||
["/assets/characters/wow335a-v1/manifest.json"],
|
||||
async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => manifest("3.3.5", {
|
||||
"human:male": { raceId: "human", gender: "male", model: "/base-human.glb" },
|
||||
"orc:male": { raceId: "orc", gender: "male", model: "/orc.glb" },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const fallback = getAvatarEntry(loaded, "orc", "male", (packId) => packId === "core:character-basic");
|
||||
expect(fallback?.model).toBe("/base-human.glb");
|
||||
expect(fallback?.counts).toEqual({ skinColor: 1, face: 1, hairStyle: 1, hairColor: 1, feature: 1 });
|
||||
expect(getAvatarEntry(loaded, "orc", "male", () => true)?.model).toBe("/orc.glb");
|
||||
});
|
||||
|
||||
it("keeps valid base characters available when an optional pack fails", async () => {
|
||||
vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||
const base = manifest("3.3.5", {
|
||||
|
||||
+108
-20
@@ -1,5 +1,8 @@
|
||||
import type { AppearanceCounts, GenderId, RaceId } from "../app/characterCatalog";
|
||||
import { resolveContentUrl } from "../content/contentManager";
|
||||
import {
|
||||
contentPackIsUsable,
|
||||
resolveContentUrl,
|
||||
} from "../content/contentManager";
|
||||
|
||||
export type TextureTriplet = readonly [string, string, string];
|
||||
export type TextureRegion = readonly [number, number, number, number];
|
||||
@@ -73,6 +76,8 @@ export interface AvatarManifestEntry {
|
||||
featureOptions: readonly AvatarFeatureOption[];
|
||||
attachmentPoints?: Readonly<Record<string, AvatarAttachmentPoint>>;
|
||||
rendererStrategy?: RunewakerModularRendererStrategy;
|
||||
/** Runtime ownership; generated manifests may omit it. */
|
||||
contentPackId?: string;
|
||||
}
|
||||
|
||||
export interface AvatarManifest {
|
||||
@@ -97,12 +102,24 @@ export interface AvatarManifest {
|
||||
export type CharacterManifest = AvatarManifest;
|
||||
|
||||
export const AVATAR_MANIFEST_URL = "/assets/characters/wow335a-v1/manifest.json";
|
||||
export const AVATAR_MANIFEST_URLS = [
|
||||
AVATAR_MANIFEST_URL,
|
||||
"/assets/characters/ascension-v1/manifest.json",
|
||||
"/assets/characters/runewaker-v1/manifest.json",
|
||||
export const BASIC_CHARACTER_CONTENT_PACK_ID = "core:character-basic";
|
||||
export const WOW_CHARACTER_CONTENT_PACK_ID = "characters:wow335a-v1";
|
||||
export const AVATAR_MANIFEST_SOURCES = [
|
||||
{
|
||||
packIds: [BASIC_CHARACTER_CONTENT_PACK_ID, WOW_CHARACTER_CONTENT_PACK_ID],
|
||||
url: AVATAR_MANIFEST_URL,
|
||||
},
|
||||
{
|
||||
packIds: ["characters:ascension-v1"],
|
||||
url: "/assets/characters/ascension-v1/manifest.json",
|
||||
},
|
||||
{
|
||||
packIds: ["characters:runewaker-v1"],
|
||||
url: "/assets/characters/runewaker-v1/manifest.json",
|
||||
},
|
||||
] as const;
|
||||
let manifestPromise: Promise<AvatarManifest> | null = null;
|
||||
export const AVATAR_MANIFEST_URLS = AVATAR_MANIFEST_SOURCES.map((source) => source.url);
|
||||
let manifestRequest: { readonly key: string; readonly promise: Promise<AvatarManifest> } | null = null;
|
||||
|
||||
interface AvatarManifestResponse {
|
||||
readonly ok: boolean;
|
||||
@@ -114,12 +131,37 @@ export function avatarManifestKey(raceId: RaceId, gender: GenderId): string {
|
||||
return `${raceId}:${gender}`;
|
||||
}
|
||||
|
||||
function mergeAvatarManifests(manifests: readonly AvatarManifest[]): AvatarManifest {
|
||||
const [baseManifest, ...extensions] = manifests;
|
||||
export function availableAvatarManifestUrls(
|
||||
isUsable: (packId: string) => boolean = contentPackIsUsable,
|
||||
): readonly string[] {
|
||||
return AVATAR_MANIFEST_SOURCES
|
||||
.filter((source) => source.packIds.some((packId) => isUsable(packId)))
|
||||
.map((source) => source.url);
|
||||
}
|
||||
|
||||
function contentPackForAvatarEntry(url: string, entry: AvatarManifestEntry): string {
|
||||
if (url === AVATAR_MANIFEST_URL) {
|
||||
return entry.raceId === "human"
|
||||
? BASIC_CHARACTER_CONTENT_PACK_ID
|
||||
: WOW_CHARACTER_CONTENT_PACK_ID;
|
||||
}
|
||||
return AVATAR_MANIFEST_SOURCES.find((source) => source.url === url)?.packIds[0]
|
||||
?? WOW_CHARACTER_CONTENT_PACK_ID;
|
||||
}
|
||||
|
||||
function mergeAvatarManifests(
|
||||
sources: readonly { readonly url: string; readonly manifest: AvatarManifest }[],
|
||||
): AvatarManifest {
|
||||
const [{ manifest: baseManifest }, ...extensions] = sources;
|
||||
return {
|
||||
...baseManifest,
|
||||
sourceBuild: manifests.map((manifest) => manifest.sourceBuild).join(" + "),
|
||||
entries: Object.assign({}, baseManifest.entries, ...extensions.map((manifest) => manifest.entries)),
|
||||
sourceBuild: sources.map(({ manifest }) => manifest.sourceBuild).join(" + "),
|
||||
entries: Object.assign({}, ...sources.map(({ manifest, url }) => Object.fromEntries(
|
||||
Object.entries(manifest.entries).map(([key, entry]) => [
|
||||
key,
|
||||
{ ...entry, contentPackId: contentPackForAvatarEntry(url, entry) },
|
||||
]),
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,10 +180,10 @@ export async function loadAvatarManifestSources(
|
||||
if (manifest.version !== 1 || !manifest.entries) {
|
||||
throw new Error(`Avatar manifest is malformed: ${url}`);
|
||||
}
|
||||
return manifest;
|
||||
return { url, manifest };
|
||||
}));
|
||||
const manifests = results.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
||||
if (!manifests.length) {
|
||||
const sources = results.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
||||
if (!sources.length) {
|
||||
const firstFailure = results.find((result) => result.status === "rejected");
|
||||
throw firstFailure?.status === "rejected"
|
||||
? firstFailure.reason
|
||||
@@ -152,27 +194,73 @@ export async function loadAvatarManifestSources(
|
||||
console.warn(`Optional playable-character pack could not be loaded: ${urls[index]}`, result.reason);
|
||||
}
|
||||
});
|
||||
return mergeAvatarManifests(manifests);
|
||||
return mergeAvatarManifests(sources);
|
||||
}
|
||||
|
||||
export async function loadAvatarManifest(): Promise<AvatarManifest> {
|
||||
if (!manifestPromise) {
|
||||
manifestPromise = loadAvatarManifestSources(
|
||||
AVATAR_MANIFEST_URLS,
|
||||
const urls = availableAvatarManifestUrls();
|
||||
if (!urls.length) throw new Error("No optional playable-character packs are installed");
|
||||
const key = urls.join("|");
|
||||
if (manifestRequest?.key !== key) {
|
||||
const promise = loadAvatarManifestSources(
|
||||
urls,
|
||||
(url) => fetch(resolveContentUrl(url), { cache: "force-cache" }),
|
||||
)
|
||||
.catch((error) => {
|
||||
manifestPromise = null;
|
||||
if (manifestRequest?.promise === promise) manifestRequest = null;
|
||||
throw error;
|
||||
});
|
||||
manifestRequest = { key, promise };
|
||||
}
|
||||
return manifestPromise;
|
||||
return manifestRequest.promise;
|
||||
}
|
||||
|
||||
function firstOrEmpty<T>(values: readonly T[]): readonly T[] {
|
||||
return values.length ? [values[0]] : [];
|
||||
}
|
||||
|
||||
function basicHumanEntry(entry: AvatarManifestEntry): AvatarManifestEntry {
|
||||
const skin = entry.skinOptions?.[0];
|
||||
const face = entry.faceOptions?.[0];
|
||||
const hair = entry.hairOptions?.[0];
|
||||
const feature = entry.featureOptions?.[0];
|
||||
return {
|
||||
...entry,
|
||||
contentPackId: BASIC_CHARACTER_CONTENT_PACK_ID,
|
||||
counts: { skinColor: 1, face: 1, hairStyle: 1, hairColor: 1, feature: 1 },
|
||||
dbcIndices: {
|
||||
skinColor: firstOrEmpty(entry.dbcIndices?.skinColor ?? []),
|
||||
face: firstOrEmpty(entry.dbcIndices?.face ?? []),
|
||||
hairStyle: firstOrEmpty(entry.dbcIndices?.hairStyle ?? []),
|
||||
hairColor: firstOrEmpty(entry.dbcIndices?.hairColor ?? []),
|
||||
feature: firstOrEmpty(entry.dbcIndices?.feature ?? []),
|
||||
},
|
||||
skinOptions: skin ? [skin] : [],
|
||||
faceOptions: face ? [{ ...face, bySkin: firstOrEmpty(face.bySkin) }] : [],
|
||||
hairOptions: hair ? [{ ...hair, byColor: firstOrEmpty(hair.byColor) }] : [],
|
||||
featureOptions: feature ? [{ ...feature, byColor: firstOrEmpty(feature.byColor) }] : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function getAvatarEntry(
|
||||
manifest: AvatarManifest,
|
||||
raceId: RaceId,
|
||||
gender: GenderId,
|
||||
isUsable: (packId: string) => boolean = contentPackIsUsable,
|
||||
): AvatarManifestEntry | null {
|
||||
return manifest.entries[avatarManifestKey(raceId, gender)] ?? null;
|
||||
const requested = manifest.entries[avatarManifestKey(raceId, gender)] ?? null;
|
||||
if (requested) {
|
||||
const requestedPackUsable = !requested.contentPackId || isUsable(requested.contentPackId);
|
||||
if (requested.raceId === "human") {
|
||||
if (requestedPackUsable && isUsable(WOW_CHARACTER_CONTENT_PACK_ID)) return requested;
|
||||
} else if (requestedPackUsable) {
|
||||
return requested;
|
||||
}
|
||||
}
|
||||
const basicHuman = manifest.entries[avatarManifestKey("human", gender)]
|
||||
?? manifest.entries[avatarManifestKey("human", "male")]
|
||||
?? null;
|
||||
return basicHuman && (!basicHuman.contentPackId || isUsable(basicHuman.contentPackId))
|
||||
? basicHumanEntry(basicHuman)
|
||||
: null;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { BROWSER_CONTENT_CACHE_NAME } from "./contentManager";
|
||||
|
||||
const workerSource = readFileSync(
|
||||
new URL("../../public/content-cache-sw.js", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
describe("browser content cache worker", () => {
|
||||
it("serves only cached same-origin asset requests from the active content cache", () => {
|
||||
expect(workerSource).toContain(`const CONTENT_CACHE_NAME = "${BROWSER_CONTENT_CACHE_NAME}";`);
|
||||
expect(workerSource).toContain('request.method !== "GET"');
|
||||
expect(workerSource).toContain("url.origin !== self.location.origin");
|
||||
expect(workerSource).toContain("MANAGED_ASSET_PREFIXES.some");
|
||||
expect(workerSource).toContain('"/assets/game/"');
|
||||
expect(workerSource).toContain("if (cached) return cached");
|
||||
expect(workerSource).toContain("BUNDLED_ASSET_PATHS.has(url.pathname)");
|
||||
expect(workerSource).toContain("BUNDLED_ASSET_PREFIXES.some");
|
||||
expect(workerSource).not.toContain('"/assets/characters/wow335a-v1/textures/character/human/",');
|
||||
expect(workerSource).toContain('status: 404');
|
||||
expect(workerSource).not.toContain("cached ?? fetch(request)");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
BROWSER_CONTENT_CACHE_NAME,
|
||||
checkForContentUpdates,
|
||||
contentPackHasUpdate,
|
||||
contentPackIsBundled,
|
||||
contentPackIsInstalled,
|
||||
contentPackIsUsable,
|
||||
getContentManagerSnapshot,
|
||||
installContentPack,
|
||||
removeContentPack,
|
||||
} from "./contentManager";
|
||||
import type { ContentManifest } from "./contentManifest";
|
||||
|
||||
class MemoryCache {
|
||||
readonly responses = new Map<string, Response>();
|
||||
|
||||
async match(request: RequestInfo | URL): Promise<Response | undefined> {
|
||||
return this.responses.get(this.key(request))?.clone();
|
||||
}
|
||||
|
||||
async put(request: RequestInfo | URL, response: Response): Promise<void> {
|
||||
this.responses.set(this.key(request), response.clone());
|
||||
}
|
||||
|
||||
async delete(request: RequestInfo | URL): Promise<boolean> {
|
||||
return this.responses.delete(this.key(request));
|
||||
}
|
||||
|
||||
private key(request: RequestInfo | URL): string {
|
||||
if (request instanceof Request) return request.url;
|
||||
return new URL(String(request), "http://localhost").toString();
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryCacheStorage {
|
||||
readonly stores = new Map<string, MemoryCache>();
|
||||
|
||||
async open(name: string): Promise<Cache> {
|
||||
const cache = this.stores.get(name) ?? new MemoryCache();
|
||||
this.stores.set(name, cache);
|
||||
return cache as unknown as Cache;
|
||||
}
|
||||
|
||||
async delete(name: string): Promise<boolean> {
|
||||
return this.stores.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
function sha256(bytes: Uint8Array): string {
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
function contentManifest(version: string, objectName: string, bytes: Uint8Array): ContentManifest {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
version,
|
||||
createdAt: "2026-09-04T00:00:00.000Z",
|
||||
minimumAppVersion: "0.1.0",
|
||||
packs: [{
|
||||
id: "characters:test",
|
||||
label: "Test characters",
|
||||
bytes: bytes.byteLength,
|
||||
fileCount: 1,
|
||||
bundledOnAndroid: false,
|
||||
dependencies: [],
|
||||
}],
|
||||
files: {
|
||||
"/assets/characters/test.glb": {
|
||||
packId: "characters:test",
|
||||
url: `/content/objects/${objectName}`,
|
||||
sha256: sha256(bytes),
|
||||
bytes: bytes.byteLength,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("browser content packs", () => {
|
||||
it("downloads, updates, and removes verified optional content", async () => {
|
||||
const firstBytes = new TextEncoder().encode("first-model");
|
||||
const updatedBytes = new TextEncoder().encode("updated-model");
|
||||
let manifest = contentManifest("content-one", "first.glb", firstBytes);
|
||||
let corruptUpdate = false;
|
||||
const storage = new MemoryCacheStorage();
|
||||
vi.spyOn(Capacitor, "isNativePlatform").mockReturnValue(false);
|
||||
vi.stubGlobal("caches", storage as unknown as CacheStorage);
|
||||
vi.stubGlobal("fetch", vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith("/content/manifest.json")) return Response.json(manifest);
|
||||
if (url.endsWith("/first.glb")) return new Response(firstBytes);
|
||||
if (url.endsWith("/updated.glb")) {
|
||||
return new Response(corruptUpdate ? new Uint8Array(updatedBytes.byteLength).fill(7) : updatedBytes);
|
||||
}
|
||||
return new Response(null, { status: 404 });
|
||||
}));
|
||||
|
||||
await checkForContentUpdates();
|
||||
expect(contentPackIsInstalled("characters:test")).toBe(false);
|
||||
expect(contentPackIsUsable("characters:test")).toBe(false);
|
||||
expect(contentPackHasUpdate("characters:test")).toBe(false);
|
||||
|
||||
await installContentPack("characters:test");
|
||||
expect(contentPackIsInstalled("characters:test")).toBe(true);
|
||||
expect(contentPackIsUsable("characters:test")).toBe(true);
|
||||
expect(contentPackHasUpdate("characters:test")).toBe(false);
|
||||
expect(getContentManagerSnapshot()).toMatchObject({
|
||||
installingPackId: null,
|
||||
downloadedBytes: firstBytes.byteLength,
|
||||
totalBytes: firstBytes.byteLength,
|
||||
error: "",
|
||||
});
|
||||
const active = storage.stores.get(BROWSER_CONTENT_CACHE_NAME);
|
||||
expect(await (await active?.match("http://localhost/assets/characters/test.glb"))?.text()).toBe("first-model");
|
||||
|
||||
manifest = contentManifest("content-two", "updated.glb", updatedBytes);
|
||||
await checkForContentUpdates();
|
||||
expect(contentPackHasUpdate("characters:test")).toBe(true);
|
||||
|
||||
corruptUpdate = true;
|
||||
await expect(installContentPack("characters:test")).rejects.toThrow(/verification/i);
|
||||
expect(await (await active?.match("http://localhost/assets/characters/test.glb"))?.text()).toBe("first-model");
|
||||
|
||||
corruptUpdate = false;
|
||||
await installContentPack("characters:test");
|
||||
expect(contentPackHasUpdate("characters:test")).toBe(false);
|
||||
expect(await (await active?.match("http://localhost/assets/characters/test.glb"))?.text()).toBe("updated-model");
|
||||
|
||||
await removeContentPack("characters:test");
|
||||
expect(contentPackIsInstalled("characters:test")).toBe(false);
|
||||
expect(contentPackIsUsable("characters:test")).toBe(false);
|
||||
expect(await active?.match("http://localhost/assets/characters/test.glb")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps default browser content installed and non-removable", async () => {
|
||||
vi.spyOn(Capacitor, "isNativePlatform").mockReturnValue(false);
|
||||
|
||||
expect(contentPackIsBundled("core:ui")).toBe(true);
|
||||
expect(contentPackIsUsable("core:ui")).toBe(true);
|
||||
await expect(removeContentPack("core:ui")).resolves.toBeUndefined();
|
||||
expect(contentPackIsUsable("core:ui")).toBe(true);
|
||||
});
|
||||
});
|
||||
+194
-24
@@ -11,7 +11,23 @@ import { NativeContentStorage } from "./nativeContentStorage";
|
||||
|
||||
const CONTENT_STATE_KEY = "healer-man.content-state.v1";
|
||||
const PRODUCTION_ORIGIN = "https://iwanttoheal.phenomrom.com";
|
||||
const STATIC_ANDROID_BUNDLED_PACKS = new Set(["core:ui", "core:equipment", "dungeon:wailing-caverns"]);
|
||||
const STATIC_ANDROID_BUNDLED_PACKS = new Set([
|
||||
"core:ui",
|
||||
"core:equipment",
|
||||
"core:character-basic",
|
||||
"core:runtime",
|
||||
"dungeon:wailing-caverns",
|
||||
]);
|
||||
const STATIC_BROWSER_BUNDLED_PACKS = new Set([
|
||||
"core:ui",
|
||||
"core:equipment",
|
||||
"core:character-basic",
|
||||
"core:runtime",
|
||||
"dungeon:wailing-caverns",
|
||||
]);
|
||||
|
||||
export const BROWSER_CONTENT_CACHE_NAME = "healer-man-content-v1";
|
||||
export const BROWSER_CONTENT_WORKER_URL = "/content-cache-sw.js";
|
||||
|
||||
export interface InstalledContentFile {
|
||||
readonly packId: string;
|
||||
@@ -29,6 +45,7 @@ interface PersistedContentState {
|
||||
|
||||
export interface ContentManagerSnapshot {
|
||||
readonly revision: number;
|
||||
readonly installedRevision: number;
|
||||
readonly checking: boolean;
|
||||
readonly manifest: ContentManifest | null;
|
||||
readonly bundledManifest: ContentManifest | null;
|
||||
@@ -42,6 +59,7 @@ const EMPTY_STATE: PersistedContentState = { version: 1, packVersions: {}, files
|
||||
let installedState = readInstalledState();
|
||||
let snapshot: ContentManagerSnapshot = {
|
||||
revision: 0,
|
||||
installedRevision: 0,
|
||||
checking: false,
|
||||
manifest: null,
|
||||
bundledManifest: null,
|
||||
@@ -87,13 +105,45 @@ function absoluteContentUrl(value: string): string {
|
||||
return new URL(value, `${contentOrigin() || "http://localhost"}/`).toString();
|
||||
}
|
||||
|
||||
function browserCacheRequest(logicalPath: string): Request {
|
||||
const appOrigin = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
||||
return new Request(new URL(logicalPath, `${appOrigin}/`).toString(), { method: "GET" });
|
||||
}
|
||||
|
||||
function browserCacheStorage(): CacheStorage | null {
|
||||
return typeof caches === "undefined" ? null : caches;
|
||||
}
|
||||
|
||||
async function registerBrowserContentWorker(): Promise<void> {
|
||||
if (Capacitor.isNativePlatform()
|
||||
|| import.meta.env.DEV
|
||||
|| typeof navigator === "undefined"
|
||||
|| !("serviceWorker" in navigator)) return;
|
||||
try {
|
||||
await navigator.serviceWorker.register(BROWSER_CONTENT_WORKER_URL, {
|
||||
scope: "/",
|
||||
updateViaCache: "none",
|
||||
});
|
||||
await navigator.serviceWorker.ready;
|
||||
} catch (error) {
|
||||
console.warn("Browser content cache worker could not be registered.", error);
|
||||
}
|
||||
}
|
||||
|
||||
function extensionForEntry(entry: ContentManifestFile): string {
|
||||
const match = /(?:\.[A-Za-z0-9]+)(?:\?.*)?$/.exec(entry.url);
|
||||
return match?.[0].split("?")[0].toLowerCase() ?? ".bin";
|
||||
}
|
||||
|
||||
function bundledFiles(): Readonly<Record<string, { readonly sha256: string }>> {
|
||||
return snapshot.bundledManifest?.files ?? {};
|
||||
if (snapshot.bundledManifest) return snapshot.bundledManifest.files;
|
||||
if (!snapshot.manifest) return {};
|
||||
const bundledPacks = Capacitor.isNativePlatform()
|
||||
? STATIC_ANDROID_BUNDLED_PACKS
|
||||
: STATIC_BROWSER_BUNDLED_PACKS;
|
||||
return Object.fromEntries(Object.entries(snapshot.manifest.files).filter(([, entry]) => (
|
||||
bundledPacks.has(entry.packId)
|
||||
)));
|
||||
}
|
||||
|
||||
function activeFileIdentities(): Readonly<Record<string, { readonly sha256: string }>> {
|
||||
@@ -117,21 +167,31 @@ export function resolveContentUrl(logicalUrl: string): string {
|
||||
return installed?.fileUri ? Capacitor.convertFileSrc(installed.fileUri) : logicalUrl;
|
||||
}
|
||||
|
||||
export function contentPackIsBundled(packId: string): boolean {
|
||||
if (!Capacitor.isNativePlatform()) return STATIC_BROWSER_BUNDLED_PACKS.has(packId);
|
||||
return STATIC_ANDROID_BUNDLED_PACKS.has(packId)
|
||||
|| Boolean(snapshot.bundledManifest?.packs.some((pack) => pack.id === packId));
|
||||
}
|
||||
|
||||
export function contentPackIsInstalled(packId: string): boolean {
|
||||
return contentPackIsBundled(packId) || Boolean(installedState.packVersions[packId]);
|
||||
}
|
||||
|
||||
export function contentPackIsUsable(packId: string): boolean {
|
||||
if (!Capacitor.isNativePlatform()) return true;
|
||||
const installed = STATIC_ANDROID_BUNDLED_PACKS.has(packId)
|
||||
|| snapshot.bundledManifest?.packs.some((pack) => pack.id === packId)
|
||||
|| Boolean(installedState.packVersions[packId]);
|
||||
if (!installed) return false;
|
||||
if (contentPackIsBundled(packId)) return true;
|
||||
if (!installedState.packVersions[packId]) return false;
|
||||
const pack = snapshot.manifest?.packs.find((candidate) => candidate.id === packId);
|
||||
return pack?.dependencies.every((dependency) => contentPackIsUsable(dependency)) ?? true;
|
||||
}
|
||||
|
||||
export function contentPackHasUpdate(packId: string): boolean {
|
||||
if (!snapshot.manifest) return false;
|
||||
if (!snapshot.manifest || !contentPackIsInstalled(packId)) return false;
|
||||
const pack = snapshot.manifest.packs.find((candidate) => candidate.id === packId);
|
||||
return changedFilesForPack(snapshot.manifest, packId, activeFileIdentities()).length > 0
|
||||
|| Boolean(pack?.dependencies.some((dependency) => contentPackHasUpdate(dependency)));
|
||||
|| Boolean(!contentPackIsBundled(packId)
|
||||
&& pack?.dependencies.some((dependency) => (
|
||||
!contentPackIsUsable(dependency) || contentPackHasUpdate(dependency)
|
||||
)));
|
||||
}
|
||||
|
||||
export async function checkForContentUpdates(): Promise<ContentManifest | null> {
|
||||
@@ -149,7 +209,7 @@ export async function checkForContentUpdates(): Promise<ContentManifest | null>
|
||||
}
|
||||
|
||||
async function loadBundledManifest(): Promise<void> {
|
||||
if (!Capacitor.isNativePlatform()) return;
|
||||
if (!Capacitor.isNativePlatform() && import.meta.env.VITE_CONTENT_MODE !== "downloadable") return;
|
||||
try {
|
||||
const response = await fetch("/content/bootstrap-manifest.json", { cache: "force-cache" });
|
||||
if (!response.ok) return;
|
||||
@@ -162,13 +222,102 @@ async function loadBundledManifest(): Promise<void> {
|
||||
export function initializeContentSystem(): Promise<void> {
|
||||
if (!initialization) {
|
||||
initialization = (async () => {
|
||||
await loadBundledManifest();
|
||||
await Promise.all([loadBundledManifest(), registerBrowserContentWorker()]);
|
||||
if (typeof navigator === "undefined" || navigator.onLine !== false) void checkForContentUpdates();
|
||||
})();
|
||||
}
|
||||
return initialization;
|
||||
}
|
||||
|
||||
async function sha256Bytes(bytes: ArrayBuffer): Promise<string> {
|
||||
if (!globalThis.crypto?.subtle) {
|
||||
throw new Error("This browser cannot verify downloaded content.");
|
||||
}
|
||||
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
||||
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
async function installBrowserContentPack(
|
||||
packId: string,
|
||||
manifest: ContentManifest,
|
||||
changes: ReturnType<typeof changedFilesForPack>,
|
||||
): Promise<void> {
|
||||
const storage = browserCacheStorage();
|
||||
if (!storage) throw new Error("Offline content storage is unavailable in this browser.");
|
||||
|
||||
const totalBytes = changes.reduce((total, [, entry]) => total + entry.bytes, 0);
|
||||
const stagingName = `${BROWSER_CONTENT_CACHE_NAME}-staging-${encodeURIComponent(packId)}`;
|
||||
await storage.delete(stagingName);
|
||||
const staging = await storage.open(stagingName);
|
||||
publish({ installingPackId: packId, downloadedBytes: 0, totalBytes, error: "" });
|
||||
|
||||
let completedBytes = 0;
|
||||
try {
|
||||
for (const [logicalPath, entry] of changes) {
|
||||
const response = await fetch(absoluteContentUrl(entry.url), { cache: "no-store" });
|
||||
if (!response.ok) throw new Error(`Content request failed (${response.status}): ${logicalPath}`);
|
||||
const bytes = await response.arrayBuffer();
|
||||
if (bytes.byteLength !== entry.bytes) {
|
||||
throw new Error(`Downloaded content has the wrong size: ${logicalPath}`);
|
||||
}
|
||||
if (await sha256Bytes(bytes) !== entry.sha256) {
|
||||
throw new Error(`Downloaded content failed verification: ${logicalPath}`);
|
||||
}
|
||||
const headers = new Headers();
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType) headers.set("content-type", contentType);
|
||||
await staging.put(browserCacheRequest(logicalPath), new Response(bytes, {
|
||||
headers,
|
||||
status: 200,
|
||||
}));
|
||||
completedBytes += entry.bytes;
|
||||
publish({ downloadedBytes: completedBytes });
|
||||
}
|
||||
|
||||
const active = await storage.open(BROWSER_CONTENT_CACHE_NAME);
|
||||
for (const [logicalPath] of changes) {
|
||||
const cached = await staging.match(browserCacheRequest(logicalPath));
|
||||
if (!cached) throw new Error(`Verified content was not staged: ${logicalPath}`);
|
||||
await active.put(browserCacheRequest(logicalPath), cached);
|
||||
}
|
||||
|
||||
const pendingFiles = { ...installedState.files };
|
||||
const latestPaths = new Set(filesForContentPack(manifest, packId).map(([logicalPath]) => logicalPath));
|
||||
for (const [logicalPath, installed] of Object.entries(pendingFiles)) {
|
||||
if (installed.packId !== packId || latestPaths.has(logicalPath)) continue;
|
||||
await active.delete(browserCacheRequest(logicalPath));
|
||||
delete pendingFiles[logicalPath];
|
||||
}
|
||||
for (const [logicalPath, entry] of filesForContentPack(manifest, packId)) {
|
||||
pendingFiles[logicalPath] = {
|
||||
packId,
|
||||
sha256: entry.sha256,
|
||||
bytes: entry.bytes,
|
||||
extension: extensionForEntry(entry),
|
||||
fileUri: absoluteContentUrl(logicalPath),
|
||||
};
|
||||
}
|
||||
installedState = {
|
||||
version: 1,
|
||||
files: pendingFiles,
|
||||
packVersions: { ...installedState.packVersions, [packId]: manifest.version },
|
||||
};
|
||||
persistInstalledState();
|
||||
publish({
|
||||
installingPackId: null,
|
||||
downloadedBytes: totalBytes,
|
||||
totalBytes,
|
||||
error: "",
|
||||
installedRevision: snapshot.installedRevision + 1,
|
||||
});
|
||||
} catch (error) {
|
||||
publish({ installingPackId: null, error: error instanceof Error ? error.message : "Content download failed." });
|
||||
throw error;
|
||||
} finally {
|
||||
await storage.delete(stagingName).catch(() => false);
|
||||
}
|
||||
}
|
||||
|
||||
export async function installContentPack(packId: string): Promise<void> {
|
||||
const manifest = snapshot.manifest ?? await checkForContentUpdates();
|
||||
if (!manifest) throw new Error(snapshot.error || "Content server is unavailable.");
|
||||
@@ -177,15 +326,20 @@ export async function installContentPack(packId: string): Promise<void> {
|
||||
}
|
||||
const pack = manifest.packs.find((candidate) => candidate.id === packId);
|
||||
if (!pack) throw new Error(`Unknown content pack: ${packId}`);
|
||||
if (!Capacitor.isNativePlatform()) return;
|
||||
|
||||
for (const dependency of pack.dependencies) {
|
||||
if (!contentPackIsUsable(dependency) || contentPackHasUpdate(dependency)) {
|
||||
await installContentPack(dependency);
|
||||
if (!contentPackIsBundled(packId)) {
|
||||
for (const dependency of pack.dependencies) {
|
||||
if (!contentPackIsUsable(dependency) || contentPackHasUpdate(dependency)) {
|
||||
await installContentPack(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const changes = changedFilesForPack(manifest, packId, activeFileIdentities());
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
await installBrowserContentPack(packId, manifest, changes);
|
||||
return;
|
||||
}
|
||||
const totalBytes = changes.reduce((total, [, entry]) => total + entry.bytes, 0);
|
||||
const storage = await NativeContentStorage.getFreeBytes();
|
||||
const safetyReserve = 128 * 1024 * 1024;
|
||||
@@ -232,7 +386,13 @@ export async function installContentPack(packId: string): Promise<void> {
|
||||
packVersions: { ...installedState.packVersions, [packId]: manifest.version },
|
||||
};
|
||||
persistInstalledState();
|
||||
publish({ installingPackId: null, downloadedBytes: totalBytes, totalBytes, error: "" });
|
||||
publish({
|
||||
installingPackId: null,
|
||||
downloadedBytes: totalBytes,
|
||||
totalBytes,
|
||||
error: "",
|
||||
installedRevision: snapshot.installedRevision + 1,
|
||||
});
|
||||
} catch (error) {
|
||||
publish({ installingPackId: null, error: error instanceof Error ? error.message : "Content download failed." });
|
||||
throw error;
|
||||
@@ -242,19 +402,29 @@ export async function installContentPack(packId: string): Promise<void> {
|
||||
}
|
||||
|
||||
export async function removeContentPack(packId: string): Promise<void> {
|
||||
if (!Capacitor.isNativePlatform()) return;
|
||||
if (contentPackIsBundled(packId)) return;
|
||||
const remainingFiles = { ...installedState.files };
|
||||
const targetFiles = Object.entries(remainingFiles).filter(([, entry]) => entry.packId === packId);
|
||||
for (const [logicalPath, entry] of targetFiles) {
|
||||
const shared = Object.entries(remainingFiles).some(([otherPath, other]) => (
|
||||
otherPath !== logicalPath && other.sha256 === entry.sha256 && other.extension === entry.extension && other.packId !== packId
|
||||
));
|
||||
if (!shared) await NativeContentStorage.remove({ sha256: entry.sha256, extension: entry.extension });
|
||||
delete remainingFiles[logicalPath];
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
for (const [logicalPath, entry] of targetFiles) {
|
||||
const shared = Object.entries(remainingFiles).some(([otherPath, other]) => (
|
||||
otherPath !== logicalPath && other.sha256 === entry.sha256 && other.extension === entry.extension && other.packId !== packId
|
||||
));
|
||||
if (!shared) await NativeContentStorage.remove({ sha256: entry.sha256, extension: entry.extension });
|
||||
delete remainingFiles[logicalPath];
|
||||
}
|
||||
} else {
|
||||
const storage = browserCacheStorage();
|
||||
if (!storage) throw new Error("Offline content storage is unavailable in this browser.");
|
||||
const active = await storage.open(BROWSER_CONTENT_CACHE_NAME);
|
||||
for (const [logicalPath] of targetFiles) {
|
||||
await active.delete(browserCacheRequest(logicalPath));
|
||||
delete remainingFiles[logicalPath];
|
||||
}
|
||||
}
|
||||
const packVersions = { ...installedState.packVersions };
|
||||
delete packVersions[packId];
|
||||
installedState = { version: 1, files: remainingFiles, packVersions };
|
||||
persistInstalledState();
|
||||
publish({ error: "" });
|
||||
publish({ error: "", installedRevision: snapshot.installedRevision + 1 });
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const combatMedia = vi.hoisted(() => ({ ready: false }));
|
||||
|
||||
vi.mock("./combatMediaPack", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("./combatMediaPack")>()),
|
||||
combatMediaPackReady: () => combatMedia.ready,
|
||||
}));
|
||||
|
||||
import {
|
||||
DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
type CombatPresentationEvent,
|
||||
} from "./combatPresentation";
|
||||
import {
|
||||
combatAudioOutputEnabled,
|
||||
authenticCombatAudioEnabled,
|
||||
combatMediaPreloadEnabled,
|
||||
configureCombatAudio,
|
||||
masterAudioOutputEnabled,
|
||||
playCombatAudio,
|
||||
@@ -28,7 +38,10 @@ const fixtureEvent: CombatPresentationEvent = {
|
||||
targetPosition: [1, 0, 0],
|
||||
};
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
afterEach(() => {
|
||||
combatMedia.ready = false;
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("combat audio output policy", () => {
|
||||
it("does not create or fetch audio while the master volume is disabled", async () => {
|
||||
@@ -50,6 +63,30 @@ describe("combat audio output policy", () => {
|
||||
expect(masterAudioOutputEnabled(0.6)).toBe(true);
|
||||
expect(combatAudioOutputEnabled(DEFAULT_COMBAT_PRESENTATION_SETTINGS, 0)).toBe(false);
|
||||
expect(combatAudioOutputEnabled(DEFAULT_COMBAT_PRESENTATION_SETTINGS, 0.6)).toBe(true);
|
||||
expect(authenticCombatAudioEnabled(DEFAULT_COMBAT_PRESENTATION_SETTINGS)).toBe(false);
|
||||
combatMedia.ready = true;
|
||||
expect(authenticCombatAudioEnabled({
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
originalMediaEnabled: true,
|
||||
})).toBe(true);
|
||||
expect(combatMediaPreloadEnabled(DEFAULT_COMBAT_PRESENTATION_SETTINGS, 0.6)).toBe(false);
|
||||
expect(combatMediaPreloadEnabled({
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
originalMediaEnabled: true,
|
||||
}, 0.6)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not fetch packaged sound effects before the player downloads them", async () => {
|
||||
const AudioContextConstructor = vi.fn();
|
||||
const fetch = vi.fn();
|
||||
vi.stubGlobal("window", { AudioContext: AudioContextConstructor });
|
||||
vi.stubGlobal("fetch", fetch);
|
||||
configureCombatAudio(DEFAULT_COMBAT_PRESENTATION_SETTINGS, 0.6);
|
||||
|
||||
await expect(playMasterAudioCue("/assets/game/combat-effects/audio/fixture.wav")).resolves.toBeNull();
|
||||
|
||||
expect(AudioContextConstructor).not.toHaveBeenCalled();
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not initialize audio for a zero-volume actor group", () => {
|
||||
|
||||
+22
-2
@@ -6,6 +6,10 @@ import {
|
||||
type CombatPresentationSettings,
|
||||
type CombatPresentationSchool,
|
||||
} from "./combatPresentation";
|
||||
import {
|
||||
combatMediaPackReady,
|
||||
fetchOptionalMediaAsset,
|
||||
} from "./combatMediaPack";
|
||||
|
||||
const MAX_COMBAT_VOICES = 32;
|
||||
|
||||
@@ -37,6 +41,19 @@ export function combatAudioOutputEnabled(
|
||||
));
|
||||
}
|
||||
|
||||
export function authenticCombatAudioEnabled(settings: CombatPresentationSettings): boolean {
|
||||
return settings.originalMediaEnabled && combatMediaPackReady();
|
||||
}
|
||||
|
||||
export function combatMediaPreloadEnabled(
|
||||
settings: CombatPresentationSettings,
|
||||
masterVolume: number,
|
||||
): boolean {
|
||||
return authenticCombatAudioEnabled(settings) && (
|
||||
settings.vfxQuality !== "off" || combatAudioOutputEnabled(settings, masterVolume)
|
||||
);
|
||||
}
|
||||
|
||||
function combatAudioGroupOutputEnabled(
|
||||
settings: CombatPresentationSettings,
|
||||
masterVolume: number,
|
||||
@@ -185,6 +202,7 @@ function playSynthesizedCombatCue(event: CombatPresentationEvent): void {
|
||||
}
|
||||
|
||||
function weightedSound(event: CombatPresentationEvent): CombatEffectSoundAsset | null {
|
||||
if (!configuredSettings || !authenticCombatAudioEnabled(configuredSettings)) return null;
|
||||
const sounds = combatEffectManifestEntry(event.sourceSpellId, event.source)?.sounds ?? [];
|
||||
const playable = sounds.filter((sound) => sound.url);
|
||||
if (!playable.length) return null;
|
||||
@@ -201,7 +219,7 @@ async function decodeAudio(url: string): Promise<AudioBuffer | null> {
|
||||
const audioContext = ensureContext();
|
||||
if (!audioContext) return null;
|
||||
if (!decodedBuffers.has(url)) {
|
||||
decodedBuffers.set(url, fetch(url)
|
||||
decodedBuffers.set(url, fetchOptionalMediaAsset(url)
|
||||
.then((response) => response.ok ? response.arrayBuffer() : Promise.reject(new Error(String(response.status))))
|
||||
.then((data) => audioContext.decodeAudioData(data))
|
||||
.catch(() => null));
|
||||
@@ -213,7 +231,9 @@ export async function playMasterAudioCue(
|
||||
url: string,
|
||||
volume = 1,
|
||||
): Promise<(() => void) | null> {
|
||||
if (!masterAudioOutputEnabled(configuredMasterVolume)) return null;
|
||||
if (!configuredSettings
|
||||
|| !authenticCombatAudioEnabled(configuredSettings)
|
||||
|| !masterAudioOutputEnabled(configuredMasterVolume)) return null;
|
||||
const audioContext = ensureContext();
|
||||
const buffer = await decodeAudio(url);
|
||||
if (
|
||||
|
||||
@@ -116,6 +116,9 @@ describe("named aura combat runtime", () => {
|
||||
school: "fire",
|
||||
interruptible: true,
|
||||
targetActorId: PLAYER_AGGRO_ID,
|
||||
sourcePosition: [0, 0, 0],
|
||||
targetPosition: [1, 0, 0],
|
||||
sourceYaw: Math.PI / 2,
|
||||
startedAt: 1_000,
|
||||
completesAt: 6_000,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const content = vi.hoisted(() => ({
|
||||
usable: false,
|
||||
install: vi.fn(async () => undefined),
|
||||
remove: vi.fn(async () => undefined),
|
||||
listener: null as (() => void) | null,
|
||||
snapshot: {
|
||||
downloadedBytes: 4,
|
||||
totalBytes: 8,
|
||||
manifest: {
|
||||
packs: [{ id: "core:combat-media", fileCount: 12 }],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../content/contentManager", () => ({
|
||||
contentPackIsUsable: () => content.usable,
|
||||
getContentManagerSnapshot: () => content.snapshot,
|
||||
installContentPack: content.install,
|
||||
removeContentPack: content.remove,
|
||||
resolveContentUrl: (url: string) => `resolved:${url}`,
|
||||
subscribeContentManager: (listener: () => void) => {
|
||||
content.listener = listener;
|
||||
return () => { content.listener = null; };
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
combatMediaPackReady,
|
||||
downloadCombatMediaPack,
|
||||
fetchCombatMediaAsset,
|
||||
removeCombatMediaPack,
|
||||
} from "./combatMediaPack";
|
||||
|
||||
beforeEach(() => {
|
||||
content.usable = false;
|
||||
content.install.mockClear();
|
||||
content.remove.mockClear();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("optional combat media pack", () => {
|
||||
it("uses the shared verified content-pack installer only after an explicit request", async () => {
|
||||
const progress = vi.fn();
|
||||
await expect(downloadCombatMediaPack(progress)).resolves.toEqual({ fileCount: 12 });
|
||||
expect(content.install).toHaveBeenCalledWith("core:combat-media");
|
||||
expect(progress).toHaveBeenLastCalledWith({ completed: 4, total: 8 });
|
||||
});
|
||||
|
||||
it("does not fetch source media while its pack is offloaded", async () => {
|
||||
const fetch = vi.fn();
|
||||
vi.stubGlobal("fetch", fetch);
|
||||
|
||||
expect(combatMediaPackReady()).toBe(false);
|
||||
await expect(fetchCombatMediaAsset("/assets/game/combat-effects/audio/shield.wav"))
|
||||
.rejects.toThrow("not installed");
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves installed media through the shared content router and removes the same pack", async () => {
|
||||
content.usable = true;
|
||||
const response = new Response(new Uint8Array([1, 2, 3]));
|
||||
const fetch = vi.fn(async () => response);
|
||||
vi.stubGlobal("fetch", fetch);
|
||||
|
||||
await expect(fetchCombatMediaAsset("/assets/game/combat-effects/audio/shield.wav"))
|
||||
.resolves.toBe(response);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"resolved:/assets/game/combat-effects/audio/shield.wav",
|
||||
{ cache: "no-store" },
|
||||
);
|
||||
|
||||
await removeCombatMediaPack();
|
||||
expect(content.remove).toHaveBeenCalledWith("core:combat-media");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
contentPackIsUsable,
|
||||
getContentManagerSnapshot,
|
||||
installContentPack,
|
||||
removeContentPack,
|
||||
resolveContentUrl,
|
||||
subscribeContentManager,
|
||||
} from "../content/contentManager";
|
||||
|
||||
export const COMBAT_MEDIA_MANIFEST_URL = "/assets/game/combat-effects/manifest.json";
|
||||
export const COMBAT_MEDIA_CONTENT_PACK_ID = "core:combat-media";
|
||||
|
||||
export interface CombatMediaDownloadProgress {
|
||||
readonly completed: number;
|
||||
readonly total: number;
|
||||
}
|
||||
|
||||
export interface CombatMediaDownloadResult {
|
||||
readonly fileCount: number;
|
||||
}
|
||||
|
||||
type ProgressListener = (progress: CombatMediaDownloadProgress) => void;
|
||||
|
||||
function isCombatMediaUrl(value: unknown): value is string {
|
||||
return typeof value === "string" && value.startsWith("/assets/game/combat-effects/");
|
||||
}
|
||||
|
||||
/** Callers must enforce the player's original-media opt-in before fetching. */
|
||||
export async function fetchCombatMediaAsset(url: string): Promise<Response> {
|
||||
if (!isCombatMediaUrl(url)) throw new Error(`Unsupported combat media URL: ${url}`);
|
||||
return fetchOptionalMediaAsset(url);
|
||||
}
|
||||
|
||||
/** Fetch source media only after its shared optional pack has been enabled. */
|
||||
export async function fetchOptionalMediaAsset(url: string): Promise<Response> {
|
||||
if (!combatMediaPackReady()) throw new Error("Original spell media is not installed.");
|
||||
return fetch(resolveContentUrl(url), { cache: "no-store" });
|
||||
}
|
||||
|
||||
/** Download and verify every file owned by the original combat-media pack. */
|
||||
export async function downloadCombatMediaPack(
|
||||
onProgress?: ProgressListener,
|
||||
): Promise<CombatMediaDownloadResult> {
|
||||
const publishProgress = () => {
|
||||
const state = getContentManagerSnapshot();
|
||||
onProgress?.({
|
||||
completed: state.downloadedBytes,
|
||||
total: Math.max(1, state.totalBytes),
|
||||
});
|
||||
};
|
||||
const unsubscribe = subscribeContentManager(publishProgress);
|
||||
try {
|
||||
await installContentPack(COMBAT_MEDIA_CONTENT_PACK_ID);
|
||||
publishProgress();
|
||||
const pack = getContentManagerSnapshot().manifest?.packs.find((candidate) => (
|
||||
candidate.id === COMBAT_MEDIA_CONTENT_PACK_ID
|
||||
));
|
||||
return { fileCount: pack?.fileCount ?? 0 };
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCombatMediaPack(): Promise<void> {
|
||||
await removeContentPack(COMBAT_MEDIA_CONTENT_PACK_ID);
|
||||
}
|
||||
|
||||
export function combatMediaPackReady(): boolean {
|
||||
return contentPackIsUsable(COMBAT_MEDIA_CONTENT_PACK_ID);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ describe("combat presentation", () => {
|
||||
audioEnabled: { enemy: false },
|
||||
audioVolume: { player: 2, party: -1, enemy: 0.35 },
|
||||
})).toEqual({
|
||||
originalMediaEnabled: false,
|
||||
vfxMode: "generated",
|
||||
vfxQuality: "low",
|
||||
vfxEnabled: { player: false, party: true, enemy: true },
|
||||
@@ -31,8 +32,13 @@ describe("combat presentation", () => {
|
||||
});
|
||||
expect(normalizeCombatPresentationSettings({
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
originalMediaEnabled: true,
|
||||
vfxMode: "original",
|
||||
}).vfxMode).toBe("original");
|
||||
expect(normalizeCombatPresentationSettings({
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
vfxMode: "original",
|
||||
}).vfxMode).toBe("generated");
|
||||
expect(normalizeCombatPresentationSettings({
|
||||
...DEFAULT_COMBAT_PRESENTATION_SETTINGS,
|
||||
vfxMode: "hybrid",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog";
|
||||
import {
|
||||
COMBAT_MEDIA_MANIFEST_URL,
|
||||
combatMediaPackReady,
|
||||
fetchCombatMediaAsset,
|
||||
} from "./combatMediaPack";
|
||||
|
||||
export type CombatActorGroup = "player" | "party" | "enemy";
|
||||
export type CombatVfxMode = "generated" | "original";
|
||||
@@ -24,6 +29,7 @@ export type CombatPresentationSource = "wow335" | "ascension" | "runewaker" | "f
|
||||
export type CombatPresentationPosition = readonly [number, number, number];
|
||||
|
||||
export interface CombatPresentationSettings {
|
||||
readonly originalMediaEnabled: boolean;
|
||||
readonly vfxMode: CombatVfxMode;
|
||||
readonly vfxQuality: CombatVfxQuality;
|
||||
readonly vfxEnabled: Readonly<Record<CombatActorGroup, boolean>>;
|
||||
@@ -34,6 +40,7 @@ export interface CombatPresentationSettings {
|
||||
const ACTOR_GROUPS: readonly CombatActorGroup[] = ["player", "party", "enemy"];
|
||||
|
||||
export const DEFAULT_COMBAT_PRESENTATION_SETTINGS: CombatPresentationSettings = Object.freeze({
|
||||
originalMediaEnabled: false,
|
||||
vfxMode: "generated",
|
||||
vfxQuality: "high",
|
||||
vfxEnabled: Object.freeze({ player: true, party: true, enemy: true }),
|
||||
@@ -80,13 +87,20 @@ export function normalizeCombatPresentationSettings(
|
||||
const record = value && typeof value === "object" && !Array.isArray(value)
|
||||
? value as Partial<Record<keyof CombatPresentationSettings, unknown>>
|
||||
: {};
|
||||
const vfxMode = record.vfxMode === "generated" || record.vfxMode === "original"
|
||||
const originalMediaEnabled = typeof record.originalMediaEnabled === "boolean"
|
||||
? record.originalMediaEnabled
|
||||
: fallback.originalMediaEnabled;
|
||||
const requestedVfxMode = record.vfxMode === "generated" || record.vfxMode === "original"
|
||||
? record.vfxMode
|
||||
: fallback.vfxMode;
|
||||
const vfxMode = requestedVfxMode === "original" && !originalMediaEnabled
|
||||
? "generated"
|
||||
: requestedVfxMode;
|
||||
const vfxQuality = record.vfxQuality === "off" || record.vfxQuality === "low" || record.vfxQuality === "high"
|
||||
? record.vfxQuality
|
||||
: fallback.vfxQuality;
|
||||
return Object.freeze({
|
||||
originalMediaEnabled,
|
||||
vfxMode,
|
||||
vfxQuality,
|
||||
vfxEnabled: booleanGroups(record.vfxEnabled, fallback.vfxEnabled),
|
||||
@@ -191,7 +205,6 @@ interface GeneratedCombatEffectManifest {
|
||||
readonly entries: readonly CombatEffectManifestEntry[];
|
||||
}
|
||||
|
||||
const COMBAT_EFFECT_MANIFEST_URL = "/assets/game/combat-effects/manifest.json";
|
||||
const manifestByKey = new Map<string, CombatEffectManifestEntry>();
|
||||
const manifestBySpellId = new Map<number, CombatEffectManifestEntry>();
|
||||
let manifestRequest: Promise<void> | null = null;
|
||||
@@ -207,13 +220,19 @@ function installCombatEffectManifest(manifest: GeneratedCombatEffectManifest): v
|
||||
export function preloadCombatEffectManifest(): Promise<void> {
|
||||
if (manifestRequest) return manifestRequest;
|
||||
if (typeof fetch === "undefined") return Promise.resolve();
|
||||
manifestRequest = fetch(COMBAT_EFFECT_MANIFEST_URL)
|
||||
manifestRequest = fetchCombatMediaAsset(COMBAT_MEDIA_MANIFEST_URL)
|
||||
.then((response) => response.ok ? response.json() : Promise.reject(new Error(String(response.status))))
|
||||
.then((manifest) => installCombatEffectManifest(manifest as GeneratedCombatEffectManifest))
|
||||
.catch(() => undefined);
|
||||
return manifestRequest;
|
||||
}
|
||||
|
||||
export function effectiveCombatVfxMode(settings: CombatPresentationSettings): CombatVfxMode {
|
||||
return settings.originalMediaEnabled && combatMediaPackReady()
|
||||
? settings.vfxMode
|
||||
: "generated";
|
||||
}
|
||||
|
||||
export function combatEffectManifestEntry(
|
||||
spellId: number | undefined,
|
||||
source?: CombatPresentationSource,
|
||||
|
||||
@@ -83,12 +83,14 @@ import {
|
||||
} from "./talentModifiers";
|
||||
import {
|
||||
getMobPosition,
|
||||
getMobRuntimeTransform,
|
||||
mobRuntimeDistanceSquared,
|
||||
nearbyMobRuntimeIds,
|
||||
removeMobPosition,
|
||||
setMobPosition,
|
||||
type CombatPosition,
|
||||
} from "./mobRuntimeRegistry";
|
||||
import { enemyTelegraphContainsPosition } from "./enemyTelegraphGeometry";
|
||||
import {
|
||||
getPartyRuntimePosition,
|
||||
partyRuntimeDistanceSquared,
|
||||
@@ -299,6 +301,9 @@ export interface EnemyActiveCast {
|
||||
readonly school: EnemyDamageSchool;
|
||||
readonly interruptible: boolean;
|
||||
readonly targetActorId: AggroActorId | null;
|
||||
readonly sourcePosition: CombatPosition;
|
||||
readonly targetPosition: CombatPosition;
|
||||
readonly sourceYaw: number;
|
||||
readonly startedAt: number;
|
||||
readonly completesAt: number;
|
||||
}
|
||||
@@ -6163,6 +6168,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
);
|
||||
if (selectedAttack && startingEnemyCast) {
|
||||
const attack = selectedAttack;
|
||||
const sourceTransform = getMobRuntimeTransform(id);
|
||||
enemyActiveCast = {
|
||||
attackId: attack.id,
|
||||
name: attack.name,
|
||||
@@ -6170,6 +6176,10 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
school: attack.school,
|
||||
interruptible: attack.interruptible !== false,
|
||||
targetActorId: resolution.targetActorId,
|
||||
sourcePosition: [...position],
|
||||
targetPosition: [...targetPosition],
|
||||
sourceYaw: sourceTransform?.yaw
|
||||
?? Math.atan2(targetPosition[0] - position[0], targetPosition[2] - position[2]),
|
||||
startedAt: now,
|
||||
completesAt: now + (attack.castTimeMs ?? 0),
|
||||
};
|
||||
@@ -6187,6 +6197,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
);
|
||||
} else if (selectedAttack && !cannotAttack) {
|
||||
const attack = selectedAttack!;
|
||||
const resolvedEnemyCast = enemyActiveCast;
|
||||
enemyActiveCast = null;
|
||||
const effect = attack.effect ?? {
|
||||
kind: "damage" as const,
|
||||
@@ -6198,10 +6209,24 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
stableCombatUnit(`${id}:${attack.id}:${attackRevision}:target`) * eligibleActors.length,
|
||||
)]
|
||||
: resolution.targetActorId;
|
||||
const telegraphPlacement = resolvedEnemyCast
|
||||
? {
|
||||
sourcePosition: resolvedEnemyCast.sourcePosition,
|
||||
targetPosition: resolvedEnemyCast.targetPosition,
|
||||
sourceYaw: resolvedEnemyCast.sourceYaw,
|
||||
}
|
||||
: {
|
||||
sourcePosition: position,
|
||||
targetPosition,
|
||||
sourceYaw: Math.atan2(targetPosition[0] - position[0], targetPosition[2] - position[2]),
|
||||
};
|
||||
const unvalidatedTargets = attack.target === "nearby-party"
|
||||
? eligibleActors.filter((actorId) => {
|
||||
const candidate = actorPosition(runtimeState, actorId);
|
||||
if (!candidate) return false;
|
||||
if (attack.telegraph) {
|
||||
return enemyTelegraphContainsPosition(attack.telegraph, telegraphPlacement, candidate);
|
||||
}
|
||||
const radius = attack.radius ?? Math.max(1, attack.range * 0.35);
|
||||
const targetDx = candidate[0] - position[0];
|
||||
const targetDy = candidate[1] - position[1];
|
||||
@@ -6298,6 +6323,21 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
targetId: id,
|
||||
now,
|
||||
}).auras];
|
||||
} else if (effect.kind === "target-aura") {
|
||||
for (const actorId of targets) {
|
||||
namedAuras = [...applyCombatAura(namedAuras, {
|
||||
definition: {
|
||||
id: `enemy-debuff:${id}:${attack.id}:${effect.aura}`,
|
||||
name: `${attack.name}: ${effect.aura}`,
|
||||
disposition: "debuff",
|
||||
durationMs: effect.durationMs > 0 ? effect.durationMs : null,
|
||||
dispelCategory: "magic",
|
||||
},
|
||||
sourceId: id,
|
||||
targetId: actorId,
|
||||
now,
|
||||
}).auras];
|
||||
}
|
||||
} else if (effect.kind === "control") {
|
||||
for (const actorId of targets) {
|
||||
if (hasControlImmunity(namedAuras, actorId, effect.mechanic)) continue;
|
||||
|
||||
@@ -167,6 +167,7 @@ describe("generated dungeon campaign catalog", () => {
|
||||
"Fear",
|
||||
"Magic's Bane",
|
||||
"Corrupt Soul",
|
||||
"Soulstorm",
|
||||
]);
|
||||
expect(attackNames(culling!, "Mal'Ganis")).toEqual([
|
||||
"Basic Attack",
|
||||
@@ -204,6 +205,49 @@ describe("generated dungeon campaign catalog", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("compiles Archaedas's timed and health-based encounter mechanics", () => {
|
||||
const uldaman = generated.definitions.find((dungeon) => dungeon.id === "uldaman")!;
|
||||
const archaedas = Object.values(uldaman.entities).find(
|
||||
(entity) => entity.name === "Archaedas",
|
||||
) as unknown as {
|
||||
readonly combat: {
|
||||
readonly mechanicIds: readonly string[];
|
||||
readonly attacks: ReadonlyArray<{
|
||||
readonly id: string;
|
||||
readonly initialCooldownMs?: readonly [number, number];
|
||||
readonly repeatCooldownMs?: readonly [number, number];
|
||||
readonly condition?: { readonly kind: string; readonly maximum?: number };
|
||||
readonly effect?: { readonly kind: string; readonly creatureEntry?: number; readonly count?: number };
|
||||
readonly telegraph?: { readonly shape: string; readonly anchor: string; readonly radius?: number };
|
||||
}>;
|
||||
};
|
||||
};
|
||||
expect(archaedas.combat.mechanicIds).toEqual([
|
||||
"archaedas-awaken-earthen-custodian",
|
||||
"archaedas-ground-tremor",
|
||||
"archaedas-awaken-earthen-guardians",
|
||||
"archaedas-awaken-vault-warders",
|
||||
]);
|
||||
expect(archaedas.combat.attacks.find((attack) => attack.id === "archaedas-ground-tremor"))
|
||||
.toMatchObject({
|
||||
initialCooldownMs: [0, 10_000],
|
||||
repeatCooldownMs: [14_000, 20_000],
|
||||
telegraph: { shape: "circle", anchor: "source", radius: 18 },
|
||||
});
|
||||
expect(archaedas.combat.attacks.find(
|
||||
(attack) => attack.id === "archaedas-awaken-earthen-guardians",
|
||||
)).toMatchObject({
|
||||
condition: { kind: "self-health-percent", maximum: 70 },
|
||||
effect: { kind: "summon", creatureEntry: 7076, count: 6 },
|
||||
});
|
||||
expect(archaedas.combat.attacks.find(
|
||||
(attack) => attack.id === "archaedas-awaken-vault-warders",
|
||||
)).toMatchObject({
|
||||
condition: { kind: "self-health-percent", maximum: 40 },
|
||||
effect: { kind: "summon", creatureEntry: 10120, count: 2 },
|
||||
});
|
||||
});
|
||||
|
||||
it("does not leak outdoor zones, arenas, or raids into the dungeon browser", () => {
|
||||
const ids = new Set(generated.definitions.map((dungeon) => dungeon.id));
|
||||
for (const excluded of [
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { encounterTimeline } from "./encounterTimeline";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
|
||||
describe("encounter mechanic timeline", () => {
|
||||
beforeEach(() => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 40 });
|
||||
useCombatStore.getState().registerMob("boss", {
|
||||
name: "Timeline Boss",
|
||||
boss: true,
|
||||
maxHealth: 1_000,
|
||||
attacks: [{
|
||||
id: "timed-quake",
|
||||
name: "Timed Quake",
|
||||
delivery: "area",
|
||||
target: "nearby-party",
|
||||
school: "nature",
|
||||
animation: "cast",
|
||||
range: 20,
|
||||
radius: 10,
|
||||
cooldownMs: 15_000,
|
||||
repeatCooldownMs: [12_000, 18_000],
|
||||
damageMultiplier: 1,
|
||||
}, {
|
||||
id: "health-adds",
|
||||
name: "Awaken Adds",
|
||||
delivery: "area",
|
||||
target: "self",
|
||||
school: "physical",
|
||||
animation: "cast",
|
||||
range: 0,
|
||||
cooldownMs: 1_000,
|
||||
maximumCasts: 1,
|
||||
damageMultiplier: 0,
|
||||
effect: { kind: "summon", creatureEntry: 1, count: 2 },
|
||||
condition: { kind: "self-health-percent", minimum: 0, maximum: 70 },
|
||||
}],
|
||||
});
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
boss: {
|
||||
...state.mobs.boss,
|
||||
engaged: true,
|
||||
abilityReadyAt: { "timed-quake": 16_000, "health-adds": 1_000 },
|
||||
},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it("shows both timer countdowns and pending health thresholds", () => {
|
||||
const model = encounterTimeline(useCombatStore.getState().mobs, 10_000);
|
||||
expect(model?.bossName).toBe("Timeline Boss");
|
||||
expect(model?.rows.map((row) => [row.name, row.kind, row.value])).toEqual([
|
||||
["Timed Quake", "timer", "6.0s"],
|
||||
["Awaken Adds", "health", "70% HP"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("promotes an active cast and hides a completed one-shot threshold", () => {
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
boss: {
|
||||
...state.mobs.boss,
|
||||
abilityCastCounts: { "health-adds": 1 },
|
||||
activeCast: {
|
||||
attackId: "timed-quake",
|
||||
name: "Timed Quake",
|
||||
school: "nature",
|
||||
interruptible: true,
|
||||
targetActorId: "__player__",
|
||||
sourcePosition: [0, 0, 0],
|
||||
targetPosition: [0, 0, 5],
|
||||
sourceYaw: 0,
|
||||
startedAt: 10_000,
|
||||
completesAt: 12_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
expect(encounterTimeline(useCombatStore.getState().mobs, 11_250)?.rows).toMatchObject([{
|
||||
name: "Timed Quake",
|
||||
kind: "casting",
|
||||
value: "0.8s",
|
||||
}]);
|
||||
});
|
||||
|
||||
it("keeps every pending health threshold visible beyond the countdown row limit", () => {
|
||||
const boss = useCombatStore.getState().mobs.boss;
|
||||
const extraTimers = Array.from({ length: 5 }, (_, index) => ({
|
||||
...boss.attacks[0],
|
||||
id: `extra-timer-${index}`,
|
||||
name: `Extra Timer ${index}`,
|
||||
}));
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
boss: {
|
||||
...state.mobs.boss,
|
||||
attacks: [...state.mobs.boss.attacks, ...extraTimers],
|
||||
abilityReadyAt: {
|
||||
...state.mobs.boss.abilityReadyAt,
|
||||
...Object.fromEntries(extraTimers.map((attack, index) => [attack.id, 11_000 + index * 1_000])),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const rows = encounterTimeline(useCombatStore.getState().mobs, 10_000, 4)?.rows ?? [];
|
||||
expect(rows.filter((row) => row.kind === "timer")).toHaveLength(4);
|
||||
expect(rows.filter((row) => row.kind === "health").map((row) => row.value)).toEqual(["70% HP"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { MobCombatState } from "./combatStore";
|
||||
import type { EnemyDamageSchool, EnemyTelegraphShape } from "./enemyAttacks";
|
||||
|
||||
export type EncounterTimelineKind = "casting" | "timer" | "health";
|
||||
|
||||
export interface EncounterTimelineRow {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly kind: EncounterTimelineKind;
|
||||
readonly value: string;
|
||||
readonly progress: number;
|
||||
readonly sortAt: number;
|
||||
readonly school: EnemyDamageSchool;
|
||||
readonly telegraphShape?: EnemyTelegraphShape;
|
||||
}
|
||||
|
||||
export interface EncounterTimelineModel {
|
||||
readonly bossId: string;
|
||||
readonly bossName: string;
|
||||
readonly rows: readonly EncounterTimelineRow[];
|
||||
}
|
||||
|
||||
function clampedProgress(value: number): number {
|
||||
return Math.min(1, Math.max(0, value));
|
||||
}
|
||||
|
||||
export function encounterTimeline(
|
||||
mobs: Readonly<Record<string, MobCombatState>>,
|
||||
now: number,
|
||||
limit = 4,
|
||||
): EncounterTimelineModel | null {
|
||||
const boss = Object.values(mobs)
|
||||
.filter((mob) => mob.boss && mob.engaged && !mob.dead && !mob.despawned)
|
||||
.sort((left, right) => (
|
||||
Number(Boolean(right.activeCast)) - Number(Boolean(left.activeCast))
|
||||
|| left.id.localeCompare(right.id)
|
||||
))[0];
|
||||
if (!boss) return null;
|
||||
|
||||
const healthPercent = (boss.health / Math.max(1, boss.maxHealth)) * 100;
|
||||
const rows = boss.attacks.flatMap<EncounterTimelineRow>((attack) => {
|
||||
if (attack.id === "basic-attack") return [];
|
||||
const castCount = boss.abilityCastCounts[attack.id] ?? 0;
|
||||
if (attack.maximumCasts && castCount >= attack.maximumCasts) return [];
|
||||
const common = {
|
||||
id: attack.id,
|
||||
name: attack.name,
|
||||
school: attack.school,
|
||||
...(attack.telegraph ? { telegraphShape: attack.telegraph.shape } : {}),
|
||||
};
|
||||
if (boss.activeCast?.attackId === attack.id) {
|
||||
const duration = Math.max(1, boss.activeCast.completesAt - boss.activeCast.startedAt);
|
||||
const remaining = Math.max(0, boss.activeCast.completesAt - now);
|
||||
return [{
|
||||
...common,
|
||||
kind: "casting",
|
||||
value: `${(remaining / 1_000).toFixed(1)}s`,
|
||||
progress: clampedProgress(remaining / duration),
|
||||
sortAt: -1,
|
||||
}];
|
||||
}
|
||||
if (attack.condition?.kind === "self-health-percent") {
|
||||
const threshold = attack.condition.maximum;
|
||||
return [{
|
||||
...common,
|
||||
kind: "health",
|
||||
value: `${Math.round(threshold)}% HP`,
|
||||
progress: clampedProgress(healthPercent / Math.max(1, threshold)),
|
||||
sortAt: 1_000_000 + (100 - threshold) * 1_000,
|
||||
}];
|
||||
}
|
||||
const readyAt = boss.abilityReadyAt[attack.id];
|
||||
if (!Number.isFinite(readyAt)) return [];
|
||||
const remaining = Math.max(0, (readyAt ?? now) - now);
|
||||
const cooldown = Math.max(
|
||||
1,
|
||||
attack.repeatCooldownMs?.[1] ?? attack.initialCooldownMs?.[1] ?? attack.cooldownMs,
|
||||
);
|
||||
return [{
|
||||
...common,
|
||||
kind: "timer",
|
||||
value: `${(remaining / 1_000).toFixed(1)}s`,
|
||||
progress: clampedProgress(remaining / cooldown),
|
||||
sortAt: remaining,
|
||||
}];
|
||||
}).sort((left, right) => left.sortAt - right.sortAt || left.id.localeCompare(right.id));
|
||||
|
||||
const alwaysVisibleHealthRows = rows.filter((row) => row.kind === "health");
|
||||
const countdownRows = rows
|
||||
.filter((row) => row.kind !== "health")
|
||||
.slice(0, Math.max(1, limit));
|
||||
const visibleRows = [...countdownRows, ...alwaysVisibleHealthRows];
|
||||
|
||||
return visibleRows.length ? {
|
||||
bossId: boss.id,
|
||||
bossName: boss.name,
|
||||
rows: visibleRows,
|
||||
} : null;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeEnemyAttack, type EnemyAttackDefinition } from "./enemyAttacks";
|
||||
|
||||
function attack(overrides: Partial<EnemyAttackDefinition>): EnemyAttackDefinition {
|
||||
return {
|
||||
id: "test-special",
|
||||
name: "Test Special",
|
||||
delivery: "area",
|
||||
target: "nearby-party",
|
||||
school: "physical",
|
||||
animation: "attack",
|
||||
range: 12,
|
||||
cooldownMs: 10_000,
|
||||
damageMultiplier: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("enemy attack telegraphs", () => {
|
||||
it("infers a warned cone for legacy frontal mechanics", () => {
|
||||
const normalized = normalizeEnemyAttack(attack({ name: "Stone Cleave" }));
|
||||
expect(normalized.telegraph).toMatchObject({
|
||||
shape: "cone",
|
||||
anchor: "source",
|
||||
warningMs: 1_650,
|
||||
});
|
||||
expect(normalized.castTimeMs).toBe(1_650);
|
||||
});
|
||||
|
||||
it("preserves authored geometry and extends too-short cast warnings", () => {
|
||||
const normalized = normalizeEnemyAttack(attack({
|
||||
name: "Falling Ruin",
|
||||
castTimeMs: 500,
|
||||
telegraph: {
|
||||
shape: "circle",
|
||||
anchor: "target",
|
||||
warningMs: 2_200,
|
||||
radius: 7,
|
||||
},
|
||||
}));
|
||||
expect(normalized.castTimeMs).toBe(2_200);
|
||||
expect(normalized.telegraph).toEqual({
|
||||
shape: "circle",
|
||||
anchor: "target",
|
||||
warningMs: 2_200,
|
||||
radius: 7,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not telegraph basic attacks or beneficial mechanics", () => {
|
||||
expect(normalizeEnemyAttack(attack({ id: "basic-attack" })).telegraph).toBeUndefined();
|
||||
expect(normalizeEnemyAttack(attack({
|
||||
effect: { kind: "heal", multiplier: 1 },
|
||||
})).telegraph).toBeUndefined();
|
||||
});
|
||||
});
|
||||
+106
-2
@@ -17,6 +17,21 @@ export type EnemyDamageSchool =
|
||||
|
||||
export type EnemyControlMechanic = "fear" | "sleep" | "root" | "stun" | "silence";
|
||||
|
||||
export type EnemyTelegraphShape = "circle" | "cone" | "line";
|
||||
export type EnemyTelegraphAnchor = "source" | "target";
|
||||
|
||||
/** World-space warning geometry shared by simulation and Three presentation. */
|
||||
export interface EnemyAttackTelegraph {
|
||||
readonly shape: EnemyTelegraphShape;
|
||||
readonly anchor: EnemyTelegraphAnchor;
|
||||
/** Minimum warning time before the mechanic resolves. */
|
||||
readonly warningMs: number;
|
||||
readonly radius?: number;
|
||||
readonly length?: number;
|
||||
readonly width?: number;
|
||||
readonly angleDegrees?: number;
|
||||
}
|
||||
|
||||
export type EnemyAbilityEffect =
|
||||
| { readonly kind: "damage"; readonly multiplier: number }
|
||||
| { readonly kind: "heal"; readonly multiplier: number }
|
||||
@@ -26,6 +41,11 @@ export type EnemyAbilityEffect =
|
||||
readonly durationMs: number;
|
||||
readonly magnitude?: number;
|
||||
}
|
||||
| {
|
||||
readonly kind: "target-aura";
|
||||
readonly aura: string;
|
||||
readonly durationMs: number;
|
||||
}
|
||||
| {
|
||||
readonly kind: "control";
|
||||
readonly mechanic: EnemyControlMechanic;
|
||||
@@ -83,6 +103,7 @@ export interface EnemyAttackDefinition {
|
||||
readonly maximumCasts?: number;
|
||||
readonly damageMultiplier: number;
|
||||
readonly radius?: number;
|
||||
readonly telegraph?: EnemyAttackTelegraph;
|
||||
readonly effect?: EnemyAbilityEffect;
|
||||
readonly condition?: EnemyAbilityCondition;
|
||||
readonly source?: EnemyAbilitySource;
|
||||
@@ -96,6 +117,8 @@ export interface EnemyCombatDefinition {
|
||||
readonly moveSpeed?: number;
|
||||
readonly aggroRange?: number;
|
||||
readonly leashRange?: number;
|
||||
/** Stable special-attack ids available for future encounter loadout randomization. */
|
||||
readonly mechanicIds?: readonly string[];
|
||||
readonly attacks: readonly EnemyAttackDefinition[];
|
||||
}
|
||||
|
||||
@@ -125,14 +148,94 @@ function normalizeCooldownRange(
|
||||
return Object.freeze([Math.round(lower), Math.round(upper)]);
|
||||
}
|
||||
|
||||
function harmfulEnemyAttack(value: EnemyAttackDefinition): boolean {
|
||||
return !value.effect
|
||||
|| value.effect.kind === "damage"
|
||||
|| value.effect.kind === "control"
|
||||
|| value.effect.kind === "target-aura";
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy/imported encounters receive safe defaults while authored mechanics can
|
||||
* override every dimension in the modular mechanic catalog.
|
||||
*/
|
||||
export function inferEnemyAttackTelegraph(
|
||||
value: EnemyAttackDefinition,
|
||||
): EnemyAttackTelegraph | undefined {
|
||||
if (value.id === "basic-attack" || !harmfulEnemyAttack(value)) return undefined;
|
||||
const radius = value.radius ?? Math.max(3, value.range * 0.35);
|
||||
if (/\b(?:breath|cleave|frontal|sweep|cone)\b/i.test(value.name)) {
|
||||
return {
|
||||
shape: "cone",
|
||||
anchor: "source",
|
||||
warningMs: 1_650,
|
||||
length: Math.max(5.5, value.range),
|
||||
angleDegrees: /breath/i.test(value.name) ? 70 : 95,
|
||||
};
|
||||
}
|
||||
if (/\b(?:beam|charge|line|lance)\b/i.test(value.name)) {
|
||||
return {
|
||||
shape: "line",
|
||||
anchor: "source",
|
||||
warningMs: 1_650,
|
||||
length: Math.max(7, value.range),
|
||||
width: Math.max(2, radius * 0.45),
|
||||
};
|
||||
}
|
||||
if (value.delivery === "area" || value.radius !== undefined) {
|
||||
const targetAnchored = /\b(?:blizzard|rain|barrage|volley|eruption|ground|meteor)\b/i
|
||||
.test(value.name) && value.target !== "self";
|
||||
return {
|
||||
shape: "circle",
|
||||
anchor: targetAnchored ? "target" : "source",
|
||||
warningMs: 1_650,
|
||||
radius,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeTelegraph(value: EnemyAttackTelegraph): EnemyAttackTelegraph {
|
||||
const warningMs = Math.round(finiteAtLeast(value.warningMs, 1_650, 250));
|
||||
if (value.shape === "circle") {
|
||||
return Object.freeze({
|
||||
shape: value.shape,
|
||||
anchor: value.anchor,
|
||||
warningMs,
|
||||
radius: finiteAtLeast(value.radius, 4, 0.5),
|
||||
});
|
||||
}
|
||||
if (value.shape === "cone") {
|
||||
return Object.freeze({
|
||||
shape: value.shape,
|
||||
anchor: "source",
|
||||
warningMs,
|
||||
length: finiteAtLeast(value.length, 8, 1),
|
||||
angleDegrees: Math.min(180, finiteAtLeast(value.angleDegrees, 90, 5)),
|
||||
});
|
||||
}
|
||||
return Object.freeze({
|
||||
shape: value.shape,
|
||||
anchor: "source",
|
||||
warningMs,
|
||||
length: finiteAtLeast(value.length, 8, 1),
|
||||
width: finiteAtLeast(value.width, 3, 0.5),
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeEnemyAttack(
|
||||
value: EnemyAttackDefinition,
|
||||
fallback: EnemyAttackDefinition = DEFAULT_ENEMY_ATTACK,
|
||||
): EnemyAttackDefinition {
|
||||
const cooldownMs = Math.round(finiteAtLeast(value.cooldownMs, fallback.cooldownMs, 1));
|
||||
const telegraph = value.telegraph ?? inferEnemyAttackTelegraph(value);
|
||||
const inferredCastTimeMs = value.castTimeMs === undefined && value.animation === "cast"
|
||||
? (fallback.castTimeMs ?? 1_500)
|
||||
: (fallback.castTimeMs ?? 0);
|
||||
: telegraph?.warningMs ?? fallback.castTimeMs ?? 0;
|
||||
const castTimeMs = Math.max(
|
||||
Math.round(finiteAtLeast(value.castTimeMs, inferredCastTimeMs, 0)),
|
||||
telegraph?.warningMs ?? 0,
|
||||
);
|
||||
return Object.freeze({
|
||||
id: value.id.trim() || fallback.id,
|
||||
name: value.name.trim() || fallback.name,
|
||||
@@ -143,7 +246,7 @@ export function normalizeEnemyAttack(
|
||||
target: value.target,
|
||||
school: value.school,
|
||||
animation: value.animation,
|
||||
castTimeMs: Math.round(finiteAtLeast(value.castTimeMs, inferredCastTimeMs, 0)),
|
||||
castTimeMs,
|
||||
interruptible: value.interruptible ?? fallback.interruptible ?? true,
|
||||
range: finiteAtLeast(value.range, fallback.range, 0),
|
||||
cooldownMs,
|
||||
@@ -160,6 +263,7 @@ export function normalizeEnemyAttack(
|
||||
...(value.radius !== undefined
|
||||
? { radius: finiteAtLeast(value.radius, 0, 0) }
|
||||
: {}),
|
||||
...(telegraph ? { telegraph: normalizeTelegraph(telegraph) } : {}),
|
||||
...(value.effect ? { effect: value.effect } : {}),
|
||||
...(value.condition ? { condition: value.condition } : {}),
|
||||
...(value.source ? { source: value.source } : {}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import {
|
||||
deterministicEnemyCooldown,
|
||||
useCombatStore,
|
||||
@@ -171,6 +172,42 @@ describe("server-backed enemy ability runtime", () => {
|
||||
expect(summonIds.every((id) => getMobPosition(id) === null)).toBe(true);
|
||||
});
|
||||
|
||||
it("applies a modular target aura as a dispellable player debuff", () => {
|
||||
useCombatStore.getState().registerMob("runewaker-debuffer", {
|
||||
name: "RuneWaker Debuffer",
|
||||
maxHealth: 1_000,
|
||||
attacks: [{
|
||||
id: "conductivity",
|
||||
name: "Conductivity",
|
||||
delivery: "projectile",
|
||||
target: "primary",
|
||||
school: "arcane",
|
||||
animation: "cast",
|
||||
castTimeMs: 1_000,
|
||||
range: 20,
|
||||
cooldownMs: 10_000,
|
||||
initialCooldownMs: [0, 0],
|
||||
repeatCooldownMs: [10_000, 10_000],
|
||||
damageMultiplier: 0,
|
||||
effect: { kind: "target-aura", aura: "conductivity", durationMs: 6_000 },
|
||||
}],
|
||||
});
|
||||
setMobPosition("runewaker-debuffer", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("runewaker-debuffer", 1, 1_000);
|
||||
useCombatStore.getState().advanceMobCombat(1_000, [0, 0, 0]);
|
||||
useCombatStore.getState().advanceMobCombat(2_000, [0, 0, 0]);
|
||||
|
||||
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
||||
sourceId: "runewaker-debuffer",
|
||||
targetId: PLAYER_AGGRO_ID,
|
||||
expiresAt: 8_000,
|
||||
definition: expect.objectContaining({
|
||||
disposition: "debuff",
|
||||
dispelCategory: "magic",
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it("honors encounter-state conditions before selecting an ability", () => {
|
||||
useWailingEncounterStore.getState().reset();
|
||||
useCombatStore.getState().registerMob("event-caster", {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { enemyTelegraphContainsPosition } from "./enemyTelegraphGeometry";
|
||||
|
||||
const placement = {
|
||||
sourcePosition: [0, 0, 0] as const,
|
||||
targetPosition: [0, 0, 10] as const,
|
||||
sourceYaw: 0,
|
||||
};
|
||||
|
||||
describe("enemy telegraph gameplay geometry", () => {
|
||||
it("matches source and target anchored circles", () => {
|
||||
expect(enemyTelegraphContainsPosition({
|
||||
shape: "circle", anchor: "source", warningMs: 1_000, radius: 4,
|
||||
}, placement, [3, 0, 0])).toBe(true);
|
||||
expect(enemyTelegraphContainsPosition({
|
||||
shape: "circle", anchor: "target", warningMs: 1_000, radius: 3,
|
||||
}, placement, [0, 0, 8])).toBe(true);
|
||||
expect(enemyTelegraphContainsPosition({
|
||||
shape: "circle", anchor: "target", warningMs: 1_000, radius: 3,
|
||||
}, placement, [0, 0, 4])).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps cone hits in front and within the visible angle", () => {
|
||||
const cone = {
|
||||
shape: "cone" as const,
|
||||
anchor: "source" as const,
|
||||
warningMs: 1_000,
|
||||
length: 10,
|
||||
angleDegrees: 90,
|
||||
};
|
||||
expect(enemyTelegraphContainsPosition(cone, placement, [2, 0, 7])).toBe(true);
|
||||
expect(enemyTelegraphContainsPosition(cone, placement, [8, 0, 2])).toBe(false);
|
||||
expect(enemyTelegraphContainsPosition(cone, placement, [0, 0, -2])).toBe(false);
|
||||
});
|
||||
|
||||
it("uses the visible line width for collision", () => {
|
||||
const line = {
|
||||
shape: "line" as const,
|
||||
anchor: "source" as const,
|
||||
warningMs: 1_000,
|
||||
length: 12,
|
||||
width: 4,
|
||||
};
|
||||
expect(enemyTelegraphContainsPosition(line, placement, [1.9, 0, 8])).toBe(true);
|
||||
expect(enemyTelegraphContainsPosition(line, placement, [2.1, 0, 8])).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { EnemyAttackTelegraph } from "./enemyAttacks";
|
||||
import type { CombatPosition } from "./mobRuntimeRegistry";
|
||||
|
||||
export interface EnemyTelegraphPlacement {
|
||||
readonly sourcePosition: CombatPosition;
|
||||
readonly targetPosition: CombatPosition;
|
||||
readonly sourceYaw: number;
|
||||
}
|
||||
|
||||
export function enemyTelegraphYaw(placement: EnemyTelegraphPlacement): number {
|
||||
const dx = placement.targetPosition[0] - placement.sourcePosition[0];
|
||||
const dz = placement.targetPosition[2] - placement.sourcePosition[2];
|
||||
return dx * dx + dz * dz > 0.0001 ? Math.atan2(dx, dz) : placement.sourceYaw;
|
||||
}
|
||||
|
||||
export function enemyTelegraphOrigin(
|
||||
telegraph: EnemyAttackTelegraph,
|
||||
placement: EnemyTelegraphPlacement,
|
||||
): CombatPosition {
|
||||
return telegraph.anchor === "target"
|
||||
? placement.targetPosition
|
||||
: placement.sourcePosition;
|
||||
}
|
||||
|
||||
/** Matches the visible warning mesh in the X/Z combat plane. */
|
||||
export function enemyTelegraphContainsPosition(
|
||||
telegraph: EnemyAttackTelegraph,
|
||||
placement: EnemyTelegraphPlacement,
|
||||
candidate: CombatPosition,
|
||||
): boolean {
|
||||
const origin = enemyTelegraphOrigin(telegraph, placement);
|
||||
const dx = candidate[0] - origin[0];
|
||||
const dz = candidate[2] - origin[2];
|
||||
if (telegraph.shape === "circle") {
|
||||
const radius = telegraph.radius ?? 0;
|
||||
return dx * dx + dz * dz <= radius * radius;
|
||||
}
|
||||
|
||||
const yaw = enemyTelegraphYaw(placement);
|
||||
const forwardX = Math.sin(yaw);
|
||||
const forwardZ = Math.cos(yaw);
|
||||
const forward = dx * forwardX + dz * forwardZ;
|
||||
const side = Math.abs(dx * forwardZ - dz * forwardX);
|
||||
const length = telegraph.length ?? 0;
|
||||
if (forward < 0 || forward > length) return false;
|
||||
if (telegraph.shape === "line") return side <= (telegraph.width ?? 0) / 2;
|
||||
const distance = Math.hypot(dx, dz);
|
||||
if (distance <= 0.0001) return true;
|
||||
const halfAngle = ((telegraph.angleDegrees ?? 0) * Math.PI) / 360;
|
||||
return Math.acos(Math.min(1, Math.max(-1, forward / distance))) <= halfAngle;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -156,7 +156,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -416,7 +416,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -496,7 +496,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -596,7 +596,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -696,7 +696,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -836,7 +836,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -876,7 +876,7 @@ export const AETERNAL_CIRCLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-aeternal-circle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -2303,6 +2303,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,7 +16,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -296,7 +296,7 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -355,9 +355,14 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-barren-caves-101462-spell-492292",
|
||||
"rw-barren-caves-101462-spell-492293",
|
||||
"rw-barren-caves-101462-control-492294"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-barren-caves-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -368,13 +373,14 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-barren-caves-spell-492292",
|
||||
"id": "rw-barren-caves-101462-spell-492292",
|
||||
"name": "Trample",
|
||||
"spellId": 492292,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -385,16 +391,24 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-barren-caves-spell-492293",
|
||||
"id": "rw-barren-caves-101462-spell-492293",
|
||||
"name": "Bash",
|
||||
"spellId": 492293,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -405,7 +419,46 @@ export const BARREN_CAVES_ENTITIES = {
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-barren-caves-101462-control-492294",
|
||||
"name": "Gulp",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "stun",
|
||||
"durationMs": 3000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1009,6 +1062,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -116,7 +116,7 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -255,9 +255,18 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-cave-water-dragon-102623-spell-494249",
|
||||
"rw-cave-water-dragon-102623-spell-494250",
|
||||
"rw-cave-water-dragon-102623-spell-494253",
|
||||
"rw-cave-water-dragon-102623-spell-494254",
|
||||
"rw-cave-water-dragon-102623-debuff-494297",
|
||||
"rw-cave-water-dragon-102623-debuff-494298",
|
||||
"rw-cave-water-dragon-102623-phase-45"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -266,6 +275,193 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-spell-494249",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"spellId": 494249,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-spell-494250",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"spellId": 494250,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-spell-494253",
|
||||
"name": "Water Dragon Bomb",
|
||||
"spellId": 494253,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-spell-494254",
|
||||
"name": "Dragon Attack",
|
||||
"spellId": 494254,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 10,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-debuff-494297",
|
||||
"name": "Law of the Dragons",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "confusion",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-debuff-494298",
|
||||
"name": "Law of the Dragons",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "confusion",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-102623-phase-45",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-45",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 45
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -296,9 +492,15 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-cave-water-dragon-104161-spell-493996",
|
||||
"rw-cave-water-dragon-104161-spell-493999",
|
||||
"rw-cave-water-dragon-104161-spell-494000",
|
||||
"rw-cave-water-dragon-104161-aura-494386"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-cave-water-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -307,6 +509,115 @@ export const CAVE_WATER_DRAGON_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-104161-spell-493996",
|
||||
"name": "Water Spirit Attack",
|
||||
"spellId": 493996,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-104161-spell-493999",
|
||||
"name": "Wave Bomb",
|
||||
"spellId": 493999,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 20,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-104161-spell-494000",
|
||||
"name": "Wave Strike",
|
||||
"spellId": 494000,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 20,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-cave-water-dragon-104161-aura-494386",
|
||||
"name": "Soul of the Water Dragon",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "soul-of-the-water-dragon",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1458,6 +1769,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} 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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -116,7 +116,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -156,7 +156,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -236,7 +236,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -396,7 +396,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -536,7 +536,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -696,7 +696,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -736,7 +736,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -776,7 +776,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -816,7 +816,7 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -855,9 +855,15 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-demon-stronghold-102701-aura-494871",
|
||||
"rw-demon-stronghold-102701-aura-494873",
|
||||
"rw-demon-stronghold-102701-spell-494875",
|
||||
"rw-demon-stronghold-102701-spell-494876"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-demon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -866,6 +872,105 @@ export const DEMON_STRONGHOLD_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-demon-stronghold-102701-aura-494871",
|
||||
"name": "Hurricane",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "hurricane",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-demon-stronghold-102701-aura-494873",
|
||||
"name": "Struggle",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 10000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "struggle",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-demon-stronghold-102701-spell-494875",
|
||||
"name": "Double Cyclone",
|
||||
"spellId": 494875,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 3000,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-demon-stronghold-102701-spell-494876",
|
||||
"name": "Titanic Attack",
|
||||
"spellId": 494876,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2954,6 +3059,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -1,2 +1,2 @@
|
||||
// Generated by npm run dungeon:catalog. Do not edit.
|
||||
export default "H4sIAAAAAAACCuy9bY/jOJIu+lcMnwOcWcBmMYLvBZwP/bKzM/dO7/Tt2jODi0FhQUlUpqecVl7ZWdXVjf7vF0FKtmzJaTntrsrKqf0w22WmRDIUEYyXJ4K/Ttf5bbjzfwv1elGtpq9hNl1XD3Uepq9/neb+7t4vblY/+NWiDOvN9PW0eFjdhGo1v1/ch+ViFV61P7R/y/65rlbT2TSs3i/qanUXVpsfff7O34T19PU/pus6f3Xj78Krm7AKtd+E4lW5eB9+XPqPof4+vevPd/dVvVm3bxp45M6v/HpT1XffrNdh+/74wOQPm0DP+/rjpPTLZebzd/82fUsLyquH1SbU3/mNX1Y309ePvvmnh9VmcReaP27X4n8JdbW5/a6qw3fV3d2CSCILV0AhM2VQBAsuV7lwvNS5BzAGrSiyMrNB7D/+JlL5L1X+bnghg3+6Jcl9WC63+/h16td5WKUPOGXs1V98sXi3/uHH/+ffi8Wmql/ly0VYbeb5Q12H1eaVr4OfK3x17zf57fz7V99/+8fFMqy/i3/16g29mxVZTh/xvspv0zv/FPwy1D/41fzNpqr9TXjVssD8Q1W/a/lg/So+M09THn31b7/Npnn1PtCLpq//8WvLV38uiBq3vqg+lH51M38Xwv10Nr3z9zQiROczrqevDf1zs9gsiLcEse7Gbxb5m3v/YbWevgbuZtO68neL1Q3xyHr6GuWOv9uvkVd1mP42O1gD8cCHxaqYrzdV/s4XobMMub8M1V0G9JbhxOEq+MhFFMEXd4tVWHfm1o+QALFHAtMngRg5+we/WC5WN/Pcvw/1qrMGefAZ7N4a+OEaUOHhGszIJdT+l6ouw2r+rvYPy84KzGOMYEdQAXDkErKlz9+VfnNb3c2LEO67dLD7q9B7dDA9OnDorWIsJzwsC3/nV7u5DT+LAoLjkylws6ruQh1uutO7g+nl45IoTG/rAsZK4sPqXVjNSasvO0IYhfv4EkSP+lIdLkGey4VF9aErB4DuMQZQPTZ0svcRxgrjOvf1MmzmdxWdT6H+2FmGdY/oI9HjQ0Fcd7AMPXIZvzwsS1/X/t1ueuSPTY8wQh2APEcY6yp/N1/fL+oON+Dhp4C9VZieQCite4oRz15FEe43tx2OQHEgFyC6y9ADR5Tsfww1miduq2V151d5lxKH3GBPaAZSVE9dwKYmxbi86x6Oh1/CdedXvdNBip5qoDeMmv/O1/6hqFbdo/nR3fe0gsKeVkA+Wi3chHJRh3l+69d3nTXYxzRT3z6A/hcYKw8FTX+3fzT2JAH3JKGnlYzqSYIa+wHWt36zCXUo5rd+uewIgpIHgrAnB6Jvpzj7ZNWYLauqmJcP9cp3BUFJfGQJ0FOLINyTl3AblsvICjV5QfVmjxIH1tLe54AeJax68kG93gR/994/LDfd6dV5VBhQB2OttYdVEeqsuunOrh8RhQFzVcOTzdX10r8P8/uw2iO+eWT3OCCKvbNJjKW+r3O/qf0vndkVPrZ7d/rTj5XDrNr41SL33bnFY6ey65vI8GTuvwv5rV/5uju7PIvrrHyySZCcxfnSZx/rxWpz212EOo/57NMVwDpsbsO7dz01qPRjwq8HzIEne6wUuZhvqrtsb/7H+D/SeH9+cE82BvxDfhtWfjHP64/3expQ2ceIIE/7y8KMpsHNgqzj9XwT6nr/OLCPKsK+zyh7ilCfYx/O76rar7u2oXaPLGDgOHqyVRDWub8P87Iml/WhXt1Wy274QunHzua+ahC2/0HGLuVhc+PrIhzEcZR5TD0MSCbg0w+lZgX3i9XK510PUpnH9MMYJTWWCpvbMF+Fnx+6QmH0Y75jTzOgeLqKpOmr/GG5P799bPNjPsHYgzF/WMY4UlXOhxwG5R5V0/1wEjw5nha1c1pHtdpbgjuLE4x5spVS1P7d5tbX1epAJjTnZ51Wzj3Zife//LNazlehfsi688Nj+rHnORn+VH7YfoXl4uZ2s1qsbrqrwLOYYYgtxwaWbh5WxV4kQ/PHArv9ya15cjzv/aKiaA5p5u789pFP0HPc3FPFwN+u3vnbsJlXy2L+brG6KaqO+6rh0XhO32YQ4sk2Q1nVNyEKZPXQtZu0wMdMhh4zyp7xasfqxnrhl7QCUpL5rb+7X3TjCVrxR4jR+yTqqZ/kfrGJZCDF0J39MYYQfWnQ9snO41Yq61AuQ77ZJ4N+1ITrL+TJKnqzWL0L9Sbkt/P1bfWBIq67ZVg80FHQXUafM/Vjq7ivqzwUD7Vfzts8YZ8/6+qXsJrXwee33eC3/cTLqOqbarMJqzllgzqRX2N/b4K8nU2LUC5WC2KIdUzV9TPGi+Gs3WaxWdKL32xHJv93L5+3LG/atG9B77eUqv353rfpzHzp1+tFvp9R/qEqIiMVm/ntx6xekBItFmW5yB+Wm49/8/XCrzZptXFtq6q+88u9P+r+uib6/ODX7+JuluF9WP7kVzEzCXYm+NuGDBRa+X77hj/vvdm/94ulzxbLRXx3889ob97XYR1W9CVoR79Ol1Xe/PcAaTa34Y729tf7sKLM6rKYhJ/vQ7FoJHL9cHfna5rij3R8Tja3dfVwczs5eNNsUoQy+M1ksVlPthwyqStyj2YTvyom9JUmm9swoYjVx4lfLt4HNp1Nl5UvFqubH8J6HbOz0x/rcO/rxermcBLG6O8fVvmtrzeh+KYO/j99XP3/aX+b9HdIjHVTVw8rot//4JYXnD5vmVLZebWs6jjgAYFPZ9NV8HVKfJb0H6j5b7Opv8sopfwXosHeY95m0pFds1htKCdOX4MzxN9m09twt1jf34Y6bB9bv/v4XfukNS63FEJJi9v+zh3PuDt8o5a/zWL8NapKv+yvpMDcuGL/OZhNq7Jchw2xFs6Qz+bm7W8kEJtAJ9E6vuFhUz1sfoyh1Shz0//+Fv4nYRjSCNGYfn47m2bLsCr2/xLjX8aBzh/6gjjofdj/Wxn/th3b/nlc0D0t5cOi2NxSkJ4T+dIWBVmq9/TQ6iYZBHm12lQPdXzpDygnyNXkO6snAHYCEicozQS5nYCByRsh5MTARHI9AdTT2fQHYSco3OQ74G4CVkzA4AS1naDk8bE3QsqJ0xPhzMSY6VsCD3gCfkRqvV+sH/xyJ+vNv2fTcrEMDTum39jNkhjjoV5OX09fpTck2MUW9fFKiPlOkb3aey6/Dfm79cPd9PVU5DJg4YKzgcAd4AnlEQQEoQCd1VwFb2VWWCEQweXccyO8B2kzV3pjSI4XvwSylJw1UstZNERWN8vwHUnq9LV0yIGT3smr5XKRlGG7xe1Pc875/k73hs7f8NDjnX1zV+Y65zygB1eW0uZQZLLIrAtZcByU4FnhLBgshTOZlgFcYYTnipeF0byzb5BO93atlRKaTr/eNuH4NuGybUJ/m1qDNaC8F76wXmPmjFXcAXijQTulNDfCclGgsKhECcCzHHOEkmORO9XdpqZoy+ht4vFt4mXbxP42jQKFslQ2A6N9gCxTWYG68EaZQuSGF2iUyApjjc2dUtzLzCuvyjwIrnzW2SZytGdsUxzfprhsm6K/zUyA17JEiSrnRa5UaQBQIqIw3JWuKJQP3ilecq28VCZ4m5fATebKkPuw9zXtOduUx7cpL9um7G8TMpCldzJDmxtZWs4DbYWjtNKWyktjpeCG+zLLi8y5LMtDKDOdO5tnKNS+bOIZ21THt6ku26YaYFpwTmbeGuXLzOSIFkvMNfhMSSu4A64CWu+VK2xWZAGzEoo8U7r0PofgOtuUTp/zNfXxberLtqn728SyhBJzg4XgptAauFZZ8KYseY5K0OIBICAP0mnIJGRCYGm5QZlpB7KzTWXlOds0x7dpLtum6W+zyJQ3nKPPvcid92BLMDxoHoQrNYpcWlAh1y5ojTKoUqIJeeaKQgjnMtvZppZcnrFNe3yb9rJt2oEDpfBZoYWQvNCobaa1R+O9DwY5VyorA2RYhKC9U4BBYs6tLrEUpdXS2e42BVfnyKY7vk132TZdf5tWccsDlmDyXFuygXyweVkI64PI8pBnRikRQDqjdCh5UFxaZ70zUhhfdg8UpfCMcxOOW0FwmRUEA1aQ45gJWwSUIstKEXwZiiCdLbISTZlxLaCQ4HhRClMi5JzzQkJJpgFCZnbbFE5Z3pdNo4Vzg9s8bgXBZVYQDFlBaHNhM2VlrvPCegxo0YFzygZrJHhe6kJlpQ82aCkNF7km9HPBQwiye6A4C+dYQXDcCoLLrCAYsIJUrp1x5Cy7TOvcGG4hZCYYHbDMJGgMMoCWPCAvueAoMGAuM8+d1KZzoCAIMaRptbFycJvHrSC4zAqCASvIQR5MnjtTGIVaZcgLcFoorRVA4bh1VqEDL/Lcl4UN0ijNy5znWgReio4KkgIk9G130lzDn/O4GQSXmUEwYAZpsmezTJKBIHRRZii01nnhvRWl5WC1Ba+dFegKlXsM2kmhfamx5NYZv/ucaMSAcGojrBrc5nEzCC4zg2DADOKZsJgJ8kc0Wh8ynkuvcy68kmB9yDEIIzELRYGZxcDzzJXCFybzJS9N1wwCZ844UeC4GQSXmUEwYAbZEHJeuMBLrpzTBhVmVsjcaVUKMhtUyCDjZaZtFlyOHp1KZqFUDsRum0ILPbBNIx0fFs7jZhBcZgbBgBmkbK6tDUpYyLzKMVjaKLfKF9rkQVqUhROIeemVlJrnWkEITkqJGUiDO6bVVg6pWmtxeJvHzSC4zAyCATPI2MJa7V1hHSg0PM8kyCC58JkSufUSkOcuK02JLkcltXdaYcBCC5VDKbbbVJw72bf2nAEcPjiPm0FwmRkEA2aQKAoLkMtCS+Ho0MyMFVmeq6wwDrhDKYALb3JbFNZTHCUrlC/KAnNuSreLDgmrxcDXNJoLMbRNPG4G4WVmEA6YQTLjvvDKKY5C0ukitPFGidLnqlDKW2O9w1BwoZwurc5kIaSCwpRZiZZ3XBRjnTzDPsDjZhBeZgbhgBlUeFdiWeRLine truncated
|
||||
export default "H4sIAAAAAAACCuy9a5PbyJUo+FcY3N24MxFkKs/J10lF3A/9GI+96x57W147NhwdN0AgWcURH7UgS2p1h/77xskESJAASbBISaWy/EHuQhLIzJPn/crfh+v8Piyyv4dyPVsth69hNFyvHss8DF//PsyzxUM2u1v+lC1n07DeDF8Pi8flXVgtxw+zhzCfLcOr+kH9W/Hf69VyOBqG5btZuVouwnLz1yx/m92F9fD1P4frMn91ly3Cq7uwDGW2CcWr6exd+Os8+xDKH9O3/rR4WJWbdf2ljlcW2TJbb1bl4rv1Omy/H18Y/Nsm8PtZ+WEwzebzSZa//ffhL7ygfPW43ITyh2yTzVd3w9cnv/zz43IzW4Tqx/Vast9Cudrc/7Aqww+rxWLGINGFL6DQE+NQBQKfm1x5ObV5BuAckiom0wkFtf/6mwjlP6/yt90L6fzpFiQPYT7f7uP3YbbOwzId4FCIV3/Oitnb9U9//b//o5htVuWrfD4Ly804fyzLsNy8ysqQjQ2+esg2+f34x1c/fv+H2Tysf4i/evWGvy2KSc6H+LDK79M3/xiyeSh/ypbjN5tVmd2FVzUKjN+vyrc1HqxfxXfGacqjn/74cTTMV+8Cf2j4+p+/13j1p4KhcZ8Vq/fTbHk3fhvCw3A0XGQPPKJU4xjXw9eO/9zMNjPGLcWou8k2s/zNQ/Z+uR6+BulHw3KVLWbLO8aR9fA16h1+16eRr8ow/Dg6WAPjwPvZshivN6v8bVaExjL0/jJMcxnQWoZXh6uQPRdRhKxYzJZh3ZjbngABYgsErg0C1XP299lsPlvejfPsXSiXjTXog2OgvTXIwzWgwcM1uJ5LKLPfVuU0LMdvy+xx3liBO4UI1AMKgD2XMJln+dtptrlfLcZFCA9NOND+KuweHFwLDhJaq+iLCY/zIltky93cTl4EASXxyRC4W64WoQx3zen9wfT6NCUq19q6gr6U+Lh8G5Zj5urzBhFG4j6+BNWCvjaHS9CXYmGxet+kA0B/CgFMCw29bh1CX2Jc51k5D5vxYsXyKZQfGssgf4IfqRYeKsa6g2XYnsv47XE+zcoye7ubHuWp6RF6sAPQlxBjucrfjtcPs7KBDXh4FLC3CtciCGNtizHixasowsPmvoERqA7oAlRzGbZDROn2YZjeOHG/mq8W2TJvQuIQG+gMZ2BG9dQFbEpmjPNFUzgenoRvzm9a0kGrFmvgL/Saf5GV2WOxWjZF88ndt7iCwRZXQNmbLdyF6awM4/w+Wy8aa6BTnKmtH0D7BPrSQ8HTL/ZFY4sScI8SWlzJmRYlmL4HsL7PNptQhmJ8n83nDUIw+oAQ9uhAtfUUT09mjZP5alWMp4/lMmsSgtF4YgnQYoug/JOXcB/m84gKJVtB5WYPEgfa0t5xQAsSZJ4sqNebkC3eZY/zTXN6cxkUOthBX23tcVmEcrK6a85uT5BCh7pq4cnq6nqevQvjh7DcA747sXvsIMWWbFJ9oZ+VebYps98asxs8tXt//uj70uFktcmWszxrzq1OSWXfVpHhydi/CPl9tszK5uz6Iqwj/WSVIBmL43k2+VDOlpv75iLMZchHT2cA67C5D2/fttigsaeI33aoA0+2WNlzMd6sFpO9+U/hf4Tx/vzgn6wMZI/5fVhms3FefnjY44CGTgFBn7eXlesNg7sZa8fr8SaU5b44oJOMsG0z6hYjtJfoh+PFqszWTd3Q+hML6BBHT9YKwjrPHsJ4WrLJ+lgu71fzpvvC2FOyuc0aFLUPpO9SHjd3WVmEAz+OcafYQwdlAj5dKFUreJgtl1netCCNO8Uf+jCpvlDY3IfxMvz62CQKZ0/Zji3OgOrpLJKnX+WP8/356dTm+xxBX8GYP86jH2k1HXcZDMafZNNtdxI82Z8WuXNax2q5twR/ESY492QtpSizt5v7rFwtD2jCSnmRtPL+yUZ89tt/r+bjZSgfJ8354RR/bFlOTj4VH7anMJ/d3W+Ws+VdcxV4ETJ0oWVfx9Ld47LY82RYecqx256c3JP9ee9mK/bmMGduzk8njqBluPmnkkF2v3yb3YfNeDUvxm9ny7ti1TBfLZz057R1BqWerDNMV+VdiAS5emzqTVbhKZWhhYy6pbxSX95YzrI5r4CZZH6fLR5mTX+CNfIEMFpHYp56JA+zTQQDM4bm7KcQQrWpwdKTjcctVZZhOg/5Zh8M9qQK117Ik1n0ZrZ8G8pNyO/H6/vVe/a47pZBeMCjoLmMNmbaU6t4KFd5KB7LbD6u44Rt/CxXv4XluAxZft90ftNnXsaqvFttNmE55mhQw/Pr6FMD5JfRsAjT2XLGCLGOobp2xHjWHbXbzDZz/vCb7cjg/2rF8+bTuzrsW/D3iUO1vz5kdTgzn2fr9Szfjyj/tCoiIhWb8f2HSTljJlrMptNZ/jjffPh7Vs6y5SatNq5tuSoX2XzvR82na4bPT9n6bdzNPLwL85+zZYxMAo2U/KUCA7tWftx+4U97X87eZbN5NpnNZ/Hb1Z9R33wowzos+SR4R78P56u8+u8O0Gzuw4L39peHsOTI6rwYhF8fQjGrKHL9uFhkJU/xBxafg819uXq8ux8cfGk0KMI0ZJvBbLMebDFkUK7YPBoNsmUx4FMabO7DgD1WHwbZfPYuiOFoOF9lxWx591NYr2N0dvjXMjxk5Wx5dziJEPz7x2V+n5WbUHxXhuy/srj6/6d+NmjvkBHrrlw9Lhl+/5skWUg+3mkKZeer+aqMAxkgyOFouAxZmQKfU/4PtPLjaJgtJhxS/jPDYO+1jCbas14zW244Js6nIQXix9HwPixm64f7UIbta+u3H36o3yTnc2IXSlrc9rn0ciL94Ret/jiK/tfIKrN5eyUF5s4X++/BaLiaTtdhw6iFI5SjsfvlIxPEJrAkWscvPG5Wj5u/RtdqpLnh//oe/nfOYUgjDGN+/MtoOJmHZbH/S4y/jAONH2YFY9C7sP9bHX9bj21/Hhf0wEt5Pys29+yklwy+tEXFmuoDv7S8SwpBvlpuVo9l/OhPqAcozeAHsgMAGoDGAWo3QEkDcDB4o5QeOBhoaQeAdjga/qRogMoPfgDpB0BqAA4HaGmAWsbX3iitB94OlHcD54a/cPJAxokfEVrvZuvHbL6j9erv0XA6m4cKHdMzcTdnxHgs58PXw1fpCyntYpv18Uqp8Y6Rvdp7L78P+dv142L4eqhyHbDwwVPg5A7IOMsjKAjKAHqy0oSM9KQgpRDB5zKTTmUZaJr4aeYc0/Hst8CakienrR5FRWR5Nw8/MKUOX2uPEiTznXw1n88SM6y3uH00llLu73Rv6PINd73e2Lf009zmUgbMwE+nmnIoJrqYkA+T4CUYJSeFJ3A4Vd5NrA7gC6cyaeS0cFY29g3a29aurTHKsvRrbROObxOu2ya0t2ktkAOTZSorKLM48Y6M9ACZs2C9MVY6RVIVqAiNmgLISY45wlRikXvT3KZlb0vvbeLxbeJ128T2Np0Bg3pqaALOZgEmEzMp0BaZM65QuZMFOqMmhSNHuTdGZnqSmcxM86CkySaNbaJEumCb6vg21XXbVO1tThRkVk9Ro8llkRszdQCoEVE56ae+KEwWMm/kVFqTaeNCRvkUpJv4acizsHeadMk29fFt6uu2qdvbhAnoaeb1BCl3ekpSBt6KRE2apibTjrSSTmbTSV5MvJ9M8hCmE5t7yieozD5t4gXbNMe3aa7bpulAWvBeTzJyJptOXI5IOMXcQjYxmpT0IE1AyjLjC5oUk4CTKRT5xNhpluUQfGOb2ttLTtMe36a9bpu2vU2cTmGKucNCSVdYC9KaScjcdCpzNIoXDwABZdDewkTDRCmcknSoJ9aDbmzTkL5km+74Nt1123TtbRYTkzkpMcszlfssA5qCk8HKoPzUoso1gQm59cFa1MFMNbqQT3xRKOX9hBrbtFrqC7ZJx7dJ122TOgRKkU0Kq5SWhUVLE2szdFmWBYdSGjOZBphgEYLNvAEMGnNJdopTNSWrPTW3qaS5hDb98W3667bp29skI0kGnILLc0usA2WB8mmhKAtqkod84oxRAbR3xoapDEZq8pR5p5XLpk2BYgxeIDfhuBYE12lB0KEFeYkTRUVArSaTqQrZNBRBeyomU3TTibQKCg1eFlPlpgi5lLLQMGXVAGHidttU3pBs06azyvvObR7XguA6LQi6tCCkXNHEkM5tXlCGAQk9eG8okNOQyaktzGSaBQpWaydVbjn7uZAhBN0UKJ7gEi0IjmtBcJ0WBB1akMmtd56NZT+xNndOEoSJC84GnE40WAw6gNUyoJxKJVFhwFxPMum1dQ2BgqBUF6e1jnTnNo9rQXCdFgQdWpCHPLg8965wBq2ZoCzAW2WsNQCFl+TJoIdM5Xk2LShoZ6yc5jK3KsiparAgrUBDW3dnztV9nMfVILhODYIONciyPjuZaFYQlC2mE1TW2rzIMlJTkkCWILOeFPrC5BkG67Wy2dTiVJJ32e440akO4rROkenc5nE1CK5Tg6BDDZITRThRbI9YpCxMZK4zm0uVGQ2UhRyDchonoShwQhhkPvFTlRVukk3l1DXVIPDuAokCx9UguE4Ngg41iELIZeGDnErjvXVocEJK596aqWK1wYQJTOR0YmkSfI4ZepPUQm08qN02lVW2Y5tOe9lNnMfVILhODYIONchQbomCUQSTzOQYiDcqyWSFdXnQhLrwCjGfZkZrK3NrIASvtcYJaIc7pLWku1gtEXZv87gaBNepQdChBjkqiGzmC/Jg0Ml8okEHLVU2MSqnTAPK3E+mboo+R6Nt5q3BgIVVJoep2m7TSOl1W9vzDrBbcB5Xg+A6NQg61CBVFASQ68Jq5VloThypSZ6bSeE8SI9agVSZy6koKGM/yqQwWTEtMJdu6nfeIUVWdZyms1Kprm3icTUIr1ODsEMN0hOZFZnxRqLSLF2UdZkzaprlpjAmI0eZx1BIZbydkp3oQmkDhZtOpkiyYaI
Line truncated
|
||||
@@ -1 +1 @@
|
||||
{"schemaVersion":1,"source":{"campaignManifest":"dungeon-pipeline/dungeon-campaign.json","environmentPackages":["src/game/generated/fivePlayerDungeonImports.json","src/game/generated/manastormAssetPackages.json (temporary fallback)"],"encounterCatalog":"src/game/generated/manastormRuntimeCatalog.json","azerothCoreCommit":"4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3","azerothCoreSourceLock":"src/game/generated/azerothCoreSourceLock.json","spellCatalog":{"ascension":"../LadiksMPQEditor/client-current/area-52/patch-D/DBFilesClient/Spell.dbc","epoch":"../HealerMan-Storage/pipeline-work/dungeons/epoch-client/DBFilesClient/Spell.dbc"}},"coverage":[{"dungeonId":"shadowfang-keep","mapId":33,"encounters":7,"entities":31,"staticSpawns":109,"roamingPacks":24,"source":"azerothcore"},{"dungeonId":"stormwind-stockade","mapId":34,"encounters":5,"entities":11,"staticSpawns":93,"roamingPacks":0,"source":"azerothcore"},{"dungeonId":"deadmines","mapId":36,"encounters":7,"entities":22,"staticSpawns":179,"roamingPacks":23,"source":"azerothcore"},{"dungeonId":"wailing-caverns","mapId":43,"encounters":8,"entities":20,"staticSpawns":252,"roamingPacks":7,"source":"azerothcore"},{"dungeonId":"razorfen-kraul","mapId":47,"encounters":7,"entities":38,"staticSpawns":179,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"blackfathom-deeps","mapId":48,"encounters":6,"entities":27,"staticSpawns":201,"roamingPacks":10,"source":"azerothcore"},{"dungeonId":"uldaman","mapId":70,"encounters":7,"entities":38,"staticSpawns":302,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"gnomeregan","mapId":90,"encounters":4,"entities":31,"staticSpawns":371,"roamingPacks":31,"source":"azerothcore"},{"dungeonId":"sunken-temple","mapId":109,"encounters":4,"entities":33,"staticSpawns":245,"roamingPacks":4,"source":"azerothcore"},{"dungeonId":"razorfen-downs","mapId":129,"encounters":6,"entities":25,"staticSpawns":194,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"scarlet-monastery","mapId":189,"encounters":5,"entities":37,"staticSpawns":310,"roamingPacks":16,"source":"azerothcore"},{"dungeonId":"zulfarrak","mapId":209,"encounters":5,"entities":21,"staticSpawns":252,"roamingPacks":14,"source":"azerothcore"},{"dungeonId":"blackrock-spire","mapId":229,"encounters":15,"entities":78,"staticSpawns":566,"roamingPacks":22,"source":"azerothcore"},{"dungeonId":"blackrock-depths","mapId":230,"encounters":13,"entities":61,"staticSpawns":1040,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"scholomance","mapId":289,"encounters":8,"entities":38,"staticSpawns":327,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"stratholme","mapId":329,"encounters":9,"entities":50,"staticSpawns":431,"roamingPacks":29,"source":"azerothcore"},{"dungeonId":"maraudon","mapId":349,"encounters":8,"entities":33,"staticSpawns":525,"roamingPacks":20,"source":"azerothcore"},{"dungeonId":"ragefire-chasm","mapId":389,"encounters":4,"entities":11,"staticSpawns":127,"roamingPacks":4,"source":"azerothcore"},{"dungeonId":"dire-maul","mapId":429,"encounters":12,"entities":75,"staticSpawns":756,"roamingPacks":59,"source":"azerothcore"},{"dungeonId":"shattered-halls","mapId":540,"encounters":3,"entities":32,"staticSpawns":198,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"blood-furnace","mapId":542,"encounters":3,"entities":17,"staticSpawns":139,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"hellfire-ramparts","mapId":543,"encounters":2,"entities":12,"staticSpawns":85,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"steamvault","mapId":545,"encounters":3,"entities":17,"staticSpawns":127,"roamingPacks":17,"source":"azerothcore"},{"dungeonId":"underbog","mapId":546,"encounters":4,"entities":22,"staticSpawns":161,"roamingPacks":23,"source":"azerothcore"},{"dungeonId":"slave-pens","mapId":547,"encounters":3,"entities":21,"staticSpawns":122,"roamingPacks":32,"source":"azerothcore"},{"dungeonId":"arcatraz","mapId":552,"encounters":4,"entities":29,"staticSpawns":85,"roamingPacks":9,"source":"azerothcore"},{"dungeonId":"botanica","mapId":553,"encounters":5,"entities":29,"staticSpawns":171,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"mechanar","mapId":554,"encounters":3,"entities":17,"staticSpawns":84,"roamingPacks":14,"source":"azerothcore"},{"dungeonId":"shadow-labyrinth","mapId":555,"encounters":4,"entities":22,"staticSpawns":189,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"sethekk-halls","mapId":556,"encounters":2,"entities":16,"staticSpawns":104,"roamingPacks":24,"source":"azerothcore"},{"dungeonId":"mana-tombs","mapId":557,"encounters":3,"entities":14,"staticSpawns":119,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"auchenai-crypts","mapId":558,"encounters":2,"entities":14,"staticSpawns":93,"roamingPacks":37,"source":"azerothcore"},{"dungeonId":"magisters-terrace","mapId":585,"encounters":3,"entities":18,"staticSpawns":147,"roamingPacks":6,"source":"azerothcore"},{"dungeonId":"black-morass","mapId":269,"encounters":3,"entitiesLine truncated
|
||||
{"schemaVersion":1,"source":{"campaignManifest":"dungeon-pipeline/dungeon-campaign.json","environmentPackages":["src/game/generated/fivePlayerDungeonImports.json","src/game/generated/manastormAssetPackages.json (temporary fallback)"],"encounterCatalog":"src/game/generated/manastormRuntimeCatalog.json","azerothCoreCommit":"4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3","azerothCoreSourceLock":"src/game/generated/azerothCoreSourceLock.json","spellCatalog":{"ascension":"../LadiksMPQEditor/client-current/area-52/patch-D/DBFilesClient/Spell.dbc","epoch":"../HealerMan-Storage/pipeline-work/dungeons/epoch-client/DBFilesClient/Spell.dbc"}},"coverage":[{"dungeonId":"shadowfang-keep","mapId":33,"encounters":7,"entities":31,"staticSpawns":109,"roamingPacks":24,"source":"azerothcore"},{"dungeonId":"stormwind-stockade","mapId":34,"encounters":5,"entities":11,"staticSpawns":93,"roamingPacks":0,"source":"azerothcore"},{"dungeonId":"deadmines","mapId":36,"encounters":7,"entities":22,"staticSpawns":179,"roamingPacks":23,"source":"azerothcore"},{"dungeonId":"wailing-caverns","mapId":43,"encounters":8,"entities":20,"staticSpawns":252,"roamingPacks":7,"source":"azerothcore"},{"dungeonId":"razorfen-kraul","mapId":47,"encounters":7,"entities":38,"staticSpawns":179,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"blackfathom-deeps","mapId":48,"encounters":6,"entities":27,"staticSpawns":201,"roamingPacks":10,"source":"azerothcore"},{"dungeonId":"uldaman","mapId":70,"encounters":7,"entities":38,"staticSpawns":302,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"gnomeregan","mapId":90,"encounters":4,"entities":31,"staticSpawns":371,"roamingPacks":31,"source":"azerothcore"},{"dungeonId":"sunken-temple","mapId":109,"encounters":4,"entities":33,"staticSpawns":245,"roamingPacks":4,"source":"azerothcore"},{"dungeonId":"razorfen-downs","mapId":129,"encounters":6,"entities":25,"staticSpawns":194,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"scarlet-monastery","mapId":189,"encounters":5,"entities":37,"staticSpawns":310,"roamingPacks":16,"source":"azerothcore"},{"dungeonId":"zulfarrak","mapId":209,"encounters":5,"entities":21,"staticSpawns":252,"roamingPacks":14,"source":"azerothcore"},{"dungeonId":"blackrock-spire","mapId":229,"encounters":15,"entities":78,"staticSpawns":566,"roamingPacks":22,"source":"azerothcore"},{"dungeonId":"blackrock-depths","mapId":230,"encounters":13,"entities":61,"staticSpawns":1040,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"scholomance","mapId":289,"encounters":8,"entities":38,"staticSpawns":327,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"stratholme","mapId":329,"encounters":9,"entities":50,"staticSpawns":431,"roamingPacks":29,"source":"azerothcore"},{"dungeonId":"maraudon","mapId":349,"encounters":8,"entities":33,"staticSpawns":525,"roamingPacks":20,"source":"azerothcore"},{"dungeonId":"ragefire-chasm","mapId":389,"encounters":4,"entities":11,"staticSpawns":127,"roamingPacks":4,"source":"azerothcore"},{"dungeonId":"dire-maul","mapId":429,"encounters":12,"entities":75,"staticSpawns":756,"roamingPacks":59,"source":"azerothcore"},{"dungeonId":"shattered-halls","mapId":540,"encounters":3,"entities":32,"staticSpawns":198,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"blood-furnace","mapId":542,"encounters":3,"entities":17,"staticSpawns":139,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"hellfire-ramparts","mapId":543,"encounters":2,"entities":12,"staticSpawns":85,"roamingPacks":12,"source":"azerothcore"},{"dungeonId":"steamvault","mapId":545,"encounters":3,"entities":17,"staticSpawns":127,"roamingPacks":17,"source":"azerothcore"},{"dungeonId":"underbog","mapId":546,"encounters":4,"entities":22,"staticSpawns":161,"roamingPacks":23,"source":"azerothcore"},{"dungeonId":"slave-pens","mapId":547,"encounters":3,"entities":21,"staticSpawns":122,"roamingPacks":32,"source":"azerothcore"},{"dungeonId":"arcatraz","mapId":552,"encounters":4,"entities":29,"staticSpawns":85,"roamingPacks":9,"source":"azerothcore"},{"dungeonId":"botanica","mapId":553,"encounters":5,"entities":29,"staticSpawns":171,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"mechanar","mapId":554,"encounters":3,"entities":17,"staticSpawns":84,"roamingPacks":14,"source":"azerothcore"},{"dungeonId":"shadow-labyrinth","mapId":555,"encounters":4,"entities":22,"staticSpawns":189,"roamingPacks":13,"source":"azerothcore"},{"dungeonId":"sethekk-halls","mapId":556,"encounters":2,"entities":16,"staticSpawns":104,"roamingPacks":24,"source":"azerothcore"},{"dungeonId":"mana-tombs","mapId":557,"encounters":3,"entities":14,"staticSpawns":119,"roamingPacks":15,"source":"azerothcore"},{"dungeonId":"auchenai-crypts","mapId":558,"encounters":2,"entities":14,"staticSpawns":93,"roamingPacks":37,"source":"azerothcore"},{"dungeonId":"magisters-terrace","mapId":585,"encounters":3,"entities":18,"staticSpawns":147,"roamingPacks":6,"source":"azerothcore"},{"dungeonId":"black-morass","mapId":269,"encounters":3,"entitiesLine truncated
|
||||
@@ -16,7 +16,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -236,7 +236,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Corrosive Slam",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -296,7 +296,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -416,7 +416,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -476,7 +476,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -536,7 +536,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -576,7 +576,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -636,7 +636,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -676,7 +676,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -716,7 +716,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const DUNGEON_OF_DALANIS_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-dungeon-of-dalanis-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1567,6 +1567,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Generated by scripts/runewaker-pipeline/build-population.mjs. */
|
||||
/* This file is generated by scripts/runewaker-pipeline/build-forsaken-population.mjs. */
|
||||
import type { DungeonBossObjective } from "../dungeonTypes";
|
||||
import type { PopulationDefinitionMap, StaticMobSpawnDefinition } from "../mobPopulation";
|
||||
|
||||
@@ -16,7 +16,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Corrosive Slam",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -175,9 +175,12 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-fa-100070-source-gap-burst"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -186,6 +189,33 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100070-source-gap-burst",
|
||||
"name": "Arcane Burst",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 12,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0.82,
|
||||
"radius": 8,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 2000,
|
||||
"radius": 8
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -217,7 +247,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -257,7 +287,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -297,7 +327,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Corrosive Slam",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,9 +366,12 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-fa-100561-source-gap-sweep"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Corrosive Slam",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -347,6 +380,33 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100561-source-gap-sweep",
|
||||
"name": "Corrosive Sweep",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
7000,
|
||||
7000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 1.1,
|
||||
"telegraph": {
|
||||
"shape": "cone",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"length": 7,
|
||||
"angleDegrees": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -378,7 +438,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -417,9 +477,12 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-fa-100562-source-gap-sweep"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -428,6 +491,33 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100562-source-gap-sweep",
|
||||
"name": "Heavy Sweep",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
7000,
|
||||
7000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 1.1,
|
||||
"telegraph": {
|
||||
"shape": "cone",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"length": 7,
|
||||
"angleDegrees": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -459,7 +549,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -498,9 +588,12 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-fa-100563-source-gap-sweep"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -509,6 +602,33 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100563-source-gap-sweep",
|
||||
"name": "Heavy Sweep",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
7000,
|
||||
7000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 1.1,
|
||||
"telegraph": {
|
||||
"shape": "cone",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"length": 7,
|
||||
"angleDegrees": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -539,9 +659,18 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-fa-100074-spell-490471",
|
||||
"rw-fa-100074-spell-490489",
|
||||
"rw-fa-100074-control-490636",
|
||||
"rw-fa-100074-summon-100070-at-50",
|
||||
"rw-fa-100074-summon-100070-at-25",
|
||||
"rw-fa-100074-phase-50",
|
||||
"rw-fa-100074-phase-25"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -550,6 +679,215 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-spell-490471",
|
||||
"name": "Fireball",
|
||||
"spellId": 490471,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 22.5,
|
||||
"cooldownMs": 3000,
|
||||
"initialCooldownMs": [
|
||||
3000,
|
||||
3000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
3000,
|
||||
3000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-spell-490489",
|
||||
"name": "Dark Power Release",
|
||||
"spellId": 490489,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 5000,
|
||||
"range": 15,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-control-490636",
|
||||
"name": "Group Catch",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "root",
|
||||
"durationMs": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-summon-100070-at-50",
|
||||
"name": "Reinforcements at 50%",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "summon",
|
||||
"creatureEntry": 100070,
|
||||
"count": 2
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-summon-100070-at-25",
|
||||
"name": "Reinforcements at 25%",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "summon",
|
||||
"creatureEntry": 100070,
|
||||
"count": 2
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-phase-50",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-50",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-fa-100074-phase-25",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-25",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 25
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -581,7 +919,7 @@ export const FORSAKEN_ABBEY_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-fa-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -2341,20 +2679,13 @@ export const FORSAKEN_ABBEY_BOSSES = [
|
||||
export const FORSAKEN_ABBEY_OBJECTIVE_ORDER = FORSAKEN_ABBEY_BOSSES;
|
||||
|
||||
export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
|
||||
"schemaVersion": 2,
|
||||
"schemaVersion": 1,
|
||||
"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": {
|
||||
@@ -2368,7 +2699,7 @@ export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
|
||||
"Resource/data/string_enus.db": "4fce021ba9ed110a213e08d3228b2de57cec66ddbbc8cdcf5753204874d9ab75"
|
||||
}
|
||||
},
|
||||
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(meters,meters,-meters)",
|
||||
"coordinateTransform": "threeLocal=(inverse(WDB placement)*server)*(0.1,0.1,-0.1)",
|
||||
"navmeshProjection": {
|
||||
"maximumAllowedDistance": 2,
|
||||
"maximumObservedDistance": 2.5965,
|
||||
@@ -2376,8 +2707,5 @@ export const FORSAKEN_ABBEY_POPULATION_PROVENANCE = {
|
||||
"deferredRowsBeyondCombatLimit": 1
|
||||
},
|
||||
"runeWakerIdsAreNamespaced": true,
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry.",
|
||||
"sourceSpellCatalog": "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
"sourceSpellCatalogSha256": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
"serverEntryPolicy": "RuneWaker numeric ids are never passed to AzerothCore serverEntry."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -416,7 +416,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -456,7 +456,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -536,7 +536,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -576,7 +576,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const GRAFU_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-grafu-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1306,6 +1306,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,7 +16,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -156,7 +156,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -456,7 +456,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -536,7 +536,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -596,7 +596,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -816,7 +816,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -856,7 +856,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -936,7 +936,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -976,7 +976,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1016,7 +1016,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1055,9 +1055,15 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-hoto-102021-aura-492669",
|
||||
"rw-hoto-102021-spell-493215",
|
||||
"rw-hoto-102021-spell-493307",
|
||||
"rw-hoto-102021-aura-493308"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1068,13 +1074,67 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-spell-493307",
|
||||
"id": "rw-hoto-102021-aura-492669",
|
||||
"name": "Blue Explosion",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "blue-explosion",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102021-spell-493215",
|
||||
"name": "Water Gulp",
|
||||
"spellId": 493215,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102021-spell-493307",
|
||||
"name": "Water Arrow",
|
||||
"spellId": 493307,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -1086,6 +1146,31 @@ export const HOTO_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102021-aura-493308",
|
||||
"name": "Water Arrow Volley",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 25000,
|
||||
"initialCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "water-arrow-volley",
|
||||
"durationMs": 12000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1116,9 +1201,13 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-hoto-102046-spell-493237",
|
||||
"rw-hoto-102046-spell-493239"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1129,13 +1218,42 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-spell-493239",
|
||||
"id": "rw-hoto-102046-spell-493237",
|
||||
"name": "Splinter Magic",
|
||||
"spellId": 493237,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 2000,
|
||||
"range": 15,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102046-spell-493239",
|
||||
"name": "Giant Claw Attack",
|
||||
"spellId": 493239,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -1147,26 +1265,6 @@ export const HOTO_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-spell-493237",
|
||||
"name": "Splinter Magic",
|
||||
"spellId": 493237,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 5,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1197,9 +1295,22 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-hoto-102040-aura-493207",
|
||||
"rw-hoto-102040-aura-493208",
|
||||
"rw-hoto-102040-debuff-493209",
|
||||
"rw-hoto-102040-spell-493210",
|
||||
"rw-hoto-102040-aura-493211",
|
||||
"rw-hoto-102040-aura-493212",
|
||||
"rw-hoto-102040-debuff-493305",
|
||||
"rw-hoto-102040-aura-493525",
|
||||
"rw-hoto-102040-aura-493578",
|
||||
"rw-hoto-102040-spell-493580",
|
||||
"rw-hoto-102040-aura-493595"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1208,6 +1319,280 @@ export const HOTO_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493207",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "manastorms-duo-lv5",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493208",
|
||||
"name": "Snake Lady's Seduction",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 18000,
|
||||
"initialCooldownMs": [
|
||||
18000,
|
||||
18000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
18000,
|
||||
18000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "petrify",
|
||||
"durationMs": 9000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-debuff-493209",
|
||||
"name": "Rock Binding Magic",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 18000,
|
||||
"initialCooldownMs": [
|
||||
18000,
|
||||
18000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
18000,
|
||||
18000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "rock-bind",
|
||||
"durationMs": 9000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-spell-493210",
|
||||
"name": "Rock Shattering Wave",
|
||||
"spellId": 493210,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 13000,
|
||||
"initialCooldownMs": [
|
||||
13000,
|
||||
13000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
13000,
|
||||
13000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493211",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "fortification",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493212",
|
||||
"name": "Summon Sound Shell",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 40000,
|
||||
"initialCooldownMs": [
|
||||
40000,
|
||||
40000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
40000,
|
||||
40000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "summon-sound-shell",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-debuff-493305",
|
||||
"name": "Magic Fortress",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 30000,
|
||||
"initialCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "magic-fortress",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493525",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 10000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "manastorms-duo-lv5",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493578",
|
||||
"name": "Fatal Snake Woman Scent",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 25000,
|
||||
"initialCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "fatal-snake-woman-scent",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-spell-493580",
|
||||
"name": "Snake Staff Attack",
|
||||
"spellId": 493580,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102040-aura-493595",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 35000,
|
||||
"initialCooldownMs": [
|
||||
35000,
|
||||
35000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
35000,
|
||||
35000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "manastorms-duo-lv5",
|
||||
"durationMs": 12000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1239,7 +1624,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1279,7 +1664,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1358,9 +1743,14 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-hoto-102041-spell-493231",
|
||||
"rw-hoto-102041-spell-493232",
|
||||
"rw-hoto-102041-aura-493233"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1371,13 +1761,14 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-spell-493231",
|
||||
"id": "rw-hoto-102041-spell-493231",
|
||||
"name": "Big Ripper",
|
||||
"spellId": 493231,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -1389,6 +1780,52 @@ export const HOTO_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102041-spell-493232",
|
||||
"name": "Scaly Surprise",
|
||||
"spellId": 493232,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102041-aura-493233",
|
||||
"name": "Disperse Poison",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "disperse-poison",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1419,9 +1856,16 @@ export const HOTO_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-hoto-102063-aura-493187",
|
||||
"rw-hoto-102063-aura-493188",
|
||||
"rw-hoto-102063-spell-493189",
|
||||
"rw-hoto-102063-spell-493221",
|
||||
"rw-hoto-102063-aura-493492"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1430,6 +1874,130 @@ export const HOTO_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102063-aura-493187",
|
||||
"name": "Geba's Rage",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1500,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "geba-s-rage",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102063-aura-493188",
|
||||
"name": "Sonic Roar",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "sonic-roar",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102063-spell-493189",
|
||||
"name": "Geba's Fury",
|
||||
"spellId": 493189,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102063-spell-493221",
|
||||
"name": "Whirl Counterattack",
|
||||
"spellId": 493221,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-hoto-102063-aura-493492",
|
||||
"name": "Geba's Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "geba-s-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1461,7 +2029,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1541,7 +2109,7 @@ export const HOTO_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-hoto-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -5078,6 +5646,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -316,7 +316,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -356,7 +356,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -496,7 +496,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -596,7 +596,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -676,7 +676,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -836,7 +836,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -876,7 +876,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -916,7 +916,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -956,7 +956,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -996,7 +996,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1036,7 +1036,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1115,9 +1115,12 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ice-dwarf-kingdom-101881-debuff-492657"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1126,6 +1129,31 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101881-debuff-492657",
|
||||
"name": "Conductivity",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6000,
|
||||
"initialCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "conductivity",
|
||||
"durationMs": 4000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1156,9 +1184,17 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ice-dwarf-kingdom-101883-debuff-492533",
|
||||
"rw-ice-dwarf-kingdom-101883-spell-492587",
|
||||
"rw-ice-dwarf-kingdom-101883-spell-492601",
|
||||
"rw-ice-dwarf-kingdom-101883-debuff-492602",
|
||||
"rw-ice-dwarf-kingdom-101883-debuff-493085",
|
||||
"rw-ice-dwarf-kingdom-101883-debuff-493241"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1167,6 +1203,155 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-debuff-492533",
|
||||
"name": "Runic Decline",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 20,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 3,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 3
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "runic-decline",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-spell-492587",
|
||||
"name": "Energy Burn",
|
||||
"spellId": 492587,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 4.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-spell-492601",
|
||||
"name": "Triple Slash",
|
||||
"spellId": 492601,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-debuff-492602",
|
||||
"name": "Slipping Ice Block",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 25,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "slipping-ice-block",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-debuff-493085",
|
||||
"name": "RuneWaker Spell 493085",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 20,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "repair",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101883-debuff-493241",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "food-preservative",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1197,9 +1382,13 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ice-dwarf-kingdom-101884-spell-492759",
|
||||
"rw-ice-dwarf-kingdom-101884-spell-492760"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1210,13 +1399,14 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-spell-492759",
|
||||
"id": "rw-ice-dwarf-kingdom-101884-spell-492759",
|
||||
"name": "Raging Flames",
|
||||
"spellId": 492759,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
@@ -1227,16 +1417,24 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 3,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-spell-492760",
|
||||
"id": "rw-ice-dwarf-kingdom-101884-spell-492760",
|
||||
"name": "Bash",
|
||||
"spellId": 492760,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
@@ -1278,9 +1476,14 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ice-dwarf-kingdom-101268-spell-492821",
|
||||
"rw-ice-dwarf-kingdom-101268-debuff-492822",
|
||||
"rw-ice-dwarf-kingdom-101268-spell-492841"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1291,34 +1494,15 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-spell-492841",
|
||||
"name": "Boulder Split",
|
||||
"spellId": 492841,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"range": 10,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-spell-492821",
|
||||
"id": "rw-ice-dwarf-kingdom-101268-spell-492821",
|
||||
"name": "Terrain Clear",
|
||||
"spellId": 492821,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"range": 6,
|
||||
"castTimeMs": 5000,
|
||||
"range": 14,
|
||||
"cooldownMs": 3000,
|
||||
"initialCooldownMs": [
|
||||
3000,
|
||||
@@ -1328,7 +1512,74 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
3000,
|
||||
3000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 14,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 14
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101268-debuff-492822",
|
||||
"name": "Dust",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6000,
|
||||
"initialCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "dust",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101268-spell-492841",
|
||||
"name": "Boulder Split",
|
||||
"spellId": 492841,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 14,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 14,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 14
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1359,9 +1610,15 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ice-dwarf-kingdom-101584-spell-492847",
|
||||
"rw-ice-dwarf-kingdom-101584-debuff-492848",
|
||||
"rw-ice-dwarf-kingdom-101584-aura-492850",
|
||||
"rw-ice-dwarf-kingdom-101584-debuff-492853"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1370,6 +1627,123 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101584-spell-492847",
|
||||
"name": "Ancient Fire",
|
||||
"spellId": 492847,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101584-debuff-492848",
|
||||
"name": "Fire Chaos",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 7,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "fire-chaos",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101584-aura-492850",
|
||||
"name": "Hellish Rage",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "hellish-rage",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-101584-debuff-492853",
|
||||
"name": "RuneWaker Spell 492853",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "manastorms-duo-lv5",
|
||||
"durationMs": 6000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1401,7 +1775,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1501,7 +1875,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1581,7 +1955,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1681,7 +2055,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1741,7 +2115,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1801,7 +2175,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1881,7 +2255,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1961,7 +2335,7 @@ export const ICE_DWARF_KINGDOM_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ice-dwarf-kingdom-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -8690,6 +9064,6 @@ export const ICE_DWARF_KINGDOM_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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const KAL_TUROK_HIVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kal-turok-hive-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -181,6 +181,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -156,7 +156,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -276,7 +276,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -356,7 +356,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -516,7 +516,7 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -595,9 +595,15 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kalins-shrine-101507-spell-492371",
|
||||
"rw-kalins-shrine-101507-spell-492372",
|
||||
"rw-kalins-shrine-101507-spell-492373",
|
||||
"rw-kalins-shrine-101507-aura-492374"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -608,13 +614,42 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-492372",
|
||||
"id": "rw-kalins-shrine-101507-spell-492371",
|
||||
"name": "Power Build-Up Strike",
|
||||
"spellId": 492371,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 4000,
|
||||
"range": 5,
|
||||
"cooldownMs": 8000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101507-spell-492372",
|
||||
"name": "Earth Shattering Attack",
|
||||
"spellId": 492372,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2500,
|
||||
"range": 25,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -628,13 +663,14 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-492373",
|
||||
"id": "rw-kalins-shrine-101507-spell-492373",
|
||||
"name": "Hit on the Head",
|
||||
"spellId": 492373,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -648,24 +684,29 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-492371",
|
||||
"name": "Power Build-Up Strike",
|
||||
"spellId": 492371,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 5,
|
||||
"cooldownMs": 8000,
|
||||
"id": "rw-kalins-shrine-101507-aura-492374",
|
||||
"name": "Steel Body Protection",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "steel-reinforcement",
|
||||
"durationMs": 12000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -696,9 +737,16 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kalins-shrine-101502-spell-492375",
|
||||
"rw-kalins-shrine-101502-debuff-492376",
|
||||
"rw-kalins-shrine-101502-debuff-492377",
|
||||
"rw-kalins-shrine-101502-aura-492536",
|
||||
"rw-kalins-shrine-101502-debuff-492778"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -709,13 +757,14 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-492375",
|
||||
"id": "rw-kalins-shrine-101502-spell-492375",
|
||||
"name": "Icy Water Cannon",
|
||||
"spellId": 492375,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 20,
|
||||
"cooldownMs": 5000,
|
||||
"initialCooldownMs": [
|
||||
@@ -727,6 +776,113 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
5000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101502-debuff-492376",
|
||||
"name": "Spray Mucus",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 20,
|
||||
"cooldownMs": 6000,
|
||||
"initialCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6000,
|
||||
6000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "sticky",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101502-debuff-492377",
|
||||
"name": "Strange Egg Parasites",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "strange-egg-parasites",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101502-aura-492536",
|
||||
"name": "Strange Egg Parasites",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "strange-egg-parasites",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101502-debuff-492778",
|
||||
"name": "Strange Egg Parasites",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "strange-egg-parasites",
|
||||
"durationMs": 5000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -757,9 +913,16 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kalins-shrine-101503-aura-490656",
|
||||
"rw-kalins-shrine-101503-aura-492569",
|
||||
"rw-kalins-shrine-101503-aura-492570",
|
||||
"rw-kalins-shrine-101503-aura-492571",
|
||||
"rw-kalins-shrine-101503-control-492682"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -768,6 +931,138 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101503-aura-490656",
|
||||
"name": "Wave Armor",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "wave-armor",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101503-aura-492569",
|
||||
"name": "Kiss of Blood Clot",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "kiss-of-blood-clot",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101503-aura-492570",
|
||||
"name": "Lament of Dejection",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "lament-of-dejection",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101503-aura-492571",
|
||||
"name": "Confusion Curse",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "confusion-curse",
|
||||
"durationMs": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101503-control-492682",
|
||||
"name": "Witch's Song",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 4.5,
|
||||
"cooldownMs": 25000,
|
||||
"initialCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "fear",
|
||||
"durationMs": 3000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -798,9 +1093,15 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kalins-shrine-101500-spell-490477",
|
||||
"rw-kalins-shrine-101500-spell-492367",
|
||||
"rw-kalins-shrine-101500-debuff-492383",
|
||||
"rw-kalins-shrine-101500-aura-492384"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -811,33 +1112,14 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-492367",
|
||||
"name": "Thorny Road",
|
||||
"spellId": 492367,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"range": 25,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-spell-490477",
|
||||
"id": "rw-kalins-shrine-101500-spell-490477",
|
||||
"name": "Slash",
|
||||
"spellId": 490477,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -849,6 +1131,84 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101500-spell-492367",
|
||||
"name": "Thorny Road",
|
||||
"spellId": 492367,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 25,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101500-debuff-492383",
|
||||
"name": "Wine Spirit's Invitation",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 25,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "slumber",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101500-aura-492384",
|
||||
"name": "Spiritual Divine Praise",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "heroic-blessing",
|
||||
"durationMs": 10000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -879,9 +1239,16 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kalins-shrine-101501-spell-492379",
|
||||
"rw-kalins-shrine-101501-debuff-492380",
|
||||
"rw-kalins-shrine-101501-spell-492381",
|
||||
"rw-kalins-shrine-101501-spell-492382",
|
||||
"rw-kalins-shrine-101501-aura-492573"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kalins-shrine-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -890,6 +1257,147 @@ export const KALINS_SHRINE_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101501-spell-492379",
|
||||
"name": "High Speed Pressing",
|
||||
"spellId": 492379,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 10,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101501-debuff-492380",
|
||||
"name": "Bite to Pieces",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 10,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 10
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "bleed",
|
||||
"durationMs": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101501-spell-492381",
|
||||
"name": "Water Arrow Volley",
|
||||
"spellId": 492381,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "frost",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 25,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101501-spell-492382",
|
||||
"name": "Lake Master's Fury",
|
||||
"spellId": 492382,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2500,
|
||||
"range": 15,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kalins-shrine-101501-aura-492573",
|
||||
"name": "Stone Skin",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 35000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "stone-skin",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2816,6 +3324,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -236,7 +236,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -276,7 +276,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -316,7 +316,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -355,9 +355,17 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kawaks-tomb-104228-spell-496027",
|
||||
"rw-kawaks-tomb-104228-spell-496028",
|
||||
"rw-kawaks-tomb-104228-spell-496030",
|
||||
"rw-kawaks-tomb-104228-spell-496031",
|
||||
"rw-kawaks-tomb-104228-debuff-496036",
|
||||
"rw-kawaks-tomb-104228-phase-50"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -366,6 +374,175 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-spell-496027",
|
||||
"name": "Shake Heaven and Earth",
|
||||
"spellId": 496027,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-spell-496028",
|
||||
"name": "Wing Poison",
|
||||
"spellId": 496028,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-spell-496030",
|
||||
"name": "Holy Stone Floor",
|
||||
"spellId": 496030,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-spell-496031",
|
||||
"name": "Double Horned Spike",
|
||||
"spellId": 496031,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-debuff-496036",
|
||||
"name": "Pesticide",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 5000,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "pesticide",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kawaks-tomb-104228-phase-50",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-50",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 50
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -397,7 +574,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -437,7 +614,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -477,7 +654,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -517,7 +694,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -557,7 +734,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -617,7 +794,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -677,7 +854,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -717,7 +894,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -797,7 +974,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -837,7 +1014,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -877,7 +1054,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -917,7 +1094,7 @@ export const KAWAKS_TOMB_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kawaks-tomb-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -2288,6 +2465,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -195,9 +195,17 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-kulech-bones-nest-107306-spell-496328",
|
||||
"rw-kulech-bones-nest-107306-spell-496404",
|
||||
"rw-kulech-bones-nest-107306-spell-496405",
|
||||
"rw-kulech-bones-nest-107306-aura-496406",
|
||||
"rw-kulech-bones-nest-107306-aura-496407",
|
||||
"rw-kulech-bones-nest-107306-spell-496409"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -206,6 +214,154 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-spell-496328",
|
||||
"name": "Stone Sickle Chop",
|
||||
"spellId": 496328,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-spell-496404",
|
||||
"name": "Soul Killer Potion",
|
||||
"spellId": 496404,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-spell-496405",
|
||||
"name": "Wind Sickle Shadow",
|
||||
"spellId": 496405,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-aura-496406",
|
||||
"name": "Awaken",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "awaken",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-aura-496407",
|
||||
"name": "Corrosive Mucus",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "acid-irritation",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-107306-spell-496409",
|
||||
"name": "Wind Sickle",
|
||||
"spellId": 496409,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1000,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -237,7 +393,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -317,7 +473,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -377,7 +533,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -457,7 +613,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -537,7 +693,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -577,7 +733,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -657,7 +813,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -717,7 +873,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -777,7 +933,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -837,7 +993,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -897,7 +1053,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -957,7 +1113,7 @@ export const KULECH_BONES_NEST_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-kulech-bones-nest-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -2755,6 +2911,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -156,7 +156,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -316,7 +316,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -416,7 +416,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -456,7 +456,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -496,7 +496,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -716,7 +716,7 @@ export const LAIR_DEMON_DRAGON_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-lair-demon-dragon-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1923,6 +1923,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -15,9 +15,18 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-mystic-altar-100611-spell-490476",
|
||||
"rw-mystic-altar-100611-debuff-490480",
|
||||
"rw-mystic-altar-100611-spell-490489",
|
||||
"rw-mystic-altar-100611-control-491087",
|
||||
"rw-mystic-altar-100611-aura-491095",
|
||||
"rw-mystic-altar-100611-aura-491096",
|
||||
"rw-mystic-altar-100611-spell-491104"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -28,13 +37,14 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490476",
|
||||
"id": "rw-mystic-altar-100611-spell-490476",
|
||||
"name": "Bloodthirst",
|
||||
"spellId": 490476,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -48,34 +58,40 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490489",
|
||||
"name": "Dark Power Release",
|
||||
"spellId": 490489,
|
||||
"delivery": "melee",
|
||||
"id": "rw-mystic-altar-100611-debuff-490480",
|
||||
"name": "Poisoned",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "fire",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"range": 4.5,
|
||||
"cooldownMs": 20000,
|
||||
"castTimeMs": 2000,
|
||||
"range": 10,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "poisoned",
|
||||
"durationMs": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-491104",
|
||||
"name": "Sound of the Underworld",
|
||||
"spellId": 491104,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "shadow",
|
||||
"id": "rw-mystic-altar-100611-spell-490489",
|
||||
"name": "Dark Power Release",
|
||||
"spellId": 490489,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"range": 4.5,
|
||||
"castTimeMs": 5000,
|
||||
"range": 15,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
@@ -85,7 +101,124 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100611-control-491087",
|
||||
"name": "Dreadful Howl",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 4.5,
|
||||
"cooldownMs": 25000,
|
||||
"initialCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "fear",
|
||||
"durationMs": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100611-aura-491095",
|
||||
"name": "Rampart",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "rampart",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100611-aura-491096",
|
||||
"name": "Mana Insulation",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "mana-insulation",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100611-spell-491104",
|
||||
"name": "Sound of the Underworld",
|
||||
"spellId": 491104,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 5000,
|
||||
"range": 15,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -117,7 +250,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -177,7 +310,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -237,7 +370,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -316,9 +449,19 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-mystic-altar-100154-spell-490489",
|
||||
"rw-mystic-altar-100154-spell-490633",
|
||||
"rw-mystic-altar-100154-control-490636",
|
||||
"rw-mystic-altar-100154-spell-490637",
|
||||
"rw-mystic-altar-100154-aura-490652",
|
||||
"rw-mystic-altar-100154-control-491086",
|
||||
"rw-mystic-altar-100154-debuff-491097",
|
||||
"rw-mystic-altar-100154-debuff-491101"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -329,54 +472,15 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490637",
|
||||
"name": "Silence",
|
||||
"spellId": 490637,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"range": 18,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490633",
|
||||
"name": "Thunderstorm",
|
||||
"spellId": 490633,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"range": 18,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490489",
|
||||
"id": "rw-mystic-altar-100154-spell-490489",
|
||||
"name": "Dark Power Release",
|
||||
"spellId": 490489,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"range": 4.5,
|
||||
"castTimeMs": 5000,
|
||||
"range": 15,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
@@ -386,7 +490,202 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-spell-490633",
|
||||
"name": "Thunderstorm",
|
||||
"spellId": 490633,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 18,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-control-490636",
|
||||
"name": "Group Catch",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "root",
|
||||
"durationMs": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-spell-490637",
|
||||
"name": "Silence",
|
||||
"spellId": 490637,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1000,
|
||||
"range": 18,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-aura-490652",
|
||||
"name": "Life Source",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 8000,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "life-source",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-control-491086",
|
||||
"name": "Dreadful Cry",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 12,
|
||||
"cooldownMs": 25000,
|
||||
"initialCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
25000,
|
||||
25000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "fear",
|
||||
"durationMs": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-debuff-491097",
|
||||
"name": "Life Sandglass",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 25,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "life-sandglass",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100154-debuff-491101",
|
||||
"name": "Life Sandglass",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "life-sandglass",
|
||||
"durationMs": 6000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -418,7 +717,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -478,7 +777,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -578,7 +877,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -678,7 +977,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -738,7 +1037,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -838,7 +1137,7 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -877,9 +1176,19 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-mystic-altar-100138-spell-490477",
|
||||
"rw-mystic-altar-100138-spell-490478",
|
||||
"rw-mystic-altar-100138-spell-490520",
|
||||
"rw-mystic-altar-100138-spell-490522",
|
||||
"rw-mystic-altar-100138-aura-490523",
|
||||
"rw-mystic-altar-100138-spell-490525",
|
||||
"rw-mystic-altar-100138-control-490636",
|
||||
"rw-mystic-altar-100138-aura-491095"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-mystic-altar-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -890,13 +1199,14 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490477",
|
||||
"id": "rw-mystic-altar-100138-spell-490477",
|
||||
"name": "Slash",
|
||||
"spellId": 490477,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -910,73 +1220,14 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490520",
|
||||
"name": "Whirlwind",
|
||||
"spellId": 490520,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 4.5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490525",
|
||||
"name": "Blasting Cyclone",
|
||||
"spellId": 490525,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 4.5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490522",
|
||||
"name": "Formation Breaker",
|
||||
"spellId": 490522,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 4.5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-spell-490478",
|
||||
"id": "rw-mystic-altar-100138-spell-490478",
|
||||
"name": "Slash",
|
||||
"spellId": 490478,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 6000,
|
||||
"initialCooldownMs": [
|
||||
@@ -988,6 +1239,172 @@ export const MYSTIC_ALTAR_ENTITIES = {
|
||||
6000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-spell-490520",
|
||||
"name": "Whirlwind",
|
||||
"spellId": 490520,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-spell-490522",
|
||||
"name": "Formation Breaker",
|
||||
"spellId": 490522,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 3000,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-aura-490523",
|
||||
"name": "Frenzy",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "frenzy",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-spell-490525",
|
||||
"name": "Blasting Cyclone",
|
||||
"spellId": 490525,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 8,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 8,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-control-490636",
|
||||
"name": "Group Catch",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 10,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
},
|
||||
"effect": {
|
||||
"kind": "control",
|
||||
"mechanic": "root",
|
||||
"durationMs": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-mystic-altar-100138-aura-491095",
|
||||
"name": "Rampart",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 45000,
|
||||
"initialCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
45000,
|
||||
45000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "rampart",
|
||||
"durationMs": 12000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3842,6 +4259,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,7 +16,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -276,7 +276,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -516,7 +516,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -596,7 +596,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -716,7 +716,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -816,7 +816,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -896,7 +896,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -936,7 +936,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -976,7 +976,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1036,7 +1036,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1076,7 +1076,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1116,7 +1116,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1196,7 +1196,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1236,7 +1236,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1296,7 +1296,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1336,7 +1336,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1396,7 +1396,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1476,7 +1476,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1516,7 +1516,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1556,7 +1556,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1616,7 +1616,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1676,7 +1676,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1716,7 +1716,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1756,7 +1756,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1816,7 +1816,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1856,7 +1856,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1916,7 +1916,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1956,7 +1956,7 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1995,9 +1995,15 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-raksha-temple-103946-spell-495545",
|
||||
"rw-raksha-temple-103946-spell-495591",
|
||||
"rw-raksha-temple-103946-spell-495996",
|
||||
"rw-raksha-temple-103946-spell-496112"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-raksha-temple-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -2006,6 +2012,111 @@ export const RAKSHA_TEMPLE_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-raksha-temple-103946-spell-495545",
|
||||
"name": "Soul Fire",
|
||||
"spellId": 495545,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-raksha-temple-103946-spell-495591",
|
||||
"name": "Void Fire",
|
||||
"spellId": 495591,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-raksha-temple-103946-spell-495996",
|
||||
"name": "Totem Burst",
|
||||
"spellId": 495996,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-raksha-temple-103946-spell-496112",
|
||||
"name": "RuneWaker Spell 496112",
|
||||
"spellId": 496112,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 4.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3434,6 +3545,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -102,8 +102,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "kalins-shrine-visual.glb",
|
||||
"checksum": "0cfdb047990a4870d18e67fcf96170c3cd575fbb7f0739784dcf15edd5c8bddf",
|
||||
"size": 14175516,
|
||||
"checksum": "769accb48e23c9f80e28697e3e8454ef4148243a69ce247ed06ba4eb35bb0da7",
|
||||
"size": 8747072,
|
||||
"triangleCount": 202382
|
||||
}
|
||||
],
|
||||
@@ -216,7 +216,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -291,8 +291,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "hoto-visual.glb",
|
||||
"checksum": "4c69138c8bf6a6b1055f337a7c39204b977a6169905ab735e1742a139c76ad64",
|
||||
"size": 16626184,
|
||||
"checksum": "0d12b9c6dc477704d2833c81f76113d340da7f07f5885a40bafd1321524e455e",
|
||||
"size": 6667208,
|
||||
"triangleCount": 189855
|
||||
}
|
||||
],
|
||||
@@ -405,7 +405,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -480,8 +480,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "mystic-altar-visual.glb",
|
||||
"checksum": "9f395e5670c79fe306c2a5440125885c4c58b36d677f2bde74667d3779d1eab5",
|
||||
"size": 6453276,
|
||||
"checksum": "765dd1b15bbe79fe1ffc2104e10caece19acaf86c919f0327419cb3609dbf600",
|
||||
"size": 5552872,
|
||||
"triangleCount": 174521
|
||||
}
|
||||
],
|
||||
@@ -588,7 +588,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -663,8 +663,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "kal-turok-hive-visual.glb",
|
||||
"checksum": "7a593b8b0b001d410213c09cf75f3b1edc3fb8543f412d2a5bb18e411506b0da",
|
||||
"size": 5593304,
|
||||
"checksum": "012867deefc4dc5a18c3e1a507309aa48f119cf4c2e88968fdd2787936ed7c24",
|
||||
"size": 1777616,
|
||||
"triangleCount": 24026
|
||||
}
|
||||
],
|
||||
@@ -763,7 +763,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -838,8 +838,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "paspers-shrine-visual.glb",
|
||||
"checksum": "564dc92bae483dd0be8fd0086215d4e2e6bd4dda45f2094a55f4045de8301f5f",
|
||||
"size": 6262596,
|
||||
"checksum": "82ea6cafd19c0a323c994795699223a07193807da29b300eb12dac41146f1034",
|
||||
"size": 5625952,
|
||||
"triangleCount": 169935
|
||||
}
|
||||
],
|
||||
@@ -945,7 +945,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1020,8 +1020,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "treasure-trove-visual.glb",
|
||||
"checksum": "eb8f26f1594d8e10a060444899e91744c8c5f48af720b873504031d860d9f446",
|
||||
"size": 10623004,
|
||||
"checksum": "f4c4dc12df65f8afc3c696308db17cb42b6a5894086dbe870dae2e21e1258448",
|
||||
"size": 7433280,
|
||||
"triangleCount": 182387
|
||||
}
|
||||
],
|
||||
@@ -1135,7 +1135,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1210,8 +1210,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "ystra-labyrinth-boss1-visual.glb",
|
||||
"checksum": "54febe2d5e0740da790f6bdbad73e4f383ed9b633da9c096a6687ae28081eeaa",
|
||||
"size": 8036460,
|
||||
"checksum": "c8ca72a5fdf487fa71a1aecd1e66ea821183f476540be9a89444b409be957c25",
|
||||
"size": 2709352,
|
||||
"triangleCount": 64284
|
||||
}
|
||||
],
|
||||
@@ -1324,7 +1324,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1399,8 +1399,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "barren-caves-visual.glb",
|
||||
"checksum": "71e264d5e757e2402c45e7c291b03af243c9defdd89d78ec736a3024d126d482",
|
||||
"size": 1852500,
|
||||
"checksum": "d0f733de4b26c0f432c5e160614765f320bf1753a829be269740b340e7415a12",
|
||||
"size": 1710152,
|
||||
"triangleCount": 47084
|
||||
}
|
||||
],
|
||||
@@ -1499,7 +1499,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1574,8 +1574,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "ystra-labyrinth-boss2-visual.glb",
|
||||
"checksum": "12c32e7c72cb1a6ed8693999d9d9a99bfeeee28ce32f2415852d8c9a9f59cf6c",
|
||||
"size": 7954300,
|
||||
"checksum": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab",
|
||||
"size": 2682688,
|
||||
"triangleCount": 21428
|
||||
}
|
||||
],
|
||||
@@ -1674,7 +1674,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1749,8 +1749,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "ystra-labyrinth-boss3-visual.glb",
|
||||
"checksum": "12c32e7c72cb1a6ed8693999d9d9a99bfeeee28ce32f2415852d8c9a9f59cf6c",
|
||||
"size": 7954300,
|
||||
"checksum": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab",
|
||||
"size": 2682688,
|
||||
"triangleCount": 21428
|
||||
}
|
||||
],
|
||||
@@ -1849,7 +1849,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -1924,8 +1924,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "ice-dwarf-kingdom-visual.glb",
|
||||
"checksum": "a0f4ce4572d6fe491c362a8494623b5b4f5a255c1a63c9a4b5509af633f05c72",
|
||||
"size": 11348084,
|
||||
"checksum": "a6b902e85bb6026f18281d77efa235ec880ffc2fc13ea6bfdc431d24af768e00",
|
||||
"size": 9880192,
|
||||
"triangleCount": 285517
|
||||
}
|
||||
],
|
||||
@@ -2045,7 +2045,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -2120,8 +2120,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "the-origin-visual.glb",
|
||||
"checksum": "d6e1b8aaa69d067068ea29ad38544466a3a5fab7e0a532ca15da60cbfceac51b",
|
||||
"size": 643248,
|
||||
"checksum": "a6db29db31f88a45bd082d27130725d70b2fa1db82030530750492a54b3f9bf8",
|
||||
"size": 251132,
|
||||
"triangleCount": 2664
|
||||
}
|
||||
],
|
||||
@@ -2221,7 +2221,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -2296,8 +2296,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "hall-of-survivors-visual.glb",
|
||||
"checksum": "c22b5ae6b4a836a8861d90c8db249ed813caab8dfa6eb1311bc0730fd62e1150",
|
||||
"size": 31415356,
|
||||
"checksum": "18b9d116da599159719ad76eeee45a4d89056ee8c61cc4f21cab9a1d9228fff7",
|
||||
"size": 15216768,
|
||||
"triangleCount": 380192
|
||||
}
|
||||
],
|
||||
@@ -2424,7 +2424,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -2499,8 +2499,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "cave-water-dragon-visual.glb",
|
||||
"checksum": "56b9565c53ae8c644fb7ca7d070c02f39656e3ff30c407f1e04f28874566b781",
|
||||
"size": 13081352,
|
||||
"checksum": "cc57c0285de77690c4a1cdabaecd8b192791cbd05aca38aa21ed96462688ca31",
|
||||
"size": 8893112,
|
||||
"triangleCount": 249082
|
||||
}
|
||||
],
|
||||
@@ -2613,7 +2613,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -2688,8 +2688,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "lair-demon-dragon-visual.glb",
|
||||
"checksum": "4e3e15f5c5c3d02fab6a5eefa37962e614012cb2d255c56c841dadfd983db101",
|
||||
"size": 11971136,
|
||||
"checksum": "d28481504802203e800985836f2b0907d67f6d6d0495c67500914e749d771ffe",
|
||||
"size": 4551300,
|
||||
"triangleCount": 64688
|
||||
}
|
||||
],
|
||||
@@ -2788,7 +2788,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -2863,8 +2863,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "zurhidon-stronghold-visual.glb",
|
||||
"checksum": "ac533caeaf367039a48527d33695d9f28dfaffa6c29eac7b304e93334e5bb8d3",
|
||||
"size": 38128108,
|
||||
"checksum": "a2d450946ca9687395cf65815b931c7817d9f41041afeb36ba1a351ec24104c4",
|
||||
"size": 17189492,
|
||||
"triangleCount": 364765
|
||||
}
|
||||
],
|
||||
@@ -2991,7 +2991,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -3167,7 +3167,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"The source WDB has no terrain ROS; a flat playable arena is derived from source descriptor bounds and must be visually reviewed.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -3242,8 +3242,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "demon-stronghold-visual.glb",
|
||||
"checksum": "27d9026c01418e26959c2c24fb3929fc50a7a29c63922ef7223a7cf82aceb46f",
|
||||
"size": 37609808,
|
||||
"checksum": "9368984b3c183a8db6e8b7e18b7818edcf41b8edd45ffeca1d1df28aadc592a2",
|
||||
"size": 15166984,
|
||||
"triangleCount": 261609
|
||||
}
|
||||
],
|
||||
@@ -3357,7 +3357,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -3432,8 +3432,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "zurhidon-stronghold-124-visual.glb",
|
||||
"checksum": "ac533caeaf367039a48527d33695d9f28dfaffa6c29eac7b304e93334e5bb8d3",
|
||||
"size": 38128108,
|
||||
"checksum": "a2d450946ca9687395cf65815b931c7817d9f41041afeb36ba1a351ec24104c4",
|
||||
"size": 17189492,
|
||||
"triangleCount": 364765
|
||||
}
|
||||
],
|
||||
@@ -3560,7 +3560,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -3635,8 +3635,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "demon-stronghold-125-visual.glb",
|
||||
"checksum": "e648750047d8bbcf77b3f1a7d1c79144e4dcf45bd209bc54f070777d1e55541c",
|
||||
"size": 37597956,
|
||||
"checksum": "ecbacfe274e7eecb104c5807eedf3cc51cd4b7a628967c6331fc93b86396b842",
|
||||
"size": 15134500,
|
||||
"triangleCount": 259377
|
||||
}
|
||||
],
|
||||
@@ -3749,7 +3749,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -3824,8 +3824,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "dungeon-of-dalanis-visual.glb",
|
||||
"checksum": "9dbf0d01dd3d6ba029a2647561076ccd049c29ea9391a45ade5c6259c7c11b9c",
|
||||
"size": 14905224,
|
||||
"checksum": "4c584ef34779e516f8e6ff3a4907eaab1c7c8d49d4c302de66e17b09222d8829",
|
||||
"size": 11936864,
|
||||
"triangleCount": 288949
|
||||
}
|
||||
],
|
||||
@@ -3945,7 +3945,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4020,8 +4020,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "warnorken-arena-visual.glb",
|
||||
"checksum": "52d94f8fabf0c86fb24c57d5be36a490b26703b0acf84f83139adcd7df9710df",
|
||||
"size": 10114952,
|
||||
"checksum": "c452a843c2e4123d4fe0d20da41558f5b5a1906548b3d091dc691ffc42ed19d9",
|
||||
"size": 3752136,
|
||||
"triangleCount": 60751
|
||||
}
|
||||
],
|
||||
@@ -4120,7 +4120,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4195,8 +4195,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "raksha-temple-visual.glb",
|
||||
"checksum": "91a2197f45cd7e63829c35a3748efc2311c7d5a40e6737a33f42d21163dc4b1d",
|
||||
"size": 3274336,
|
||||
"checksum": "3439e64f2a49d0545651bac35276867af83c3eb57b3c859f767de0c49c3dce66",
|
||||
"size": 937772,
|
||||
"triangleCount": 4302
|
||||
}
|
||||
],
|
||||
@@ -4296,7 +4296,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4371,8 +4371,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "kawaks-tomb-visual.glb",
|
||||
"checksum": "6a55e61218a56a0f21566555ad0b7bb88b29b52882317231e836cc76b359ba14",
|
||||
"size": 21954824,
|
||||
"checksum": "de54d223725e2c3dfb0c47a97c208a212ef9423860bd0ac390f1482b302d3a32",
|
||||
"size": 8963188,
|
||||
"triangleCount": 161613
|
||||
}
|
||||
],
|
||||
@@ -4478,7 +4478,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4553,8 +4553,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "grafu-castle-visual.glb",
|
||||
"checksum": "d5561b8b427edd6dca0a675dabaf40b251430ad5511ab66217254bde45a7eda0",
|
||||
"size": 21509296,
|
||||
"checksum": "ba4552484ef9cddc1c00607d0dfd4c8fb0bae5b3cd3f992d215a580dac1e96ed",
|
||||
"size": 11149716,
|
||||
"triangleCount": 207925
|
||||
}
|
||||
],
|
||||
@@ -4667,7 +4667,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4742,8 +4742,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "sardo-castle-visual.glb",
|
||||
"checksum": "e819c1afb60fd81f7e79f0e3fea0b3bf4e64f57eb41526761bf41ccce3876a51",
|
||||
"size": 17572084,
|
||||
"checksum": "16d759af9f8a3f98281e89362c7fb52dceff6543bbd837a7c6f89015920cdd94",
|
||||
"size": 7756568,
|
||||
"triangleCount": 147586
|
||||
}
|
||||
],
|
||||
@@ -4849,7 +4849,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -4924,8 +4924,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "tomb-seven-heroes-visual.glb",
|
||||
"checksum": "3659d1675240704a6f5e43cb59a2ffffc503c979adfe92afcc93ff4e18a0413c",
|
||||
"size": 26962236,
|
||||
"checksum": "d9f39da6b61c9f841c5d075e4985bc67e533ce422313fdf38d5f6c99dbb304c1",
|
||||
"size": 10910804,
|
||||
"triangleCount": 155422
|
||||
}
|
||||
],
|
||||
@@ -5031,7 +5031,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -5106,8 +5106,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "aeternal-circle-visual.glb",
|
||||
"checksum": "d182e54b99a9875600c89efea194d9e86aa508c800a4e2b4d6ac7420158906aa",
|
||||
"size": 12997820,
|
||||
"checksum": "42fdcff1b25b58688943a030f69fd14ea839247a7baabbc31b5f6f7faaf49785",
|
||||
"size": 4963912,
|
||||
"triangleCount": 61639
|
||||
}
|
||||
],
|
||||
@@ -5207,7 +5207,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition,
|
||||
@@ -5282,8 +5282,8 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
{
|
||||
"id": "visual-000",
|
||||
"fileName": "kulech-bones-nest-visual.glb",
|
||||
"checksum": "a29116b276c7057ef30a6d7faacc8c7ff4430cea658b9bd9eb1edb86228f770c",
|
||||
"size": 36555996,
|
||||
"checksum": "2d912dcf7c0c8e59e36362fd947df1d5554c5dd5060590e1abb112e4c39f606f",
|
||||
"size": 12146892,
|
||||
"triangleCount": 168192
|
||||
}
|
||||
],
|
||||
@@ -5390,7 +5390,7 @@ export const RUNEWAKER_DUNGEON_DEFINITIONS = [
|
||||
"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.",
|
||||
"1 runtime/dynamic actor containers use HealerMan procedural visuals because their ROS files expose no static triangles.",
|
||||
"Mechanics candidates are source-audited; complex doors, summons, phases, patrol scripts, and loot remain review-required."
|
||||
"Boss combat kits are compiled from RuneWaker spell/script evidence; complex doors, patrol scripts, and loot remain review-required."
|
||||
]
|
||||
}
|
||||
} as const satisfies DungeonDefinition
|
||||
|
||||
@@ -15,9 +15,15 @@ export const SAND_VORTEX_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-sand-vortex-100645-spell-494642",
|
||||
"rw-sand-vortex-100645-spell-494643",
|
||||
"rw-sand-vortex-100645-debuff-494644",
|
||||
"rw-sand-vortex-100645-aura-494649"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sand-vortex-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -26,6 +32,119 @@ export const SAND_VORTEX_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-sand-vortex-100645-spell-494642",
|
||||
"name": "Viscous Quicksand",
|
||||
"spellId": 494642,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 4.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 3,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-sand-vortex-100645-spell-494643",
|
||||
"name": "Sand Fountain",
|
||||
"spellId": 494643,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "nature",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 8,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-sand-vortex-100645-debuff-494644",
|
||||
"name": "Sandy Ties",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 12000,
|
||||
"initialCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
12000,
|
||||
12000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "sandy-ties",
|
||||
"durationMs": 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-sand-vortex-100645-aura-494649",
|
||||
"name": "Corrosive Sand",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "corrosive-sand",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -112,6 +231,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -56,7 +56,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -296,7 +296,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -456,7 +456,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -516,7 +516,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -695,9 +695,14 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-sardo-castle-105701-spell-497652",
|
||||
"rw-sardo-castle-105701-spell-497653",
|
||||
"rw-sardo-castle-105701-aura-497654"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -706,6 +711,87 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-sardo-castle-105701-spell-497652",
|
||||
"name": "Energy Release",
|
||||
"spellId": 497652,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-sardo-castle-105701-spell-497653",
|
||||
"name": "Falling Flame",
|
||||
"spellId": 497653,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 25,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-sardo-castle-105701-aura-497654",
|
||||
"name": "Dark Healing",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "dark-healing",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -737,7 +823,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -817,7 +903,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -857,7 +943,7 @@ export const SARDO_CASTLE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-sardo-castle-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1740,6 +1826,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -96,7 +96,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -216,7 +216,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -296,7 +296,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -376,7 +376,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -496,7 +496,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -576,7 +576,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -696,7 +696,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -756,7 +756,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -836,7 +836,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -916,7 +916,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -956,7 +956,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -996,7 +996,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1076,7 +1076,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1156,7 +1156,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1256,7 +1256,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1296,7 +1296,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1396,7 +1396,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1436,7 +1436,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1476,7 +1476,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1515,9 +1515,14 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-the-origin-102349-spell-493663",
|
||||
"rw-the-origin-102349-spell-493664",
|
||||
"rw-the-origin-102349-aura-493665"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1526,6 +1531,73 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102349-spell-493663",
|
||||
"name": "Spike Puncture",
|
||||
"spellId": 493663,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102349-spell-493664",
|
||||
"name": "Earth Rupture",
|
||||
"spellId": 493664,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102349-aura-493665",
|
||||
"name": "King Bug Shock",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "king-bug-shock",
|
||||
"durationMs": 7500
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1557,7 +1629,7 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1596,9 +1668,17 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-the-origin-102307-spell-493590",
|
||||
"rw-the-origin-102307-aura-493592",
|
||||
"rw-the-origin-102307-spell-493649",
|
||||
"rw-the-origin-102307-aura-493650",
|
||||
"rw-the-origin-102307-aura-493651",
|
||||
"rw-the-origin-102307-spell-493652"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-the-origin-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1607,6 +1687,151 @@ export const THE_ORIGIN_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-spell-493590",
|
||||
"name": "Power of the Ents",
|
||||
"spellId": 493590,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 10,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-aura-493592",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "manastorms-duo-lv5",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-spell-493649",
|
||||
"name": "Giant Tree Heavy Hammer",
|
||||
"spellId": 493649,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-aura-493650",
|
||||
"name": "Impaling Thorns",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "impaling-thorns",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-aura-493651",
|
||||
"name": "Entangling Tree Vines",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "armor-penetrating-tree-vine",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-the-origin-102307-spell-493652",
|
||||
"name": "Giant Wood Thicket",
|
||||
"spellId": 493652,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 15,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3759,6 +3984,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -116,7 +116,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -196,7 +196,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -276,7 +276,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -356,7 +356,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -436,7 +436,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -516,7 +516,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -556,7 +556,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -736,7 +736,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -776,7 +776,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -856,7 +856,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -896,7 +896,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1016,7 +1016,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1076,7 +1076,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1136,7 +1136,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1196,7 +1196,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1236,7 +1236,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1296,7 +1296,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1376,7 +1376,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1436,7 +1436,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1496,7 +1496,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1536,7 +1536,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1576,7 +1576,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1616,7 +1616,7 @@ export const TOMB_SEVEN_HEROES_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-tomb-seven-heroes-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -4330,6 +4330,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -75,9 +75,15 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-treasure-trove-101533-spell-490477",
|
||||
"rw-treasure-trove-101533-aura-492431",
|
||||
"rw-treasure-trove-101533-debuff-492433",
|
||||
"rw-treasure-trove-101533-spell-492434"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -88,33 +94,14 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-spell-492434",
|
||||
"name": "Boulder Strike",
|
||||
"spellId": 492434,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"range": 30,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-spell-490477",
|
||||
"id": "rw-treasure-trove-101533-spell-490477",
|
||||
"name": "Slash",
|
||||
"spellId": 490477,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -126,6 +113,77 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101533-aura-492431",
|
||||
"name": "Strengthen Body",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "strengthen-body",
|
||||
"durationMs": 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101533-debuff-492433",
|
||||
"name": "Armor Damaging Attack",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "armor-damage",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101533-spell-492434",
|
||||
"name": "Boulder Strike",
|
||||
"spellId": 492434,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -156,9 +214,15 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-treasure-trove-101535-debuff-492435",
|
||||
"rw-treasure-trove-101535-aura-492436",
|
||||
"rw-treasure-trove-101535-spell-492438",
|
||||
"rw-treasure-trove-101535-aura-492991"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -169,14 +233,65 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-spell-492438",
|
||||
"id": "rw-treasure-trove-101535-debuff-492435",
|
||||
"name": "Open Wound",
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "absorbed-heal",
|
||||
"durationMs": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101535-aura-492436",
|
||||
"name": "Queen's Summons",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 30000,
|
||||
"initialCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "queen-s-summons",
|
||||
"durationMs": 12000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101535-spell-492438",
|
||||
"name": "Snow's Dance",
|
||||
"spellId": 492438,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"range": 4.5,
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
@@ -186,7 +301,39 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101535-aura-492991",
|
||||
"name": "Rose Flower Demon Vine",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 30000,
|
||||
"initialCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
30000,
|
||||
30000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "rose-flower-demon-vine",
|
||||
"durationMs": 12000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -218,7 +365,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -318,7 +465,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -418,7 +565,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -518,7 +665,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -598,7 +745,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -698,7 +845,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -778,7 +925,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -878,7 +1025,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -938,7 +1085,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -998,7 +1145,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1078,7 +1225,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1138,7 +1285,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1217,9 +1364,15 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-treasure-trove-101534-spell-490477",
|
||||
"rw-treasure-trove-101534-spell-492428",
|
||||
"rw-treasure-trove-101534-debuff-492429",
|
||||
"rw-treasure-trove-101534-spell-492430"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1230,13 +1383,14 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-spell-490477",
|
||||
"id": "rw-treasure-trove-101534-spell-490477",
|
||||
"name": "Slash",
|
||||
"spellId": 490477,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
@@ -1248,6 +1402,87 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101534-spell-492428",
|
||||
"name": "Dizzy Chop",
|
||||
"spellId": 492428,
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 0,
|
||||
"range": 5,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
20000,
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101534-debuff-492429",
|
||||
"name": "Focused Gaze",
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
},
|
||||
"effect": {
|
||||
"kind": "target-aura",
|
||||
"aura": "weak-spot-exposure",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-treasure-trove-101534-spell-492430",
|
||||
"name": "Aimed Throw",
|
||||
"spellId": 492430,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1279,7 +1514,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1319,7 +1554,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1379,7 +1614,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1439,7 +1674,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1499,7 +1734,7 @@ export const TREASURE_TROVE_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-treasure-trove-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -7068,6 +7303,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const WARNORKEN_ARENA_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-warnorken-arena-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -15,9 +15,19 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ystra-labyrinth-boss1-102195-spell-490634",
|
||||
"rw-ystra-labyrinth-boss1-102195-aura-491008",
|
||||
"rw-ystra-labyrinth-boss1-102195-spell-492307",
|
||||
"rw-ystra-labyrinth-boss1-102195-spell-492308",
|
||||
"rw-ystra-labyrinth-boss1-102195-phase-80",
|
||||
"rw-ystra-labyrinth-boss1-102195-phase-60",
|
||||
"rw-ystra-labyrinth-boss1-102195-phase-40",
|
||||
"rw-ystra-labyrinth-boss1-102195-phase-20"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -28,13 +38,14 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-spell-490634",
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-spell-490634",
|
||||
"name": "Meteor Shower",
|
||||
"spellId": 490634,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 25,
|
||||
"cooldownMs": 20000,
|
||||
"initialCooldownMs": [
|
||||
@@ -46,6 +57,215 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
20000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-aura-491008",
|
||||
"name": "Transport Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "transport-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-spell-492307",
|
||||
"name": "Guard Bomb",
|
||||
"spellId": 492307,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-spell-492308",
|
||||
"name": "Guard Bomb",
|
||||
"spellId": 492308,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "fire",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 2000,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-phase-80",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-80",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-phase-60",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-60",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-phase-40",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-40",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 40
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-102195-phase-20",
|
||||
"name": "Phase Change",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"interruptible": false,
|
||||
"range": 0,
|
||||
"cooldownMs": 1000,
|
||||
"initialCooldownMs": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
1000,
|
||||
1000
|
||||
],
|
||||
"maximumCasts": 1,
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "phase-20",
|
||||
"durationMs": 5000
|
||||
},
|
||||
"condition": {
|
||||
"kind": "self-health-percent",
|
||||
"minimum": 0,
|
||||
"maximum": 20
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -76,9 +296,18 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ystra-labyrinth-boss1-101432-spell-490539",
|
||||
"rw-ystra-labyrinth-boss1-101432-aura-491008",
|
||||
"rw-ystra-labyrinth-boss1-101432-spell-492310",
|
||||
"rw-ystra-labyrinth-boss1-101432-spell-492312",
|
||||
"rw-ystra-labyrinth-boss1-101432-aura-492313",
|
||||
"rw-ystra-labyrinth-boss1-101432-spell-492314",
|
||||
"rw-ystra-labyrinth-boss1-101432-spell-492334"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -89,13 +318,67 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-spell-492310",
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-spell-490539",
|
||||
"name": "Giant Whirlwind",
|
||||
"spellId": 490539,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-aura-491008",
|
||||
"name": "Transport Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "transport-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-spell-492310",
|
||||
"name": "Drop Magic",
|
||||
"spellId": 492310,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 5000,
|
||||
"initialCooldownMs": [
|
||||
@@ -107,6 +390,115 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
5000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-spell-492312",
|
||||
"name": "Energy Overload",
|
||||
"spellId": 492312,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-aura-492313",
|
||||
"name": "Magic Focus",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "magic-focus",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-spell-492314",
|
||||
"name": "Magic Explosion",
|
||||
"spellId": 492314,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101432-spell-492334",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"spellId": 492334,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -137,9 +529,18 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ystra-labyrinth-boss1-101433-spell-490524",
|
||||
"rw-ystra-labyrinth-boss1-101433-aura-491008",
|
||||
"rw-ystra-labyrinth-boss1-101433-aura-492256",
|
||||
"rw-ystra-labyrinth-boss1-101433-aura-492322",
|
||||
"rw-ystra-labyrinth-boss1-101433-spell-492323",
|
||||
"rw-ystra-labyrinth-boss1-101433-aura-492324",
|
||||
"rw-ystra-labyrinth-boss1-101433-spell-492327"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -148,6 +549,183 @@ export const YSTRA_LABYRINTH_BOSS1_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-spell-490524",
|
||||
"name": "Clash",
|
||||
"spellId": 490524,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 6,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-aura-491008",
|
||||
"name": "Transport Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "transport-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-aura-492256",
|
||||
"name": "Fog of Petrification",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 8000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "fog-of-petrification",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-aura-492322",
|
||||
"name": "Cursed Poison Fog",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 8000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "cursed-posion",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-spell-492323",
|
||||
"name": "Potent Poison Swipe",
|
||||
"spellId": 492323,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-aura-492324",
|
||||
"name": "Demon's Roar",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "demon-s-roar",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss1-101433-spell-492327",
|
||||
"name": "Charge Assault",
|
||||
"spellId": 492327,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"telegraph": {
|
||||
"shape": "line",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"length": 30,
|
||||
"width": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -274,6 +852,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -15,9 +15,18 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ystra-labyrinth-boss2-102196-spell-490539",
|
||||
"rw-ystra-labyrinth-boss2-102196-aura-491008",
|
||||
"rw-ystra-labyrinth-boss2-102196-spell-492310",
|
||||
"rw-ystra-labyrinth-boss2-102196-spell-492312",
|
||||
"rw-ystra-labyrinth-boss2-102196-aura-492313",
|
||||
"rw-ystra-labyrinth-boss2-102196-spell-492314",
|
||||
"rw-ystra-labyrinth-boss2-102196-spell-492334"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -28,13 +37,67 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-spell-492310",
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-spell-490539",
|
||||
"name": "Giant Whirlwind",
|
||||
"spellId": 490539,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-aura-491008",
|
||||
"name": "Transport Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "transport-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-spell-492310",
|
||||
"name": "Drop Magic",
|
||||
"spellId": 492310,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 30,
|
||||
"cooldownMs": 5000,
|
||||
"initialCooldownMs": [
|
||||
@@ -46,6 +109,115 @@ export const YSTRA_LABYRINTH_BOSS2_ENTITIES = {
|
||||
5000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-spell-492312",
|
||||
"name": "Energy Overload",
|
||||
"spellId": 492312,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-aura-492313",
|
||||
"name": "Magic Focus",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "magic-focus",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-spell-492314",
|
||||
"name": "Magic Explosion",
|
||||
"spellId": 492314,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 7.5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 7.5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 7.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss2-102196-spell-492334",
|
||||
"name": "Manastorms Duo Lv5",
|
||||
"spellId": 492334,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "shadow",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 15,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -132,6 +304,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -15,9 +15,18 @@ export const YSTRA_LABYRINTH_BOSS3_ENTITIES = {
|
||||
"damageMultiplier": 1.3,
|
||||
"moveSpeed": 2.25,
|
||||
"leashRange": 32,
|
||||
"mechanicIds": [
|
||||
"rw-ystra-labyrinth-boss3-102197-spell-490524",
|
||||
"rw-ystra-labyrinth-boss3-102197-aura-491008",
|
||||
"rw-ystra-labyrinth-boss3-102197-aura-492256",
|
||||
"rw-ystra-labyrinth-boss3-102197-aura-492322",
|
||||
"rw-ystra-labyrinth-boss3-102197-spell-492323",
|
||||
"rw-ystra-labyrinth-boss3-102197-aura-492324",
|
||||
"rw-ystra-labyrinth-boss3-102197-spell-492327"
|
||||
],
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -26,6 +35,183 @@ export const YSTRA_LABYRINTH_BOSS3_ENTITIES = {
|
||||
"range": 4.5,
|
||||
"cooldownMs": 1900,
|
||||
"damageMultiplier": 1
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-spell-490524",
|
||||
"name": "Clash",
|
||||
"spellId": 490524,
|
||||
"delivery": "projectile",
|
||||
"target": "primary",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 0,
|
||||
"range": 6,
|
||||
"cooldownMs": 10000,
|
||||
"initialCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
10000,
|
||||
10000
|
||||
],
|
||||
"damageMultiplier": 0.85
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-aura-491008",
|
||||
"name": "Transport Magic",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "transport-magic",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-aura-492256",
|
||||
"name": "Fog of Petrification",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 8000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "fog-of-petrification",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-aura-492322",
|
||||
"name": "Cursed Poison Fog",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 8000,
|
||||
"initialCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
8000,
|
||||
8000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "cursed-posion",
|
||||
"durationMs": 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-spell-492323",
|
||||
"name": "Potent Poison Swipe",
|
||||
"spellId": 492323,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "attack",
|
||||
"castTimeMs": 1800,
|
||||
"range": 5,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"radius": 5,
|
||||
"telegraph": {
|
||||
"shape": "circle",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"radius": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-aura-492324",
|
||||
"name": "Demon's Roar",
|
||||
"delivery": "area",
|
||||
"target": "self",
|
||||
"school": "arcane",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1200,
|
||||
"range": 0,
|
||||
"cooldownMs": 15000,
|
||||
"initialCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
15000,
|
||||
15000
|
||||
],
|
||||
"damageMultiplier": 0,
|
||||
"effect": {
|
||||
"kind": "aura",
|
||||
"aura": "demon-s-roar",
|
||||
"durationMs": 7500
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rw-ystra-labyrinth-boss3-102197-spell-492327",
|
||||
"name": "Charge Assault",
|
||||
"spellId": 492327,
|
||||
"delivery": "area",
|
||||
"target": "nearby-party",
|
||||
"school": "physical",
|
||||
"animation": "cast",
|
||||
"castTimeMs": 1800,
|
||||
"range": 30,
|
||||
"cooldownMs": 6500,
|
||||
"initialCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"repeatCooldownMs": [
|
||||
6500,
|
||||
6500
|
||||
],
|
||||
"damageMultiplier": 0.85,
|
||||
"telegraph": {
|
||||
"shape": "line",
|
||||
"anchor": "source",
|
||||
"warningMs": 1800,
|
||||
"length": 30,
|
||||
"width": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -112,6 +298,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} 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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -16,7 +16,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -76,7 +76,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -136,7 +136,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -176,7 +176,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -256,7 +256,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -336,7 +336,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -416,7 +416,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -456,7 +456,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -536,7 +536,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -576,7 +576,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -616,7 +616,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -656,7 +656,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -696,7 +696,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -736,7 +736,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -836,7 +836,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -876,7 +876,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -936,7 +936,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1016,7 +1016,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1096,7 +1096,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1136,7 +1136,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1196,7 +1196,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1276,7 +1276,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1316,7 +1316,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1376,7 +1376,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -1436,7 +1436,7 @@ export const ZURHIDON_STRONGHOLD_ENTITIES = {
|
||||
"leashRange": 24,
|
||||
"attacks": [
|
||||
{
|
||||
"id": "rw-zurhidon-stronghold-basic-attack",
|
||||
"id": "basic-attack",
|
||||
"name": "Basic Attack",
|
||||
"delivery": "melee",
|
||||
"target": "primary",
|
||||
@@ -3193,6 +3193,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": "ad29efc1d4906fdd4f2ac7a69316cac32d8fcbaea674678f050265c86e0c506c",
|
||||
"sourceSpellCatalogSha256": "c47e67013e68ecf9c7ae30ef4b2058a850505385739dfe8c32ce8874e6ed077c",
|
||||
"sourceSpellPolicy": "Only positively classified direct-HP-damage families execute; coefficients remain conservative HealerMan values."
|
||||
} as const;
|
||||
@@ -1,6 +1,10 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
MANASTORM_DEFAULT_PORTAL_READY_CUE_ID,
|
||||
MANASTORM_PORTAL_AUDIO_ROOT,
|
||||
MANASTORM_PORTAL_AUDIO_CUES,
|
||||
manastormCueForStatusTransition,
|
||||
manastormPortalAudioCue,
|
||||
@@ -10,6 +14,8 @@ import {
|
||||
} from "./manastormClientCues";
|
||||
import { useManastormStore } from "./manastormStore";
|
||||
|
||||
const projectDirectory = fileURLToPath(new URL("../..", import.meta.url));
|
||||
|
||||
describe("Manastorm imported client cues", () => {
|
||||
afterEach(() => {
|
||||
stopManastormClientCue();
|
||||
@@ -25,10 +31,23 @@ describe("Manastorm imported client cues", () => {
|
||||
.toHaveLength(3);
|
||||
for (const cue of MANASTORM_PORTAL_AUDIO_CUES) {
|
||||
expect(cue.fileName).toMatch(/^Manastorm_BossKill_.+\.wav$/);
|
||||
expect(cue.url).toContain(cue.fileName);
|
||||
expect(cue.url).toBe(`${MANASTORM_PORTAL_AUDIO_ROOT}/${cue.fileName}`);
|
||||
const audio = readFileSync(path.join(projectDirectory, "public", cue.url.slice(1)));
|
||||
expect(audio.subarray(0, 4).toString("ascii")).toBe("RIFF");
|
||||
expect(audio.subarray(8, 12).toString("ascii")).toBe("WAVE");
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps optional portal audio out of Vite source imports", () => {
|
||||
const source = readFileSync(
|
||||
new URL("./manastormClientCues.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(source).not.toContain("LadiksMPQEditor");
|
||||
expect(source).not.toMatch(/new URL\s*\(/);
|
||||
});
|
||||
|
||||
it("maps only an actual portal-ready state edge to the automatic cue", () => {
|
||||
expect(manastormCueForStatusTransition("awaiting-loot", "portal-ready"))
|
||||
.toBe(MANASTORM_DEFAULT_PORTAL_READY_CUE_ID);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { playMasterAudioCue } from "./combatAudio";
|
||||
|
||||
export type ManastormPortalCueSignal = "portal-coming-up" | "portal-ready";
|
||||
|
||||
export interface ManastormPortalAudioCue {
|
||||
@@ -24,26 +26,12 @@ export interface ManastormAudioHandle {
|
||||
|
||||
export type ManastormAudioFactory = (url: string) => ManastormAudioHandle;
|
||||
|
||||
const genericPortalComingUpUrl = new URL(
|
||||
"../../../LadiksMPQEditor/patch-O/sound/Manastorm/Manastorm_BossKill_GenericPortalComingUp.wav",
|
||||
import.meta.url,
|
||||
).href;
|
||||
const singlePortalComingUpUrl = new URL(
|
||||
"../../../LadiksMPQEditor/patch-O/sound/Manastorm/Manastorm_BossKill_1PortalComingUp.wav",
|
||||
import.meta.url,
|
||||
).href;
|
||||
const alternateSinglePortalComingUpUrl = new URL(
|
||||
"../../../LadiksMPQEditor/patch-O/sound/Manastorm/Manastorm_BossKill_1PortalComingUp_2.wav",
|
||||
import.meta.url,
|
||||
).href;
|
||||
const genericPortalsReadyUrl = new URL(
|
||||
"../../../LadiksMPQEditor/patch-O/sound/Manastorm/Manastorm_BossKill_GenericPortalsReady.wav",
|
||||
import.meta.url,
|
||||
).href;
|
||||
const alternateGenericPortalsReadyUrl = new URL(
|
||||
"../../../LadiksMPQEditor/patch-O/sound/Manastorm/Manastorm_BossKill_GenericPortalsReady_2.wav",
|
||||
import.meta.url,
|
||||
).href;
|
||||
export const MANASTORM_PORTAL_AUDIO_ROOT =
|
||||
"/assets/game/combat-effects/audio/manastorm";
|
||||
|
||||
function packagedPortalAudioUrl(fileName: string): string {
|
||||
return `${MANASTORM_PORTAL_AUDIO_ROOT}/${fileName}`;
|
||||
}
|
||||
|
||||
export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
{
|
||||
@@ -52,7 +40,7 @@ export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
variant: "generic",
|
||||
alternate: false,
|
||||
fileName: "Manastorm_BossKill_GenericPortalComingUp.wav",
|
||||
url: genericPortalComingUpUrl,
|
||||
url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalComingUp.wav"),
|
||||
},
|
||||
{
|
||||
id: "single-portal-coming-up",
|
||||
@@ -60,7 +48,7 @@ export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
variant: "single",
|
||||
alternate: false,
|
||||
fileName: "Manastorm_BossKill_1PortalComingUp.wav",
|
||||
url: singlePortalComingUpUrl,
|
||||
url: packagedPortalAudioUrl("Manastorm_BossKill_1PortalComingUp.wav"),
|
||||
},
|
||||
{
|
||||
id: "single-portal-coming-up-alt",
|
||||
@@ -68,7 +56,7 @@ export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
variant: "single",
|
||||
alternate: true,
|
||||
fileName: "Manastorm_BossKill_1PortalComingUp_2.wav",
|
||||
url: alternateSinglePortalComingUpUrl,
|
||||
url: packagedPortalAudioUrl("Manastorm_BossKill_1PortalComingUp_2.wav"),
|
||||
},
|
||||
{
|
||||
id: "generic-portals-ready",
|
||||
@@ -76,7 +64,7 @@ export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
variant: "generic",
|
||||
alternate: false,
|
||||
fileName: "Manastorm_BossKill_GenericPortalsReady.wav",
|
||||
url: genericPortalsReadyUrl,
|
||||
url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalsReady.wav"),
|
||||
},
|
||||
{
|
||||
id: "generic-portals-ready-alt",
|
||||
@@ -84,7 +72,7 @@ export const MANASTORM_PORTAL_AUDIO_CUES = [
|
||||
variant: "generic",
|
||||
alternate: true,
|
||||
fileName: "Manastorm_BossKill_GenericPortalsReady_2.wav",
|
||||
url: alternateGenericPortalsReadyUrl,
|
||||
url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalsReady_2.wav"),
|
||||
},
|
||||
] as const satisfies readonly ManastormPortalAudioCue[];
|
||||
|
||||
@@ -167,4 +155,3 @@ export function stopManastormClientCue(): void {
|
||||
activeAudio?.pause();
|
||||
activeAudio = null;
|
||||
}
|
||||
import { playMasterAudioCue } from "./combatAudio";
|
||||
@@ -41,6 +41,9 @@ describe("catalog-driven party rotations", () => {
|
||||
school: "shadow",
|
||||
interruptible: true,
|
||||
targetActorId: "__player__",
|
||||
sourcePosition: [0, 0, 0],
|
||||
targetPosition: [1, 0, 0],
|
||||
sourceYaw: Math.PI / 2,
|
||||
startedAt: 1_000,
|
||||
completesAt: 5_000,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DUNGEON_DEFINITIONS } from "./dungeonRegistry";
|
||||
import { normalizeEnemyAttacks } from "./enemyAttacks";
|
||||
|
||||
const catalog = JSON.parse(readFileSync(
|
||||
"dungeon-pipeline/boss-mechanics.json",
|
||||
"utf8",
|
||||
)) as {
|
||||
readonly bosses: Readonly<Record<string, {
|
||||
readonly source?: { readonly kind?: string };
|
||||
readonly mechanics: readonly { readonly id: string }[];
|
||||
}>>;
|
||||
};
|
||||
|
||||
describe("modular RuneWaker boss mechanics", () => {
|
||||
const dungeons = DUNGEON_DEFINITIONS.filter(
|
||||
(definition) => definition.expansion === "runewaker",
|
||||
);
|
||||
const bosses = dungeons.flatMap((dungeon) => (
|
||||
Object.values(dungeon.entities)
|
||||
.filter((entity) => entity.kind === "boss")
|
||||
.map((entity) => ({ dungeon, entity }))
|
||||
));
|
||||
|
||||
it("gives every packaged boss a catalog-backed special-mechanic loadout", () => {
|
||||
expect(bosses).toHaveLength(56);
|
||||
for (const { dungeon, entity } of bosses) {
|
||||
const label = `${dungeon.id}:${entity.id}`;
|
||||
const authored = catalog.bosses[entity.id];
|
||||
expect(authored, label).toBeDefined();
|
||||
expect(authored.mechanics.length, label).toBeGreaterThan(0);
|
||||
expect(entity.combat?.mechanicIds, label).toEqual(
|
||||
authored.mechanics.map((mechanic) => mechanic.id),
|
||||
);
|
||||
expect(entity.combat?.attacks[0]?.id, label).toBe("basic-attack");
|
||||
expect(entity.combat?.attacks.length, label).toBeGreaterThan(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes every harmful spatial mechanic with matching warning geometry", () => {
|
||||
let telegraphed = 0;
|
||||
let healthTriggered = 0;
|
||||
for (const { dungeon, entity } of bosses) {
|
||||
const label = `${dungeon.id}:${entity.id}`;
|
||||
const attacks = normalizeEnemyAttacks(entity.combat?.attacks);
|
||||
for (const attack of attacks.slice(1)) {
|
||||
if (attack.condition?.kind === "self-health-percent") healthTriggered += 1;
|
||||
if (attack.telegraph) {
|
||||
telegraphed += 1;
|
||||
expect(attack.castTimeMs, label).toBeGreaterThanOrEqual(attack.telegraph.warningMs);
|
||||
if (attack.telegraph.shape === "circle") {
|
||||
expect(attack.telegraph.radius, label).toBeGreaterThan(0);
|
||||
} else {
|
||||
expect(attack.telegraph.length, label).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(telegraphed).toBeGreaterThanOrEqual(100);
|
||||
expect(healthTriggered).toBeGreaterThanOrEqual(25);
|
||||
});
|
||||
|
||||
it("keeps source-gap fallbacks explicit instead of presenting them as recovered mechanics", () => {
|
||||
const sourceGaps = bosses.filter(({ entity }) => (
|
||||
catalog.bosses[entity.id]?.source?.kind === "healer-man-source-gap"
|
||||
));
|
||||
expect(sourceGaps.map(({ entity }) => entity.id).sort()).toEqual([
|
||||
"rw-fa-100070",
|
||||
"rw-fa-100561",
|
||||
"rw-fa-100562",
|
||||
"rw-fa-100563",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -86,14 +86,14 @@ describe("portable RuneWaker source-spell mappings", () => {
|
||||
expect(source?.runtime).toBeDefined();
|
||||
expect(attack).toMatchObject({
|
||||
name: source?.name,
|
||||
delivery: source?.runtime?.delivery,
|
||||
target: source?.runtime?.target,
|
||||
school: source?.runtime?.school,
|
||||
animation: source?.runtime?.animation,
|
||||
range: source?.runtime?.range,
|
||||
cooldownMs: source?.runtime?.cooldownMs,
|
||||
damageMultiplier: source?.runtime?.damageMultiplier,
|
||||
});
|
||||
expect(["melee", "projectile", "area"]).toContain(attack.delivery);
|
||||
expect(["primary", "nearby-party", "random-party"]).toContain(attack.target);
|
||||
expect(attack.range).toBeGreaterThanOrEqual(source?.runtime?.range ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ const campaignIds = new Set(campaign.dungeons.map((dungeon) => dungeon.slug));
|
||||
const basicOnlyBossExceptions = new Set([
|
||||
"wailing-caverns:kresh",
|
||||
"wailing-caverns:deviate-faerie-dragon",
|
||||
"blackrock-spire:entry-9218",
|
||||
"blackrock-spire:entry-9219",
|
||||
"blackrock-spire:entry-10376",
|
||||
]);
|
||||
const noTrashExceptions = new Set(["trial-of-the-champion"]);
|
||||
const animationCache = new Map<string, Promise<readonly { readonly name: string }[]>>();
|
||||
@@ -171,4 +174,4 @@ describe("WoW dungeon combat and animation coverage", () => {
|
||||
}
|
||||
}
|
||||
}, 120_000);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import { useCombatStore } from "../game/combatStore";
|
||||
import {
|
||||
combatEffectStyle,
|
||||
preloadCombatEffectManifest,
|
||||
effectiveCombatVfxMode,
|
||||
subscribeCombatPresentation,
|
||||
type CombatPresentationEvent,
|
||||
type CombatVfxQuality,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
import {
|
||||
combatAudioOutputEnabled,
|
||||
configureCombatAudio,
|
||||
combatMediaPreloadEnabled,
|
||||
playCombatAudio,
|
||||
resetCombatAudio,
|
||||
setCombatAudioListener,
|
||||
@@ -254,17 +256,18 @@ export function CombatEffects({ active, safeGraphics = false }: CombatEffectsPro
|
||||
const camera = useThree((state) => state.camera);
|
||||
const forward = useMemo(() => new Vector3(), []);
|
||||
const audioOutputEnabled = combatAudioOutputEnabled(settings, masterVolume);
|
||||
const vfxMode = effectiveCombatVfxMode(settings);
|
||||
|
||||
useEffect(() => {
|
||||
if (!import.meta.env.DEV) return;
|
||||
document.documentElement.dataset[COMBAT_EFFECT_DATASET_KEY] = JSON.stringify({
|
||||
mode: settings.vfxMode,
|
||||
mode: vfxMode,
|
||||
quality: effectiveQuality,
|
||||
systems: effects.length,
|
||||
activeLights: 0,
|
||||
allocatedLights: 0,
|
||||
});
|
||||
}, [effectiveQuality, effects.length, settings.vfxMode]);
|
||||
}, [effectiveQuality, effects.length, vfxMode]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (import.meta.env.DEV) delete document.documentElement.dataset[COMBAT_EFFECT_DATASET_KEY];
|
||||
@@ -274,10 +277,7 @@ export function CombatEffects({ active, safeGraphics = false }: CombatEffectsPro
|
||||
settingsRef.current = settings;
|
||||
qualityRef.current = safeGraphics && settings.vfxQuality === "high" ? "low" : settings.vfxQuality;
|
||||
configureCombatAudio(settings, masterVolume);
|
||||
if (
|
||||
qualityRef.current !== "off"
|
||||
|| combatAudioOutputEnabled(settings, masterVolume)
|
||||
) void preloadCombatEffectManifest();
|
||||
if (combatMediaPreloadEnabled(settings, masterVolume)) void preloadCombatEffectManifest();
|
||||
setEffects((current) => capCombatEffects(current.filter(({ event }) => (
|
||||
qualityRef.current !== "off" && settings.vfxEnabled[event.actorGroup]
|
||||
)), qualityRef.current));
|
||||
@@ -333,7 +333,7 @@ export function CombatEffects({ active, safeGraphics = false }: CombatEffectsPro
|
||||
return (
|
||||
<group name="combat-effects">
|
||||
{effects.map((effect) => (
|
||||
settings.vfxMode === "original" ? (
|
||||
vfxMode === "original" ? (
|
||||
<OriginalCombatEffect
|
||||
key={`original:${effect.key}`}
|
||||
event={effect.event}
|
||||
|
||||
@@ -1,12 +1,107 @@
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { DoubleSide } from "three";
|
||||
import { useCombatStore } from "../game/combatStore";
|
||||
import { useCombatStore, type EnemyActiveCast } from "../game/combatStore";
|
||||
import type { EnemyAttackDefinition, EnemyDamageSchool } from "../game/enemyAttacks";
|
||||
import {
|
||||
enemyTelegraphOrigin,
|
||||
enemyTelegraphYaw,
|
||||
} from "../game/enemyTelegraphGeometry";
|
||||
|
||||
const TELEGRAPH_COLORS: Readonly<Record<EnemyDamageSchool, string>> = Object.freeze({
|
||||
physical: "#f2b45e",
|
||||
arcane: "#c68cff",
|
||||
fire: "#ff684e",
|
||||
frost: "#75cfff",
|
||||
nature: "#8ddf68",
|
||||
shadow: "#bd79e8",
|
||||
holy: "#ffe47a",
|
||||
});
|
||||
|
||||
function EnemyCastTelegraph({
|
||||
cast,
|
||||
attack,
|
||||
}: {
|
||||
readonly cast: EnemyActiveCast;
|
||||
readonly attack: EnemyAttackDefinition;
|
||||
}) {
|
||||
const telegraph = attack.telegraph;
|
||||
if (!telegraph) return null;
|
||||
const placement = {
|
||||
sourcePosition: cast.sourcePosition,
|
||||
targetPosition: cast.targetPosition,
|
||||
sourceYaw: cast.sourceYaw,
|
||||
};
|
||||
const origin = enemyTelegraphOrigin(telegraph, placement);
|
||||
const position = [origin[0], origin[1] + 0.055, origin[2]] as const;
|
||||
const color = TELEGRAPH_COLORS[attack.school];
|
||||
if (telegraph.shape === "circle") {
|
||||
const radius = telegraph.radius ?? 1;
|
||||
return (
|
||||
<group position={position} name={`enemy-telegraph-${cast.attackId}`}>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} renderOrder={6}>
|
||||
<circleGeometry args={[radius, 64]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.18} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.008, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={7}>
|
||||
<ringGeometry args={[Math.max(0.1, radius - 0.18), radius, 64]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.9} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
const yaw = enemyTelegraphYaw(placement);
|
||||
if (telegraph.shape === "cone") {
|
||||
const length = telegraph.length ?? 1;
|
||||
const angle = ((telegraph.angleDegrees ?? 90) * Math.PI) / 180;
|
||||
const start = -Math.PI / 2 - angle / 2;
|
||||
return (
|
||||
<group position={position} rotation={[0, yaw, 0]} name={`enemy-telegraph-${cast.attackId}`}>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} renderOrder={6}>
|
||||
<circleGeometry args={[length, 48, start, angle]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.2} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.008, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={7}>
|
||||
<ringGeometry args={[Math.max(0.1, length - 0.18), length, 48, 1, start, angle]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.92} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
const length = telegraph.length ?? 1;
|
||||
const width = telegraph.width ?? 1;
|
||||
return (
|
||||
<group position={position} rotation={[0, yaw, 0]} name={`enemy-telegraph-${cast.attackId}`}>
|
||||
<mesh position={[0, 0, length / 2]} rotation={[Math.PI / 2, 0, 0]} renderOrder={6}>
|
||||
<planeGeometry args={[width + 0.22, length + 0.22]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.85} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.009, length / 2]} rotation={[Math.PI / 2, 0, 0]} renderOrder={7}>
|
||||
<planeGeometry args={[width, length]} />
|
||||
<meshBasicMaterial color={color} transparent opacity={0.2} depthWrite={false} side={DoubleSide} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
/** Presentation-only rings; placement and ticking remain in the combat runtime. */
|
||||
export function GroundEffectTelegraphs() {
|
||||
const effects = useCombatStore((state) => state.areaEffects);
|
||||
const pending = useCombatStore((state) => state.pendingGroundTarget);
|
||||
const mobs = useCombatStore((state) => state.mobs);
|
||||
const enemyCasts = useMemo(() => Object.values(mobs).flatMap((mob) => {
|
||||
if (!mob.engaged || mob.dead || !mob.activeCast) return [];
|
||||
const attack = mob.attacks.find((candidate) => candidate.id === mob.activeCast?.attackId);
|
||||
return attack?.telegraph ? [{ mobId: mob.id, cast: mob.activeCast, attack }] : [];
|
||||
}), [mobs]);
|
||||
return (
|
||||
<group name="combat-ground-effects">
|
||||
{enemyCasts.map(({ mobId, cast, attack }) => (
|
||||
<Fragment key={`${mobId}:${cast.attackId}:${cast.startedAt}`}>
|
||||
<EnemyCastTelegraph cast={cast} attack={attack} />
|
||||
</Fragment>
|
||||
))}
|
||||
{pending ? (
|
||||
<mesh
|
||||
name="ground-target-reticle"
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from "../game/partyRuntimeRegistry";
|
||||
import { PLAYER_AGGRO_ID } from "../game/aggro";
|
||||
import {
|
||||
getMobRuntimeTransform,
|
||||
registerMobRuntime,
|
||||
updateMobRuntimePosition,
|
||||
} from "../game/mobRuntimeRegistry";
|
||||
@@ -1450,6 +1451,100 @@ function StaticPopulationCluster({
|
||||
);
|
||||
}
|
||||
|
||||
function SummonedPopulationMember({
|
||||
instanceId,
|
||||
entity,
|
||||
active,
|
||||
useOriginalModels,
|
||||
}: {
|
||||
readonly instanceId: string;
|
||||
readonly entity: PopulationEntityDefinition;
|
||||
readonly active: boolean;
|
||||
readonly useOriginalModels: boolean;
|
||||
}) {
|
||||
const groupRef = useRef<Group>(null);
|
||||
const initial = getMobRuntimeTransform(instanceId)?.position ?? [0, 0, 0];
|
||||
const positionRef = useRef<MutableMobVector3>([...initial]);
|
||||
useFrame((_, delta) => {
|
||||
const mob = useCombatStore.getState().mobs[instanceId];
|
||||
if (!mob || mob.dead || mob.despawned) return;
|
||||
const player = useGameStore.getState().playerPosition;
|
||||
const targetPosition = mobTargetPosition(mob.targetActorId, player);
|
||||
const position = positionRef.current;
|
||||
const now = Date.now();
|
||||
const movementLocked = mob.statuses.some((status) => status.endsAt > now
|
||||
&& MOVEMENT_LOCKING_MOB_STATUSES.has(status.kind));
|
||||
const feared = mob.statuses.some((status) => status.endsAt > now && status.kind === "fear");
|
||||
const slow = mob.statuses.find((status) => status.endsAt > now && status.kind === "slow")?.magnitude ?? 0;
|
||||
const maximumStep = Math.min(delta, 0.05) * mob.moveSpeed * Math.max(0.1, 1 - slow);
|
||||
if (!movementLocked && feared) {
|
||||
stepFearedMobAway(
|
||||
position,
|
||||
targetPosition,
|
||||
mob.homePosition ?? initial,
|
||||
maximumStep * 1.1,
|
||||
mob.leashRange,
|
||||
position,
|
||||
);
|
||||
} else if (!movementLocked && mob.combatPhase === "chasing") {
|
||||
stepToward(position, targetPosition, maximumStep);
|
||||
}
|
||||
const facingSign = feared && !movementLocked ? -1 : 1;
|
||||
const yaw = Math.atan2(
|
||||
(targetPosition[0] - position[0]) * facingSign,
|
||||
(targetPosition[2] - position[2]) * facingSign,
|
||||
);
|
||||
if (groupRef.current) {
|
||||
groupRef.current.position.set(position[0], position[1], position[2]);
|
||||
groupRef.current.rotation.y = yaw;
|
||||
}
|
||||
updateMobRuntimePosition(instanceId, position[0], position[1], position[2], yaw);
|
||||
});
|
||||
return (
|
||||
<group ref={groupRef} position={initial} name={`summoned-mob-${instanceId}`}>
|
||||
<ProxyMob
|
||||
definition={entity}
|
||||
instanceId={instanceId}
|
||||
active={active}
|
||||
moving
|
||||
showLabel={false}
|
||||
useOriginalModels={useOriginalModels}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function SummonedEnemyPopulation({
|
||||
entities,
|
||||
active,
|
||||
useOriginalModels,
|
||||
}: {
|
||||
readonly entities: PopulationDefinitionMap;
|
||||
readonly active: boolean;
|
||||
readonly useOriginalModels: boolean;
|
||||
}) {
|
||||
const mobs = useCombatStore((state) => state.mobs);
|
||||
const summons = useMemo(() => Object.values(mobs).flatMap((mob) => {
|
||||
if (!mob.summonedBy || mob.serverEntry === null || mob.despawned) return [];
|
||||
const entity = entities[`entry-${mob.serverEntry}`]
|
||||
?? Object.values(entities).find((candidate) => candidate.combat?.serverEntry === mob.serverEntry);
|
||||
return entity ? [{ instanceId: mob.id, entity }] : [];
|
||||
}), [entities, mobs]);
|
||||
return (
|
||||
<group name="summoned-enemy-population">
|
||||
{summons.map(({ instanceId, entity }) => (
|
||||
<SummonedPopulationMember
|
||||
key={instanceId}
|
||||
instanceId={instanceId}
|
||||
entity={entity}
|
||||
active={active}
|
||||
useOriginalModels={useOriginalModels}
|
||||
/>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export interface MobPopulationProps {
|
||||
readonly entities: PopulationDefinitionMap;
|
||||
readonly roamingPacks: readonly MobPackDefinition[];
|
||||
@@ -1512,6 +1607,11 @@ export function MobPopulation({
|
||||
useOriginalModels={useOriginalModels}
|
||||
/>
|
||||
))}
|
||||
<SummonedEnemyPopulation
|
||||
entities={entities}
|
||||
active={active}
|
||||
useOriginalModels={useOriginalModels}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Html } from "@react-three/drei";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { useMemo, useRef, type CSSProperties } from "react";
|
||||
import { useEffect, useMemo, useRef, type CSSProperties } from "react";
|
||||
import type { Group } from "three";
|
||||
import {
|
||||
classById,
|
||||
@@ -17,6 +17,7 @@ import { ProceduralAvatar } from "../avatar/ProceduralAvatar";
|
||||
import { useCombatStore } from "../game/combatStore";
|
||||
import { onlinePlayerActorId } from "../game/onlineSessionRuntime";
|
||||
import { usePartyStore } from "../game/partyStore";
|
||||
import { registerCombatEffectAnchor } from "./combatEffectAnchorRegistry";
|
||||
|
||||
const labelStyle: CSSProperties = {
|
||||
color: "#d8f6ff",
|
||||
@@ -60,6 +61,11 @@ function RemoteOnlinePlayer({ player, active }: {
|
||||
const actorId = onlinePlayerActorId(player.accountId);
|
||||
const selected = useCombatStore((state) => state.selectedFriendlyActorId === actorId);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
return root ? registerCombatEffectAnchor(actorId, root) : undefined;
|
||||
}, [actorId]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo, useEffect, useMemo, useRef, useState, type MutableRefObject } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import {
|
||||
AdditiveBlending,
|
||||
BufferAttribute,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Texture,
|
||||
TextureLoader,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import {
|
||||
combatOriginalPhaseAssets,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
type CombatPresentationEvent,
|
||||
type CombatVfxQuality,
|
||||
} from "../game/combatPresentation";
|
||||
import { fetchCombatMediaAsset } from "../game/combatMediaPack";
|
||||
import { abilityById } from "../game/abilityCatalog";
|
||||
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "../game/combatStore";
|
||||
import { getMobPosition } from "../game/mobRuntimeRegistry";
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
parseOriginalCombatMeshGraph,
|
||||
type OriginalCombatMeshGraph,
|
||||
} from "./originalCombatMeshRuntime";
|
||||
import { updateCombatEffectAnchorPosition } from "./combatEffectAnchorRegistry";
|
||||
|
||||
type OriginalSourceEffectGraph = OriginalCombatEffectGraph | OriginalCombatMeshGraph;
|
||||
|
||||
@@ -201,7 +204,7 @@ async function loadGraph(url: string): Promise<OriginalSourceEffectGraph> {
|
||||
// Graph filenames are keyed to the immutable source M2, while the decoded
|
||||
// browser schema can improve between releases. Revalidate the small JSON so
|
||||
// an older cached decoder payload cannot fail the current strict parser.
|
||||
const request = fetch(url, { cache: "no-cache" })
|
||||
const request = fetchCombatMediaAsset(url)
|
||||
.then((response) => response.ok
|
||||
? response.json()
|
||||
: Promise.reject(new Error(`Original effect graph request failed (${response.status}): ${url}`)))
|
||||
@@ -218,11 +221,18 @@ async function loadGraph(url: string): Promise<OriginalSourceEffectGraph> {
|
||||
async function loadTexture(url: string): Promise<Texture> {
|
||||
const existing = textureRequestCache.get(url);
|
||||
if (existing) return existing;
|
||||
const request = textureLoader.loadAsync(url).then((texture) => {
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.name = `original-combat-effect:${url}`;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
const request = fetchCombatMediaAsset(url).then(async (response) => {
|
||||
if (!response.ok) throw new Error(`Original effect texture request failed (${response.status}): ${url}`);
|
||||
const objectUrl = URL.createObjectURL(await response.blob());
|
||||
try {
|
||||
const texture = await textureLoader.loadAsync(objectUrl);
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.name = `original-combat-effect:${url}`;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
} finally {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
});
|
||||
textureRequestCache.set(url, request);
|
||||
request.catch(() => textureRequestCache.delete(url));
|
||||
@@ -446,7 +456,8 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
readonly active: boolean;
|
||||
readonly playbackRef: MutableRefObject<{ elapsedMs: number; decaying: boolean }>;
|
||||
}) {
|
||||
const billboardRef = useRef<Group>(null);
|
||||
const camera = useThree((state) => state.camera);
|
||||
const sourcePlaneRef = useRef<Group>(null);
|
||||
const geometry = useMemo(() => {
|
||||
const next = new BufferGeometry();
|
||||
const sourcePositions = graph.geometry.positions;
|
||||
@@ -457,7 +468,7 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
const positions = new Float32Array(sourcePositions.length);
|
||||
const normals = new Float32Array(sourceNormals.length);
|
||||
for (let index = 0; index < sourcePositions.length; index += 3) {
|
||||
// Turn WoW's Z-up source plane into an upright, camera-facing local XY plane.
|
||||
// Turn WoW's Z-up source plane into an upright local XY plane.
|
||||
positions[index] = -sourcePositions[index + 1];
|
||||
positions[index + 1] = sourcePositions[index + 2];
|
||||
positions[index + 2] = sourcePositions[index] - xCenter;
|
||||
@@ -501,9 +512,14 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
for (const material of materials) material.dispose();
|
||||
}, [geometry, materials]);
|
||||
|
||||
useFrame(({ camera }) => {
|
||||
useEffect(() => {
|
||||
// WoW authored this mesh as a billboard. Resolve that orientation once at
|
||||
// spawn so it stays readable without rotating whenever the camera moves.
|
||||
sourcePlaneRef.current?.lookAt(camera.position);
|
||||
}, [camera]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!active) return;
|
||||
if (billboardRef.current) billboardRef.current.lookAt(camera.position);
|
||||
const playback = originalCombatMeshPlaybackFrame(
|
||||
graph,
|
||||
playbackRef.current.elapsedMs,
|
||||
@@ -517,8 +533,22 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={billboardRef}>
|
||||
<mesh geometry={geometry} material={materials} renderOrder={20} frustumCulled={false} />
|
||||
<group ref={sourcePlaneRef}>
|
||||
<mesh
|
||||
name="original-combat-static-source-plane"
|
||||
geometry={geometry}
|
||||
material={materials}
|
||||
renderOrder={20}
|
||||
frustumCulled={false}
|
||||
/>
|
||||
<mesh
|
||||
name="original-combat-static-cross-plane"
|
||||
geometry={geometry}
|
||||
material={materials}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
renderOrder={20}
|
||||
frustumCulled={false}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
@@ -533,6 +563,7 @@ function LoadedOriginalCombatEffect({
|
||||
const elapsedRef = useRef(0);
|
||||
const doneRef = useRef(false);
|
||||
const groupRef = useRef<Group>(null);
|
||||
const liveAnchorPosition = useMemo(() => new Vector3(), []);
|
||||
const auraDecayStartedAtRef = useRef<number | null>(null);
|
||||
const staticMeshPlaybackRef = useRef({ elapsedMs: 0, decaying: false });
|
||||
const particleBudgets = useMemo(() => {
|
||||
@@ -617,8 +648,10 @@ function LoadedOriginalCombatEffect({
|
||||
);
|
||||
} else {
|
||||
const actorId = originalCombatAnchorActorId(event);
|
||||
const live = liveActorPosition(actorId);
|
||||
if (live) groupRef.current.position.set(live[0], live[1], live[2]);
|
||||
if (!updateCombatEffectAnchorPosition(actorId, groupRef.current, liveAnchorPosition)) {
|
||||
const live = liveActorPosition(actorId);
|
||||
if (live) groupRef.current.position.set(live[0], live[1], live[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const terminalDuration = projectile
|
||||
|
||||
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user