commit aa1cd9ee40278930982cc1e849065caf7ce2938f Author: Florian Berthold Date: Sat Apr 25 21:39:15 2026 +0200 initial commit: classic-only M+ planner with upreza-derived maps diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa946ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python +.venv/ +__pycache__/ +*.pyc +*.pyo + +# OS +.DS_Store +Thumbs.db + +# Editor +.vscode/ +.idea/ +*.swp + +# Large data assets — not tracked in git (no LFS yet) +# data/uprez is an 11 GB sparse-clone of upreza interface +data/uprez/ +# legacy hires PNG dump (gone, but keep ignored) +data/maps_png_hires/ +# 336 MB of WebP map tiles — exclude until LFS is set up +web/assets/maps/ + +# Build output +output/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b43aac8 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# mplus-routes + +Mythic+ route planner targeted at `mplus.exil.es` (Ascension community). +Static web app: pick a dungeon, see the 4K AI-upscaled map with boss +positions, click to drop waypoints/pull markers, draw a route, share via +URL hash. + +## Data sources + +- **Boss coords:** AtlasLootAscension `MapData.lua` (vanilla / TBC / WotLK + data files), exported by `bisbeard/export_atlasloot_maps.lua` → + `data/atlasloot_maps.json`. +- **Dungeon maps:** [keyboardturner/WoWMapUprez_Dungeons][uprez] — + AI-upscaled (SRCNN) ~4K dungeon maps for every WoW expansion. Tiles are + 4×3 grids of 1024×1024 BLPs that we stitch into 4096×3072 PNGs. + +[uprez]: https://github.com/keyboardturner/WoWMapUprez_Dungeons + +## Stack + +- **Frontend:** vanilla HTML/CSS/JS, single page, no build step. Pan/zoom + via CSS transform on the canvas stage; all overlays drawn into an SVG + matching the map's image-pixel coord space. +- **Build pipeline:** Python + Pillow (uses Pillow's native BLP decoder). +- **Hosting:** static. ~260 MB of WebPs total — drop behind any nginx / + CDN. No backend. + +## Layout + +``` +mplus-routes/ +├── data/ +│ ├── uprez/ # sparse-clone of WoWMapUprez_Dungeons +│ ├── atlasloot_maps.json # boss coords by MapName +│ └── aliases.json # AtlasLoot id → uprez basename overrides +├── tools/ +│ ├── stitch_uprez.py # BLP tiles → 4096×3072 WebP per floor +│ └── build_data.py # combine into web/assets/dungeons.json +└── web/ + ├── index.html + ├── style.css + ├── app.js + └── assets/ + ├── dungeons.json # combined data feeding the UI + └── maps/ # per-dungeon WebP files served to browsers +``` + +## Build + +```bash +python3 -m venv .venv +.venv/bin/pip install Pillow + +# 1. (one-time) sparse-clone the uprez map pack into data/uprez/ +git clone --depth=1 --no-checkout https://github.com/keyboardturner/WoWMapUprez_Dungeons.git data/uprez +cd data/uprez && git sparse-checkout init --cone && git sparse-checkout set "Interface/Worldmap" && git checkout && cd ../.. + +# 2. stitch BLP tiles → WebP (parallel, ~2 minutes on a modern laptop) +.venv/bin/python tools/stitch_uprez.py --no-png + +# 3. combine boss coords + map index → dungeons.json +.venv/bin/python tools/build_data.py +``` + +`stitch_uprez.py --max-width 2048` halves output size if 4K is overkill. + +## Run locally + +```bash +cd web && python3 -m http.server 8765 +# http://localhost:8765/ +``` + +## Hosting + +The whole `web/` directory is fully static. + +- **Domain:** `mplus.exil.es` +- **TLS:** wildcard via the cluster's existing edge (Caddy / Traefik / certbot). +- **Backend:** none — routes encode into `#` for sharing. + +A short-URL service is a clean follow-up if shareable `/r/` URLs +become wanted (KV-backed Cloudflare Worker would be enough). + +## Refresh data sources + +- **AtlasLoot coords:** re-export with `lua5.4 ../bisbeard/export_atlasloot_maps.lua > data/atlasloot_maps.json` + after AtlasLootAscension updates. +- **Maps:** `cd data/uprez && git pull` then re-run `stitch_uprez.py` + + `build_data.py` after upstream uprez updates. + +## Attribution + +Map textures derived from [WoWMapUprez_Dungeons][uprez]. +World of Warcraft trademarks belong to Blizzard Entertainment. +This site is not affiliated with Blizzard. + +## Known gaps + +- AtlasLootAscension's WotLK `MapData.lua` has a known upstream bug where + every dungeon's `MapName` is `"DireMaul"` — we ignore `MapName` for + resolution and use the AtlasLoot dungeon-id (with explicit aliases as + needed) instead. +- A handful of dungeons in AtlasLoot don't have a corresponding upreza + texture (e.g. SerpentshrineCavern interior) — those land in the + picker without a map and can still be used for blank-canvas routing. +- No trash-pull data — coords are boss-only. Trash groups are authored + by hand using the Pull tool. +- Multi-floor dungeons currently put all bosses into `unassignedBosses` + rather than mapping each pin to its specific floor. Floor-assignment + is a follow-up tool. diff --git a/data/aliases.json b/data/aliases.json new file mode 100644 index 0000000..8f840d6 --- /dev/null +++ b/data/aliases.json @@ -0,0 +1,54 @@ +{ + "_comment": "Maps AtlasLoot dungeon-id → list of webp basenames (or base map names). A bare base like 'Ulduar' expands to every stitched floor: Ulduar_overview, Ulduar_floor1, …. Case-insensitive auto-match handles most names — only put entries here when the names truly differ.", + + "AuchAuchenaiCrypts": ["AuchenaiCrypts"], + "AuchManaTombs": ["ManaTombs"], + "AuchSethekkHalls": ["SethekkHalls"], + "AuchShadowLabyrinth": ["ShadowLabyrinth"], + + "CFRSerpentshrineCavern": ["CoilfangReservoir"], + "CFRTheSlavePens": ["TheSlavePens"], + "CFRTheSteamvault": ["TheSteamvault"], + "CFRTheUnderbog": ["TheUnderbog"], + + "CoTBlackMorass": ["CoTTheBlackMorass"], + "CoTHyjal": ["CoTMountHyjal"], + "CoTOldHillsbrad": ["CoTHillsbradFoothills"], + "CoTOldStratholme": ["CoTStratholme"], + + "FHHallsOfReflection": ["HallsofReflection"], + "FHPitOfSaron": ["PitofSaron"], + "FHTheForgeOfSouls": ["TheForgeofSouls"], + + "HCBloodFurnace": ["TheBloodFurnace"], + "HCHellfireRamparts": ["HellfireRamparts"], + "HCMagtheridonsLair": ["Magtheridonslair"], + "HCTheShatteredHalls": ["TheShatteredHalls"], + + "TempestKeepArcatraz": ["TheArcatraz"], + "TempestKeepBotanica": ["TheBotanica"], + "TempestKeepMechanar": ["TheMechanar"], + "TempestKeepTheEye": ["TempestKeep"], + + "Naxxramas60": ["Naxxramas"], + "Onyxia60": ["OnyxiasLair"], + + "ObsidianSanctum": ["TheObsidianSanctum"], + "RubySanctum": ["TheRubySanctum"], + "TheRuinsofAhnQiraj": ["RuinsofAhnQiraj"], + "TheTempleofAhnQiraj": ["AhnQiraj"], + "TheSunkenTemple": ["TheTempleofAtalhakkar"], + "ragefire": ["Ragefire"], + + "_icc": "AtlasLoot has IcecrownCitadelA/B/C as separate; uprez has only IcecrownCitadel", + "IcecrownCitadelA": ["IcecrownCitadel"], + "IcecrownCitadelB": ["IcecrownCitadel"], + "IcecrownCitadelC": ["IcecrownCitadel"], + + "_ulduar": "AtlasLoot has UlduarA/B/C/D/E as separate (5 wings); uprez has multi-floor Ulduar — show all floors for any wing", + "UlduarA": ["Ulduar"], + "UlduarB": ["Ulduar"], + "UlduarC": ["Ulduar"], + "UlduarD": ["Ulduar"], + "UlduarE": ["Ulduar"] +} diff --git a/data/atlasloot_maps.json b/data/atlasloot_maps.json new file mode 100644 index 0000000..c4c53f4 --- /dev/null +++ b/data/atlasloot_maps.json @@ -0,0 +1,7450 @@ +{ + "BurningCrusade": { + "AuchAuchenaiCrypts": { + "1": [ + { + "1": "Halls of the Hereafter", + "SubZone": true + } + ], + "2": [ + { + "1": "Bridge of Souls", + "SubZone": true + }, + { + "1": "Shirrak the Dead Watcher", + "cords": [ + 47, + 67 + ], + "pinType": "dungeonskull" + }, + { + "1": "Exarch Maladaar", + "cords": [ + 74, + 50 + ], + "pinType": "dungeonskull" + }, + [ + "Avatar of the Martyred" + ], + [ + "D'ore" + ] + ], + "Acronym": "AC", + "LevelRange": "64-70", + "Location": [ + "Terokkar Forest", + 3519 + ], + "MapName": "AuchenaiCrypts", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "Lower City", + "ZoneName": [ + "Auchenai Crypts", + 3790 + ] + }, + "AuchManaTombs": { + "1": [ + { + "1": "Mana-Tombs", + "SubZone": true + }, + { + "1": "Pandemonius", + "cords": [ + 49, + 29 + ], + "pinType": "dungeonskull" + }, + [ + "Shadow Lord Xiraxis" + ], + { + "1": "Ambassador Pax'ivi (Heroic)", + "cords": [ + 61, + 15 + ], + "pinType": "dungeonskull" + }, + { + "1": "Tavarok", + "cords": [ + 61, + 74 + ], + "pinType": "dungeonskull" + }, + { + "1": "Cryo-Engineer Sha'heen", + "cords": [ + 43, + 84 + ], + "pinType": "dungeonskull" + }, + [ + "Ethereal Transporter Control Panel" + ], + { + "1": "Nexus-Prince Shaffar", + "cords": [ + 33, + 49 + ], + "pinType": "dungeonskull" + }, + [ + "Yor (Summon)" + ] + ], + "Acronym": "MT", + "LevelRange": "63-70", + "Location": [ + "Terokkar Forest", + 3519 + ], + "MapName": "ManaTombs", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "The Consortium", + "ZoneName": [ + "Mana-Tombs", + 3792 + ] + }, + "AuchSethekkHalls": { + "1": [ + { + "1": "Veil Sethekk", + "SubZone": true + }, + { + "1": "Darkweaver Syth", + "cords": [ + 49, + 68 + ], + "pinType": "dungeonskull" + }, + [ + "Lakka" + ] + ], + "2": [ + { + "1": "Halls of Mourning", + "SubZone": true + }, + { + "1": "The Saga of Terokk", + "cords": [ + 33, + 55 + ], + "pinType": "dungeonskull" + }, + [ + "Anzu (Summon)" + ], + { + "1": "Talon King Ikiss", + "cords": [ + 33, + 27 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Seth", + "LevelRange": "66-70", + "Location": [ + "Terokkar Forest", + 3519 + ], + "MapName": "SethekkHalls", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "Lower City", + "ZoneName": [ + "Sethekk Halls", + 3791 + ] + }, + "AuchShadowLabyrinth": { + "1": [ + { + "1": "Shadow Labyrinth", + "SubZone": true + }, + { + "1": "Spy To'gun", + "cords": [ + 17, + 24 + ] + }, + { + "1": "Ambassador Hellmaw", + "cords": [ + 23, + 39 + ], + "pinType": "dungeonskull" + }, + { + "1": "Blackheart the Inciter", + "cords": [ + 27, + 71 + ], + "pinType": "dungeonskull" + }, + { + "1": "Grandmaster Vorpil", + "cords": [ + 54, + 54 + ], + "pinType": "dungeonskull" + }, + [ + "The Codex of Blood" + ], + { + "1": "Murmur", + "cords": [ + 81, + 39 + ], + "pinType": "dungeonskull" + }, + { + "1": "Arcane Container", + "cords": [ + 83, + 32 + ] + }, + [ + "First Fragment Guardian" + ] + ], + "Acronym": "SL", + "LevelRange": "69-70", + "Location": [ + "Terokkar Forest", + 3519 + ], + "MapName": "ShadowLabyrinth", + "MinLevel": "65", + "PlayerLimit": "5", + "Reputation": "Lower City", + "ZoneName": [ + "Shadow Labyrinth", + 3789 + ] + }, + "BlackTemple": { + "1": [ + { + "1": "Karabor Sewers", + "SubZone": true + }, + { + "1": "High Warlord Naj'entus", + "cords": [ + 42.9, + 19.2 + ], + "pinType": "dungeonskull" + }, + [ + "Spirit of Olum" + ], + [ + "Supremus" + ] + ], + "2": [ + { + "1": "Sanctuary of Shadows", + "SubZone": true + }, + { + "1": "Spirit of Udalo", + "cords": [ + 62, + 50 + ] + }, + [ + "Aluyen " + ], + [ + "Okuno " + ], + [ + "Seer Kanai" + ], + { + "1": "Shade of Akama", + "cords": [ + 40, + 89 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Halls of Anguish", + "SubZone": true + }, + { + "1": "Gurtogg Bloodboil", + "cords": [ + 53, + 47 + ], + "pinType": "dungeonskull" + }, + { + "1": "Reliquary of Souls", + "cords": [ + 62, + 84 + ], + "pinType": "dungeonskull" + }, + [ + "Essence of Suffering" + ], + [ + "Essence of Desire" + ], + [ + "Essence of Anger" + ] + ], + "4": [ + { + "1": "Gorefiend's Vigil", + "SubZone": true + }, + { + "1": "Teron Gorefiend", + "cords": [ + 40, + 12 + ], + "pinType": "dungeonskull" + } + ], + "5": [ + { + "1": "Den of Mortal Delights", + "SubZone": true + }, + { + "1": "Mother Shahraz", + "cords": [ + 67, + 38 + ], + "pinType": "dungeonskull" + } + ], + "6": [ + { + "1": "Chamber of Command", + "SubZone": true + }, + { + "1": "The Illidari Council", + "cords": [ + 48, + 43 + ], + "pinType": "dungeonskull" + }, + [ + "Lady Malande (Priest)" + ], + [ + "High Nethermancer Zerevor (Mage)" + ], + [ + "Veras Darkshadow (Rogue)" + ] + ], + "7": [ + { + "1": "Temple Summit", + "SubZone": true + }, + { + "1": "Illidan Stormrage ", + "cords": [ + 53, + 71 + ], + "pinType": "dungeonskull" + } + ], + "8": [ + { + "1": "Illidari Training Grounds", + "SubZone": true + }, + { + "1": "Gathios the Shatterer (Paladin)", + "cords": [ + 67, + 47 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "BT", + "LevelRange": "70", + "Location": [ + "Shadowmoon Valley", + 3520 + ], + "MapName": "BlackTemple", + "MinLevel": "70", + "PlayerLimit": "10-25", + "Reputation": "Ashtongue Deathsworn", + "ZoneName": [ + "Black Temple [A] (Start)", + 3959 + ] + }, + "CFRSerpentshrineCavern": { + "1": [ + { + "1": "Serpentshrine Cavern", + "SubZone": true + }, + { + "1": "Hydross the Unstable ", + "cords": [ + 38, + 85 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Lurker Below", + "cords": [ + 37, + 59 + ], + "pinType": "dungeonskull" + }, + { + "1": "Leotheras the Blind", + "cords": [ + 42, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "Fathom-Lord Karathress", + "cords": [ + 48, + 14 + ], + "pinType": "dungeonskull" + }, + [ + "Seer Olum" + ], + { + "1": "Morogrim Tidewalker", + "cords": [ + 59, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lady Vashj ", + "cords": [ + 73, + 59 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SC", + "LevelRange": "70", + "Location": [ + "Zangarmarsh", + 3521 + ], + "MapName": "CoilfangReservoir", + "MinLevel": "70", + "PlayerLimit": "10-25", + "Reputation": "Cenarion Expedition", + "ZoneName": [ + "Serpentshrine Cavern", + 3607 + ] + }, + "CFRTheSlavePens": { + "1": [ + { + "1": "The Slave Pens", + "SubZone": true + }, + { + "1": "Ahune (Midsummer Festival)", + "cords": [ + 33, + 51 + ], + "pinType": "dungeonskull" + }, + { + "1": "Mennu the Betrayer", + "cords": [ + 49, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Weeder Greenthumb", + "cords": [ + 49, + 83 + ], + "pinType": "dungeonskull" + }, + { + "1": "Skar'this the Heretic (Heroic)", + "cords": [ + 58, + 85 + ], + "pinType": "dungeonskull" + }, + { + "1": "Rokmar the Crackler", + "cords": [ + 58, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Naturalist Bite", + "cords": [ + 95, + 67 + ], + "pinType": "dungeonskull" + }, + { + "1": "Quagmirran", + "cords": [ + 82, + 77 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SP", + "LevelRange": "61-69", + "Location": [ + "Zangarmarsh", + 3521 + ], + "MapName": "TheSlavePens", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "Cenarion Expedition", + "ZoneName": [ + "The Slave Pens", + 3717 + ] + }, + "CFRTheSteamvault": { + "1": [ + { + "1": "The Steamvault", + "SubZone": true + }, + { + "1": "Hydromancer Thespia", + "cords": [ + 54, + 14 + ], + "pinType": "dungeonskull" + }, + [ + "Main Chambers Access Panel" + ], + { + "1": "Arcane Container", + "cords": [ + 55, + 23 + ] + }, + [ + "Second Fragment Guardian" + ], + { + "1": "Mekgineer Steamrigger", + "cords": [ + 34, + 82 + ], + "pinType": "dungeonskull" + }, + [ + "Main Chambers Access Panel" + ], + { + "1": "Warlord Kalithresh", + "cords": [ + 76, + 44 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SV", + "LevelRange": "69-70", + "Location": [ + "Zangarmarsh", + 3521 + ], + "MapName": "TheSteamvault", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "Cenarion Expedition", + "ZoneName": [ + "The Steamvault", + 3715 + ] + }, + "CFRTheUnderbog": { + "1": [ + { + "1": "The Steamvault", + "SubZone": true + }, + { + "1": "Hungarfen", + "cords": [ + 70, + 90 + ], + "pinType": "dungeonskull" + }, + [ + "The Underspore" + ], + { + "1": "Ghaz'an", + "cords": [ + 79, + 29 + ], + "pinType": "dungeonskull" + }, + { + "1": "Earthbinder Rayge", + "cords": [ + 69, + 27 + ] + }, + { + "1": "Swamplord Musel'ek", + "cords": [ + 42, + 25 + ], + "pinType": "dungeonskull" + }, + [ + "Claw " + ], + { + "1": "The Black Stalker", + "cords": [ + 25, + 46 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "UB", + "LevelRange": "62-70", + "Location": [ + "Zangarmarsh", + 3521 + ], + "MapName": "TheUnderbog", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": "Cenarion Expedition", + "ZoneName": [ + "The Underbog", + 3716 + ] + }, + "CoTBlackMorass": { + "1": [ + { + "1": "The Black Morass", + "SubZone": true + }, + { + "1": "Sa'at ", + "cords": [ + 51, + 18 + ] + }, + { + "1": "Portal (Spawn Point)", + "cords": [ + 51, + 52 + ], + "pinType": "dungeonskull" + }, + { + "1": "Portal (Spawn Point)", + "cords": [ + 60, + 62 + ], + "pinType": "dungeonskull" + }, + { + "1": "Portal (Spawn Point)", + "cords": [ + 46, + 61 + ], + "pinType": "dungeonskull" + }, + { + "1": "Portal (Spawn Point)", + "cords": [ + 59, + 70 + ], + "pinType": "dungeonskull" + }, + [ + "Wave 6: Chrono Lord Deja" + ], + [ + "Wave 12: Temporus" + ], + [ + "Wave 18: Aeonus" + ], + { + "1": "The Dark Portal", + "cords": [ + 48, + 74 + ] + }, + [ + "Medivh" + ] + ], + "Acronym": "CoT2", + "LevelRange": "68-70", + "Location": [ + "Tanaris", + 440 + ], + "MapName": "CoTTheBlackMorass", + "MinLevel": "66", + "PlayerLimit": "5", + "Reputation": "Keepers of Time", + "ZoneName": [ + "The Black Morass", + 2366 + ] + }, + "CoTHyjal": { + "1": [ + { + "1": "Battle for Mount Hyjal", + "SubZone": true + }, + { + "1": "Alliance Base", + "cords": [ + 10.6, + 63.7 + ] + }, + [ + "Lady Jaina Proudmoore" + ], + { + "1": "Rage Winterchill", + "cords": [ + 9, + 70 + ], + "pinType": "dungeonskull" + }, + [ + "Anetheron" + ], + { + "1": "Horde Encampment", + "cords": [ + 48, + 41.7 + ] + }, + [ + "Thrall " + ], + { + "1": "Night Elf Village", + "cords": [ + 74.3, + 57.7 + ] + }, + [ + "Tyrande Whisperwind\n" + ], + { + "1": "Kaz'rogal", + "cords": [ + 43, + 36 + ], + "pinType": "dungeonskull" + }, + [ + "Azgalor" + ], + { + "1": "Archimonde", + "cords": [ + 77, + 28 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "CoT3", + "LevelRange": "70", + "Location": [ + "Tanaris", + 440 + ], + "MapName": "CoTMountHyjal", + "MinLevel": "70", + "PlayerLimit": "10-25", + "Reputation": "The Scale of the Sands", + "ZoneName": [ + "Hyjal Summit", + 3606 + ] + }, + "CoTOldHillsbrad": { + "1": [ + { + "1": "Old Hillsbrad Foothills", + "SubZone": true + }, + { + "1": "Lieutenant Drake", + "cords": [ + 76, + 66 + ], + "pinType": "dungeonskull" + }, + { + "1": "Thrall (Lower)", + "cords": [ + 74, + 59 + ] + }, + { + "1": "Captain Skarloc", + "cords": [ + 69, + 70 + ], + "pinType": "dungeonskull" + }, + [ + "Thrall (Second Stop)" + ], + { + "1": "Thrall (Third Stop)", + "cords": [ + 53, + 44 + ] + }, + { + "1": "Epoch Hunter", + "cords": [ + 51, + 32 + ], + "pinType": "dungeonskull" + }, + [ + "Thrall (Fourth Stop, Upper)" + ], + [ + "Taretha (Upper)" + ], + { + "1": "Jonathan Revah", + "cords": [ + 41, + 53 + ] + }, + [ + "Jerry Carter" + ] + ], + "Acronym": "CoT1", + "LevelRange": "66-70", + "Location": [ + "Tanaris", + 440 + ], + "MapName": "CoTHillsbradFoothills", + "MinLevel": "66", + "PlayerLimit": "5", + "Reputation": "Keepers of Time", + "ZoneName": [ + "Old Hillsbrad Foothills", + 2367 + ] + }, + "GruulsLair": { + "1": [ + { + "1": "Gruul's Lair", + "SubZone": true + }, + { + "1": "High King Maulgar ", + "cords": [ + 55, + 57 + ], + "pinType": "dungeonskull" + }, + [ + "Kiggler the Crazed (Shaman)" + ], + [ + "Blindeye the Seer (Priest)" + ], + [ + "Olm the Summoner (Warlock)" + ], + [ + "Krosh Firehand (Mage)" + ], + { + "1": "Gruul the Dragonkiller", + "cords": [ + 20, + 29 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "GL", + "LevelRange": "70", + "Location": [ + "Blade's Edge Mountains", + 3522 + ], + "MapName": "GruulsLair", + "MinLevel": "65", + "PlayerLimit": "10-25", + "ZoneName": [ + "Gruul's Lair", + 3923 + ] + }, + "HCBloodFurnace": { + "1": [ + { + "1": "The Blood Furnace", + "SubZone": true + }, + { + "1": "The Maker", + "cords": [ + 39, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Broggok", + "cords": [ + 42, + 22 + ], + "pinType": "dungeonskull" + }, + { + "1": "Keli'dan the Breaker", + "cords": [ + 58, + 41 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "BF", + "LevelRange": "60-68", + "Location": [ + "Hellfire Peninsula", + 3483 + ], + "MapName": "TheBloodFurnace", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": { + "Alliance": "Honor Hold", + "Horde": "Thrallmar" + }, + "ZoneName": [ + "The Blood Furnace", + 3713 + ] + }, + "HCHellfireRamparts": { + "1": [ + { + "1": "Hellfire Ramparts", + "SubZone": true + }, + { + "1": "Watchkeeper Gargolmar", + "cords": [ + 73, + 32 + ], + "pinType": "dungeonskull" + }, + { + "1": "Omor the Unscarred", + "cords": [ + 40, + 20 + ], + "pinType": "dungeonskull" + }, + { + "1": "Vazruden", + "cords": [ + 36, + 82 + ], + "pinType": "dungeonskull" + }, + [ + "Nazan " + ], + { + "1": "Reinforced Fel Iron Chest", + "cords": [ + 33, + 87 + ] + } + ], + "Acronym": "Ramp", + "LevelRange": "59-67", + "Location": [ + "Hellfire Peninsula", + 3483 + ], + "MapName": "HellfireRamparts", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": { + "Alliance": "Honor Hold", + "Horde": "Thrallmar" + }, + "ZoneName": [ + "Hellfire Ramparts", + 3562 + ] + }, + "HCMagtheridonsLair": { + "1": [ + { + "1": "Magtheridon's Lair", + "SubZone": true + }, + { + "1": "Magtheridon", + "cords": [ + 69, + 74 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Mag", + "LevelRange": "70", + "Location": [ + "Hellfire Peninsula", + 3483 + ], + "MapName": "Magtheridonslair", + "MinLevel": "65", + "PlayerLimit": "10-25", + "ZoneName": [ + "Magtheridon's Lair", + 3836 + ] + }, + "HCTheShatteredHalls": { + "1": [ + { + "1": "The Shattered Halls", + "SubZone": true + }, + { + "1": "Randy Whizzlesprocket (Alliance, Heroic)", + "cords": [ + 35, + 71 + ], + "pinType": "dungeonskull" + }, + [ + "Drisella (Horde, Heroic)" + ], + { + "1": "Grand Warlock Nethekurse", + "cords": [ + 33, + 62 + ], + "pinType": "dungeonskull" + }, + { + "1": "Blood Guard Porung (Heroic)", + "cords": [ + 30, + 14 + ], + "pinType": "dungeonskull" + }, + { + "1": "Warbringer O'mrogg", + "cords": [ + 54, + 35 + ], + "pinType": "dungeonskull" + }, + { + "1": "Warchief Kargath Bladefist", + "cords": [ + 68, + 50 + ], + "pinType": "dungeonskull" + }, + [ + "Shattered Hand Executioner (Heroic)" + ], + [ + "Private Jacint (Alliance, Heroic)" + ], + [ + "Rifleman Brownbeard (Alliance, Heroic)" + ], + [ + "Captain Alina (Alliance, Heroic)" + ], + [ + "Scout Orgarr (Horde, Heroic)" + ], + [ + "Korag Proudmane (Horde, Heroic)" + ], + [ + "Captain Boneshatter (Horde, Heroic)" + ] + ], + "Acronym": "SH", + "LevelRange": "69-70", + "Location": [ + "Hellfire Peninsula", + 3483 + ], + "MapName": "TheShatteredHalls", + "MinLevel": "55", + "PlayerLimit": "5", + "Reputation": { + "Alliance": "Honor Hold", + "Horde": "Thrallmar" + }, + "ZoneName": [ + "The Shattered Halls", + 3714 + ] + }, + "Karazhan": { + "1": [ + { + "1": "Servant's Quarters", + "SubZone": true + }, + { + "1": "Hastings ", + "cords": [ + 62, + 67 + ] + }, + { + "1": "Random Servant's Quarter Boss", + "cords": [ + 67, + 30 + ], + "pinType": "dungeonskull" + }, + [ + "Hyakiss the Lurker (Rare, Random)" + ], + [ + "Rokad the Ravager (Rare, Random)" + ], + [ + "Shadikith the Glider (Rare, Random)" + ], + { + "1": "Berthold ", + "cords": [ + 53, + 64 + ] + }, + { + "1": "Calliard ", + "cords": [ + 48, + 89 + ] + }, + { + "1": "Attumen the Huntsman", + "cords": [ + 46, + 83 + ], + "pinType": "dungeonskull" + }, + [ + "Midnight" + ], + { + "1": "Koren ", + "cords": [ + 46, + 74 + ] + } + ], + "10": [ + { + "1": "Guardian's Library", + "SubZone": true + }, + { + "1": "Shade of Aran", + "cords": [ + 73, + 27 + ], + "pinType": "dungeonskull" + } + ], + "11": [ + { + "1": "The Repository", + "SubZone": true + }, + { + "1": "Terestian Illhoof", + "cords": [ + 53, + 69 + ], + "pinType": "dungeonskull" + }, + [ + "Kil'rek (Imp)" + ] + ], + "12": [ + { + "1": "Upper Library", + "SubZone": true + }, + { + "1": "Ythyar (Repair, Rewards)", + "cords": [ + 46, + 26 + ] + } + ], + "13": [ + { + "1": "The Celestial Watch", + "SubZone": true + }, + { + "1": "Netherspite", + "cords": [ + 36, + 41 + ], + "pinType": "dungeonskull" + } + ], + "14": [ + { + "1": "Gamesman's Hall", + "SubZone": true + }, + { + "1": "Echo of Medivh", + "cords": [ + 23, + 79 + ] + }, + { + "1": "Dust Covered Chest (Chess Event)", + "cords": [ + 37, + 62 + ], + "pinType": "dungeonskull" + } + ], + "15": [ + { + "1": "Medivh's Chambers", + "SubZone": true + } + ], + "16": [ + { + "1": "The Power Station", + "SubZone": true + } + ], + "17": [ + { + "1": "Netherspace", + "SubZone": true + }, + { + "1": "Prince Malchezaar", + "cords": [ + 60, + 38.5 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Upper Livery Stables", + "SubZone": true + }, + { + "1": "Bennett ", + "cords": [ + 40, + 63 + ] + } + ], + "3": [ + { + "1": "The Banquet Hall", + "SubZone": true + }, + { + "1": "Moroes ", + "cords": [ + 26, + 64 + ], + "pinType": "dungeonskull" + }, + [ + "Baroness Dorothea Millstipe (Random, Shadow Priest)" + ], + [ + "Lady Catriona Von'Indi (Random, Holy Priest)" + ], + [ + "Lady Keira Berrybuck (Random, Holy Paladin)" + ], + [ + "Baron Rafe Dreuger (Random, Retribution Paladin)" + ], + [ + "Lord Robin Daris (Random, Arms Warrior)" + ], + [ + "Lord Crispin Ference (Random, Protection Warrior)" + ] + ], + "4": [ + { + "1": "The Guest Chambers", + "SubZone": true + }, + { + "1": "Ebonlocke ", + "cords": [ + 56, + 63 + ] + }, + { + "1": "Keanna's Log", + "cords": [ + 70, + 21 + ] + }, + { + "1": "Maiden of Virtue", + "cords": [ + 84, + 48 + ], + "pinType": "dungeonskull" + }, + { + "1": "Sebastian ", + "cords": [ + 26, + 49 + ] + }, + { + "1": "Barnes ", + "cords": [ + 20, + 27 + ] + }, + { + "1": "The Opera Event", + "cords": [ + 18, + 32 + ], + "pinType": "dungeonskull" + }, + [ + "self.Colors.RED Riding Hood (Random)" + ], + [ + "The Big Bad Wolf" + ], + [ + "Wizard of Oz (Random)" + ], + [ + "Dorothee" + ], + [ + "Tito" + ], + [ + "Strawman" + ], + [ + "Tinhead" + ], + [ + "Roar" + ], + [ + "The Crone" + ], + [ + "Romulo and Julianne (Random)" + ], + [ + "Romulo" + ], + [ + "Julianne" + ] + ], + "5": [ + { + "1": "Opera Hall Balcony", + "SubZone": true + } + ], + "6": [ + { + "1": "Master's Terrace", + "SubZone": true + }, + { + "1": "Nightbane (Summon)", + "cords": [ + 48, + 91 + ], + "pinType": "dungeonskull" + } + ], + "7": [ + { + "1": "Lower Broken Stair", + "SubZone": true + } + ], + "8": [ + { + "1": "Upper Broken Stair", + "SubZone": true + } + ], + "9": [ + { + "1": "The Menagerie", + "SubZone": true + }, + { + "1": "The Curator", + "cords": [ + 50, + 36 + ], + "pinType": "dungeonskull" + }, + { + "1": "Wravien ", + "cords": [ + 39, + 45 + ] + }, + { + "1": "Gradav ", + "cords": [ + 30, + 48 + ] + }, + { + "1": "Kamsis ", + "cords": [ + 30, + 60 + ] + } + ], + "Acronym": "Kara", + "LevelRange": "70", + "Location": [ + "Deadwind Pass", + 41 + ], + "MapName": "Karazhan", + "MinLevel": "68", + "PlayerLimit": "5", + "Reputation": "The Violet Eye", + "ZoneName": [ + "Karazhan [A] (Start)", + 3457 + ] + }, + "MagistersTerrace": { + "1": [ + { + "1": "Magisters' Terrace", + "SubZone": true + }, + { + "1": "Priestess Delrissa (Lower)", + "cords": [ + 40, + 56 + ], + "pinType": "dungeonskull" + }, + [ + "Apoko (Lower, Random, Shaman)" + ], + [ + "Eramas Brightblaze (Lower, Random, Monk)" + ], + [ + "Ellrys Duskhallow (Lower, Random, Warlock)" + ], + [ + "Fizzle (Lower, Random)" + ], + [ + "Garaxxas (Lower, Random, Hunter)" + ], + [ + "Sliver (Lower, Random)" + ], + [ + "Kagani Nightstrike (Lower, Random, Rogue)" + ], + [ + "Warlord Salaris (Lower, Random, Warrior)" + ], + [ + "Yazzai (Lower, Random, Mage)" + ], + [ + "Zelfan (Lower, Random, Engineer)" + ], + { + "1": "Kael'thas Sunstrider ", + "cords": [ + 9, + 50 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Magisters' Terrace", + "SubZone": true + }, + { + "1": "Selin Fireheart", + "cords": [ + 43, + 23 + ], + "pinType": "dungeonskull" + }, + [ + "Fel Crystals" + ], + { + "1": "Tyrith", + "cords": [ + 63, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Vexallus", + "cords": [ + 84, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Scrying Orb", + "cords": [ + 95, + 27 + ] + }, + [ + "Kalecgos" + ] + ], + "Acronym": "MaT", + "LevelRange": "70", + "Location": [ + "Isle of Quel'Danas", + 4080 + ], + "MapName": "MagistersTerrace", + "MinLevel": "68", + "PlayerLimit": "5", + "Reputation": "Shattered Sun Offensive", + "ZoneName": [ + "Magisters' Terrace", + 4095 + ] + }, + "SunwellPlateau": { + "1": [ + { + "1": "Sunwell Plateau", + "SubZone": true + }, + { + "1": "Eredar Twins (Lower)", + "cords": [ + 46, + 24 + ], + "pinType": "dungeonskull" + }, + [ + "Grand Warlock Alythess (Lower)" + ], + [ + "Lady Sacrolash (Lower)" + ], + [ + "M'uru (Upper)" + ], + [ + "Entropius (Upper)" + ], + { + "1": "Kil'jaeden ", + "cords": [ + 46, + 64 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Shrine of the Eclipse", + "SubZone": true + }, + { + "1": "Kalecgos", + "cords": [ + 30, + 51 + ], + "pinType": "dungeonskull" + }, + [ + "Sathrovarr the Corruptor" + ], + { + "1": "Madrigosa", + "cords": [ + 67, + 87 + ], + "pinType": "dungeonskull" + }, + [ + "Brutallus" + ], + [ + "Felmyst" + ], + { + "1": "Kil'jaeden ", + "cords": [ + 64, + 33 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SuP", + "LevelRange": "70", + "Location": [ + "Isle of Quel'Danas", + 4080 + ], + "MapName": "SunwellPlateau", + "MinLevel": "70", + "PlayerLimit": "10-25", + "ZoneName": [ + "Sunwell Plateau", + 4075 + ] + }, + "TempestKeepArcatraz": { + "1": [ + { + "1": "Stasis Block: Trion", + "SubZone": true + }, + { + "1": "Zereketh the Unbound", + "cords": [ + 60, + 25 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Stasis Block: Maximus", + "SubZone": true + }, + { + "1": "Arcane Container", + "cords": [ + 59, + 27 + ] + }, + [ + "Third Fragment Guardian" + ], + { + "1": "Dalliah the Doomsayer", + "cords": [ + 37, + 79 + ], + "pinType": "dungeonskull" + }, + { + "1": "Wrath-Scryer Soccothrates", + "cords": [ + 21, + 79 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Containment Core", + "SubZone": true + }, + { + "1": "Udalo", + "cords": [ + 29, + 28 + ], + "pinType": "dungeonskull" + }, + { + "1": "Harbinger Skyriss", + "cords": [ + 62, + 30 + ], + "pinType": "dungeonskull" + }, + [ + "Warden Mellichar" + ], + [ + "Millhouse Manastorm" + ] + ], + "Acronym": "Arca", + "LevelRange": "69-70", + "Location": [ + "Netherstorm", + 3523 + ], + "MapName": "TheArcatraz", + "MinLevel": "68", + "PlayerLimit": "5", + "Reputation": "The Sha'tar", + "ZoneName": [ + "The Arcatraz", + 3846 + ] + }, + "TempestKeepBotanica": { + "1": [ + { + "1": "The Botanica", + "SubZone": true + }, + { + "1": "Commander Sarannis", + "cords": [ + 45, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "High Botanist Freywinn", + "cords": [ + 24, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "Thorngrin the Tender", + "cords": [ + 8, + 48 + ], + "pinType": "dungeonskull" + }, + { + "1": "Laj", + "cords": [ + 34, + 86 + ], + "pinType": "dungeonskull" + }, + { + "1": "Warp Splinter", + "cords": [ + 34, + 36 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Bota", + "LevelRange": "69-70", + "Location": [ + "Netherstorm", + 3523 + ], + "MapName": "TheBotanica", + "MinLevel": "68", + "PlayerLimit": "5", + "Reputation": "The Sha'tar", + "ZoneName": [ + "The Botanica", + 3847 + ] + }, + "TempestKeepMechanar": { + "1": [ + { + "1": "The Mechanar", + "SubZone": true + }, + { + "1": "Gatewatcher Gyro-Kill", + "cords": [ + 47, + 58 + ], + "pinType": "dungeonskull" + }, + { + "1": "Gatewatcher Iron-Hand", + "cords": [ + 61, + 53 + ], + "pinType": "dungeonskull" + }, + [ + "Cache of the Legion" + ], + { + "1": "Mechano-Lord Capacitus", + "cords": [ + 52, + 32 + ], + "pinType": "dungeonskull" + }, + [ + "Overcharged Manacell" + ] + ], + "2": [ + { + "1": "Calculation Chamber", + "SubZone": true + }, + { + "1": "Nethermancer Sepethrea", + "cords": [ + 48, + 20 + ], + "pinType": "dungeonskull" + }, + { + "1": "Pathaleon the Calculator", + "cords": [ + 28, + 62 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Mech", + "LevelRange": "68-70", + "Location": [ + "Netherstorm", + 3523 + ], + "MapName": "TheMechanar", + "MinLevel": "68", + "PlayerLimit": "5", + "Reputation": "The Sha'tar", + "ZoneName": [ + "The Mechanar", + 3849 + ] + }, + "TempestKeepTheEye": { + "1": [ + { + "1": "The Eye", + "SubZone": true + }, + { + "1": "Al'ar ", + "cords": [ + 51, + 60 + ], + "pinType": "dungeonskull" + }, + { + "1": "Void Reaver", + "cords": [ + 26, + 49 + ], + "pinType": "dungeonskull" + }, + { + "1": "High Astromancer Solarian", + "cords": [ + 74, + 48 + ], + "pinType": "dungeonskull" + }, + { + "1": "Kael'thas Sunstrider ", + "cords": [ + 50, + 15 + ], + "pinType": "dungeonskull" + }, + [ + "Thaladred the Darkener (Warrior)" + ], + [ + "Master Engineer Telonicus (Hunter)" + ], + [ + "Grand Astromancer Capernian (Mage)" + ], + [ + "Lord Sanguinar (Paladin)" + ] + ], + "Acronym": "Eye", + "LevelRange": "70", + "Location": [ + "Netherstorm", + 3523 + ], + "MapName": "TempestKeep", + "MinLevel": "70", + "PlayerLimit": "10-25", + "Reputation": "The Sha'tar", + "ZoneName": [ + "The Eye", + 3845 + ] + }, + "ZulAman": { + "1": [ + { + "1": "Zul'Aman", + "SubZone": true + }, + { + "1": "Harrison Jones", + "cords": [ + 12, + 53 + ] + }, + { + "1": "Nalorakk ", + "cords": [ + 41, + 74 + ], + "pinType": "dungeonskull" + }, + [ + "Tanzar" + ], + [ + "The Map of Zul'Aman" + ], + { + "1": "Akil'Zon ", + "cords": [ + 35, + 23 + ], + "pinType": "dungeonskull" + }, + [ + "Harkor" + ], + { + "1": "Jan'Alai ", + "cords": [ + 56, + 71 + ], + "pinType": "dungeonskull" + }, + [ + "Kraz" + ], + { + "1": "Halazzi ", + "cords": [ + 58, + 24 + ], + "pinType": "dungeonskull" + }, + [ + "Ashli" + ], + { + "1": "Zungam", + "cords": [ + 67.4, + 37.6 + ], + "pinType": "questturnin" + }, + { + "1": "Hex Lord Malacrass", + "cords": [ + 72, + 54 + ], + "pinType": "dungeonskull" + }, + [ + "Thurg (Random)" + ], + [ + "Gazakroth (Random)" + ], + [ + "Lord Raadan (Random)" + ], + [ + "Darkheart (Random)" + ], + [ + "Alyson Antille (Random)" + ], + [ + "Slither (Random)" + ], + [ + "Fenstalker (Random)" + ], + [ + "Koragg (Random)" + ], + { + "1": "Zul'jin", + "cords": [ + 91, + 54 + ], + "pinType": "dungeonskull" + }, + { + "1": "Forest Frogs", + "cords": [ + 45, + 56 + ] + }, + [ + "Kyren " + ], + [ + "Gunter " + ], + [ + "Adarrah" + ], + [ + "Brennan" + ], + [ + "Darwen" + ], + [ + "Deez" + ], + [ + "Galathryn" + ], + [ + "Mitzi" + ], + [ + "Mannuth" + ] + ], + "Acronym": "ZA", + "LevelRange": "70", + "Location": [ + "Ghostlands", + 3433 + ], + "MapName": "ZulAman", + "MinLevel": "68", + "PlayerLimit": "5", + "ZoneName": [ + "Zul'Aman", + 3805 + ] + } + }, + "OriginalWoW": { + "BlackfathomDeeps": { + "1": [ + { + "1": "The Pool of Ask'ar", + "SubZone": true + }, + { + "1": "Ghamoo-ra", + "cords": [ + 32.9, + 59.9 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lorgalis Manuscript", + "cords": [ + 33, + 41 + ], + "pinType": "questturnin" + }, + { + "1": "Lady Sarevess", + "cords": [ + 12, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Argent Guard Thaelrid ", + "cords": [ + 21, + 74 + ] + }, + { + "1": "Gelihast", + "cords": [ + 53, + 57 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Moonshrine Sanctum", + "SubZone": true + }, + { + "1": "Lorgus Jett (Varies)", + "cords": [ + 46, + 66.5 + ] + }, + [ + "Fathom Stone" + ], + { + "1": "Baron Aquanis", + "pinType": "dungeonskull" + }, + { + "1": "Old Serra'kis", + "cords": [ + 53.4, + 70 + ], + "pinType": "dungeonskull" + }, + { + "1": "Twilight Lord Kelris", + "cords": [ + 51, + 81 + ], + "pinType": "dungeonskull" + }, + { + "1": "Aku'mai", + "cords": [ + 81.7, + 84.5 + ], + "pinType": "dungeonskull" + }, + [ + "Morridune" + ], + [ + "Altar of the Deeps" + ] + ], + "3": [ + { + "1": "The Forgotten Pool", + "SubZone": true + }, + { + "1": "Old Serra'kis", + "cords": [ + 59.4, + 31.4 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "BFD", + "LevelRange": "20-28", + "Location": [ + "Ashenvale", + 331 + ], + "MapName": "BlackFathomDeeps", + "MinLevel": "19", + "PlayerLimit": "5", + "ZoneName": [ + "Blackfathom Deeps", + 719 + ] + }, + "BlackrockDepths": { + "1": [ + { + "1": "Detention Block", + "SubZone": true + }, + { + "1": "Lord Roccor", + "cords": [ + 57, + 65 + ], + "pinType": "dungeonskull" + }, + { + "1": "Kharan Mighthammer", + "cords": [ + 52, + 82 + ] + }, + { + "1": "Commander Gor'shak ", + "cords": [ + 54, + 87 + ], + "pinType": "questturnin" + }, + { + "1": "Marshal Windsor", + "cords": [ + 53, + 94 + ], + "pinType": "questturnin" + }, + { + "1": "High Interrogator Gerstahn ", + "cords": [ + 48, + 91 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ring of Law", + "cords": [ + 51, + 61 + ], + "pinType": "dungeonskull" + }, + [ + "Anub'shiah (Random)" + ], + [ + "Eviscerator (Random)" + ], + [ + "Gorosh the Dervish (Random)" + ], + [ + "Grizzle (Random)" + ], + [ + "Hedrum the Creeper (Random)" + ], + [ + "Ok'thor the Breaker (Random)" + ], + [ + "Theldren (Summon)" + ], + [ + "Lefty" + ], + [ + "Malgen Longspear" + ], + [ + "Gnashjaw " + ], + [ + "Rotfang" + ], + [ + "Va'jashni" + ], + [ + "Houndmaster Grebmar (Lower)" + ], + [ + "Elder Morndeep (Lunar Festival)" + ], + [ + "High Justice Grimstone" + ], + { + "1": "Pyromancer Loregrain", + "cords": [ + 57, + 69 + ], + "pinType": "dungeonskull" + }, + [ + "Monument of Franclorn Forgewright" + ], + { + "1": "Fineous Darkvire ", + "cords": [ + 64, + 21 + ] + }, + { + "1": "Lord Incendius", + "cords": [ + 56, + 31 + ], + "pinType": "dungeonskull" + }, + [ + "The Black Anvil" + ], + { + "1": "Bael'Gar", + "cords": [ + 26, + 53 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Shadowforge City", + "SubZone": true + }, + { + "1": "The Vault", + "cords": [ + 60, + 66 + ], + "pinType": "dungeonskull" + }, + [ + "Warder Stilgiss" + ], + [ + "Verek" + ], + [ + "Watchman Doomgrip" + ], + { + "1": "Ring of Law", + "cords": [ + 51, + 89 + ], + "pinType": "dungeonskull" + }, + { + "1": "Shadowforge Lock", + "cords": [ + 42, + 91 + ] + }, + { + "1": "General Angerforge", + "cords": [ + 36, + 83 + ], + "pinType": "dungeonskull" + }, + { + "1": "Golem Lord Argelmach", + "cords": [ + 36, + 65 + ], + "pinType": "dungeonskull" + }, + [ + "Field Repair Bot 74A" + ], + [ + "Blacksmithing Plans" + ], + { + "1": "The Grim Guzzler", + "cords": [ + 50, + 63 + ], + "pinType": "dungeonskull" + }, + [ + "Hurley Blackbreath" + ], + [ + "Lokhtos Darkbargainer " + ], + [ + "Mistress Nagmara" + ], + [ + "Phalanx" + ], + [ + "Plugger Spazzring" + ], + [ + "Private Rocknot" + ], + [ + "Ribbly Screwspigot" + ], + [ + "Coren Direbrew (Brewfest)" + ], + [ + "Griz Gutshank " + ], + { + "1": "Ambassador Flamelash", + "cords": [ + 55, + 49 + ], + "pinType": "dungeonskull" + }, + { + "1": "Panzor the Invincible (Rare)", + "cords": [ + 50, + 35 + ], + "pinType": "dungeonskull" + }, + [ + "Blacksmithing Plans" + ], + { + "1": "Summoner's Tomb", + "cords": [ + 54, + 25 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Lyceum", + "cords": [ + 67, + 12 + ], + "pinType": "dungeonskull" + }, + { + "1": "Magmus", + "cords": [ + 81, + 12 + ], + "pinType": "dungeonskull" + }, + { + "1": "Emperor Dagran Thaurissan", + "cords": [ + 91, + 12 + ], + "pinType": "dungeonskull" + }, + [ + "Princess Moira Bronzebeard " + ], + [ + "High Priestess of Thaurissan" + ], + { + "1": "The Black Forge", + "cords": [ + 63, + 25 + ] + }, + { + "1": "Molten Core", + "cords": [ + 68, + 36 + ], + "pinType": "questturnin" + }, + [ + "Core Fragment" + ] + ], + "Acronym": "BRD", + "LevelRange": "52-58", + "Location": [ + "Searing Gorge / Burning Steppes", + 51, + 46 + ], + "MapName": "BlackrockDepths", + "MinLevel": "10-25", + "PlayerLimit": "5", + "ZoneName": [ + "Blackrock Depths", + 1584 + ] + }, + "BlackrockSpire": { + "1": [ + { + "1": "Tazz'Alaor", + "SubZone": true + }, + { + "1": "War Master Voone", + "cords": [ + 52, + 54 + ], + "pinType": "dungeonskull" + }, + [ + "Mor Grayhoof (Summon)" + ], + [ + "Sixth Mosh'aru Tablet" + ], + [ + "Bijou's Belongings" + ], + [ + "Elder Stonefort (Lunar Festival)" + ], + { + "1": "Mother Smolderweb", + "cords": [ + 64, + 70 + ], + "pinType": "dungeonskull" + }, + [ + "Crystal Fang (Rare)" + ], + { + "1": "Burning Felguard (Rare, Summon)", + "cords": [ + 43, + 54 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Blackrock Spire (Lower)", + "Zone": true + }, + { + "1": "Skitterweb Tunnels", + "SubZone": true + }, + { + "1": "Shadow Hunter Vosh'gajin", + "cords": [ + 56, + 70 + ], + "pinType": "dungeonskull" + }, + [ + "Fifth Mosh'aru Tablet" + ] + ], + "3": [ + { + "1": "Blackrock Spire (Lower)", + "Zone": true + }, + { + "1": "Hordemar City", + "SubZone": true + }, + { + "1": "Vaelan (Upper)", + "cords": [ + 52, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Warosh (Wanders)", + "cords": [ + 61, + 43 + ], + "pinType": "dungeonskull" + }, + { + "1": "Bijou", + "cords": [ + 63, + 56 + ] + }, + { + "1": "Roughshod Pike", + "cords": [ + 67, + 58 + ] + }, + { + "1": "Spirestone Butcher (Rare)", + "cords": [ + 51, + 58 + ], + "pinType": "dungeonskull" + }, + { + "1": "Highlord Omokk", + "cords": [ + 39, + 60 + ], + "pinType": "dungeonskull" + }, + [ + "Spirestone Battle Lord (Rare)" + ], + [ + "Spirestone Lord Magus (Rare)" + ] + ], + "4": [ + { + "1": "Blackrock Spire (Lower)", + "Zone": true + }, + { + "1": "Hall of Blackhand", + "SubZone": true + }, + { + "1": "Human Remains (Lower)", + "cords": [ + 43, + 56 + ] + }, + [ + "Unfired Plate Gauntlets (Lower)" + ], + { + "1": "Urok's Tribute Pile", + "cords": [ + 46, + 55 + ], + "pinType": "dungeonskull" + }, + [ + "Urok Doomhowl (Summon)" + ], + { + "1": "Bannok Grimaxe (Rare)", + "cords": [ + 46, + 65 + ], + "pinType": "dungeonskull" + } + ], + "5": [ + { + "1": "Blackrock Spire (Lower)", + "Zone": true + }, + { + "1": "Dragonspire Hall", + "SubZone": true + }, + { + "1": "Quartermaster Zigris ", + "cords": [ + 55, + 85 + ], + "pinType": "dungeonskull" + }, + { + "1": "Halycon", + "cords": [ + 38, + 85 + ] + }, + [ + "Gizrul the Slavener" + ], + { + "1": "Ghok Bashguud (Rare)", + "cords": [ + 35, + 68 + ], + "pinType": "dungeonskull" + } + ], + "6": [ + { + "1": "Blackrock Spire (Upper)", + "Zone": true + }, + { + "1": "The Rookery", + "SubZone": true + }, + { + "1": "Pyroguard Emberseer", + "cords": [ + 31, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Solakar Flamewreath", + "cords": [ + 39, + 39 + ], + "pinType": "dungeonskull" + }, + [ + "Father Flame" + ], + { + "1": "Darkstone Tablet", + "cords": [ + 32, + 42 + ] + }, + [ + "Doomrigger's Coffer" + ], + { + "1": "Overlord Wyrmthalak", + "cords": [ + 58, + 64 + ], + "pinType": "dungeonskull" + } + ], + "7": [ + { + "1": "Blackrock Spire (Upper)", + "Zone": true + }, + { + "1": "Blackrock Stadium", + "SubZone": true + }, + { + "1": "Jed Runewatcher (Rare)", + "cords": [ + 38, + 24 + ], + "pinType": "dungeonskull" + }, + { + "1": "Goraluk Anvilcrack ", + "cords": [ + 33, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Warchief Rend Blackhand", + "cords": [ + 49, + 27 + ], + "pinType": "dungeonskull" + }, + [ + "Gyth " + ], + { + "1": "Awbee", + "cords": [ + 49, + 36 + ] + }, + { + "1": "The Beast", + "cords": [ + 64, + 32 + ], + "pinType": "dungeonskull" + }, + [ + "Lord Valthalak (Summon)" + ], + [ + "Finkle Einhorn" + ], + { + "1": "General Drakkisath", + "cords": [ + 36, + 50 + ], + "pinType": "dungeonskull" + }, + [ + "Drakkisath's Brand" + ], + { + "1": "Blackwing Lair", + "cords": [ + 55, + 22 + ] + } + ], + "Acronym": "LBRS", + "LevelRange": "54-60", + "Location": [ + "Searing Gorge / Burning Steppes", + 51, + 46 + ], + "MapName": "BlackrockSpire", + "MinLevel": "45", + "PlayerLimit": "5", + "ZoneName": [ + "Blackrock Spire", + 1583 + ] + }, + "BlackwingLair": { + "1": [ + { + "1": "Dragonmaw Garrison", + "SubZone": true + }, + { + "1": "Razorgore the Untamed", + "cords": [ + 33, + 28 + ], + "pinType": "dungeonskull" + }, + { + "1": "Vaelastrasz the Corrupt", + "cords": [ + 42, + 62 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Halls of Strife", + "SubZone": true + }, + { + "1": "Broodlord Lashlayer", + "cords": [ + 48, + 55 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Crimson Laboratories", + "SubZone": true + }, + { + "1": "Broodlord Lashlayer", + "cords": [ + 50, + 59 + ], + "pinType": "dungeonskull" + }, + { + "1": "Draconic for Dummies (Chapter VII)", + "cords": [ + 32, + 43 + ] + }, + { + "1": "Master Elemental Shaper Krixix", + "cords": [ + 40, + 41 + ] + } + ], + "4": [ + { + "1": "Nefarian's Lair", + "SubZone": true + }, + { + "1": "Firemaw", + "cords": [ + 40, + 38 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ebonroc", + "cords": [ + 34, + 35 + ], + "pinType": "dungeonskull" + }, + { + "1": "Flamegor", + "cords": [ + 40, + 47 + ], + "pinType": "dungeonskull" + }, + { + "1": "Chromaggus", + "cords": [ + 41, + 72 + ], + "pinType": "dungeonskull" + }, + { + "1": "Nefarian", + "cords": [ + 72, + 72 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "BWL", + "LevelRange": "60-70", + "Location": [ + "Searing Gorge / Burning Steppes", + 51, + 46 + ], + "MapName": "BlackwingLair", + "MinLevel": "60", + "PlayerLimit": "10-25", + "ZoneName": [ + "Blackwing Lair", + 2677 + ] + }, + "DireMaul": { + "1": [ + { + "1": "Gordok Commons (North)", + "SubZone": true + }, + [ + "Library Entrance" + ], + { + "1": "Guard Mol'dar", + "cords": [ + 69.9, + 76 + ], + "pinType": "dungeonskull" + }, + { + "1": "Stomper Kreeg ", + "cords": [ + 61, + 68 + ], + "pinType": "dungeonskull" + }, + { + "1": "Guard Fengus", + "cords": [ + 42.9, + 77.6 + ], + "pinType": "dungeonskull" + }, + [ + "Knot Thimblejack" + ], + { + "1": "Guard Slip'kik", + "cords": [ + 26.5, + 57.8 + ], + "pinType": "dungeonskull" + }, + { + "1": "Captain Kromcrush", + "cords": [ + 31.8, + 51.4 + ], + "pinType": "dungeonskull" + }, + { + "1": "King Gordok", + "cords": [ + 32, + 27.7 + ], + "pinType": "dungeonskull" + }, + { + "1": "Cho'Rush the Observer", + "cords": [ + 31.4, + 26.2 + ] + } + ], + "2": [ + { + "1": "Capital Gardens (West)", + "SubZone": true + }, + { + "1": "Pylons", + "cords": [ + 70, + 51 + ] + }, + { + "1": "Pylons", + "cords": [ + 40, + 22 + ] + }, + { + "1": "Pylons", + "cords": [ + 36, + 80 + ] + }, + { + "1": "Illyanna Ravenoak", + "cords": [ + 20, + 78 + ], + "pinType": "dungeonskull" + }, + { + "1": "Tendris Warpwood", + "cords": [ + 33.4, + 53.5 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Court of the Highborne (West)", + "SubZone": true + }, + [ + "Shen'dralar Ancient" + ], + { + "1": "Magister Kalendris", + "cords": [ + 32, + 45.3 + ], + "pinType": "dungeonskull" + } + ], + "4": [ + { + "1": "Prison of Immol'thar (West)", + "SubZone": true + }, + [ + "Ancient Equine Spirit" + ], + [ + "Ferra" + ], + { + "1": "Tsu'zee (Rare)", + "cords": [ + 68.3, + 23.9 + ], + "pinType": "dungeonskull" + }, + { + "1": "Immol'thar", + "cords": [ + 35.2, + 58.1 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lord Hel'nurath (Summon)", + "cords": [ + 14.9, + 55.7 + ], + "pinType": "dungeonskull" + }, + { + "1": "Prince Tortheldrin", + "cords": [ + 61.4, + 15.1 + ], + "pinType": "dungeonskull" + }, + [ + "Library" + ], + [ + "Falrin Treeshaper" + ], + [ + "Lorekeeper Lydros" + ], + [ + "Lorekeeper Javon" + ], + [ + "Lorekeeper Kildrath" + ], + [ + "Lorekeeper Mykos" + ], + [ + "Shen'dralar Provisioner" + ], + [ + "Skeletal Remains of Kariel Winthalus" + ], + { + "1": "Pylons", + "cords": [ + 44, + 35 + ] + }, + { + "1": "Pylons", + "cords": [ + 34, + 81 + ] + } + ], + "5": [ + { + "1": "Warpwood Quarter (East)", + "SubZone": true + }, + [ + "Zevrim Thornhoof" + ], + { + "1": "Hydrospawn", + "cords": [ + 41, + 49.2 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lethtendris", + "cords": [ + 44.5, + 46.6 + ], + "pinType": "dungeonskull" + }, + [ + "Pimgib" + ] + ], + "6": [ + { + "1": "The Shrine of Eldretharr (East)", + "SubZone": true + }, + { + "1": "Old Ironbark", + "cords": [ + 40, + 68 + ] + }, + { + "1": "Alzzin the Wildshaper", + "cords": [ + 57.3, + 28.9 + ], + "pinType": "dungeonskull" + }, + { + "1": "Isalien (Summon)", + "pinType": "dungeonskull" + } + ], + "Acronym": "DM", + "LevelRange": "57-60", + "Location": [ + "Feralas", + 357 + ], + "MapName": "DireMaul", + "MinLevel": "45", + "PlayerLimit": "5", + "ZoneName": [ + "Dire Maul", + 2557 + ] + }, + "Gnomeregan": { + "1": [ + { + "1": "The Hall of Gears", + "SubZone": true + }, + { + "1": "Blastmaster Emi Shortfuse", + "cords": [ + 77, + 68 + ], + "pinType": "dungeonskull" + }, + [ + "Grubbis" + ], + [ + "Chomper" + ], + { + "1": "Viscous Fallout", + "cords": [ + 50, + 50 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "The Dormitory", + "SubZone": true + }, + { + "1": "Clean Room", + "cords": [ + 64, + 64 + ] + }, + [ + "Tink Sprocketwhistle " + ], + [ + "The Sparklematic 5200" + ], + [ + "Mail Box" + ], + { + "1": "Kernobee", + "cords": [ + 72, + 68 + ] + }, + [ + "Alarm-a-bomb 2600" + ], + [ + "Matrix Punchograph 3005-B" + ], + { + "1": "Electrocutioner 6000", + "cords": [ + 25, + 69 + ], + "pinType": "dungeonskull" + }, + [ + "Matrix Punchograph 3005-C" + ] + ], + "3": [ + { + "1": "Launch Bay", + "SubZone": true + }, + { + "1": "Crowd Pummeler 9-60 (Upper)", + "cords": [ + 44, + 87 + ], + "pinType": "dungeonskull" + }, + [ + "Matrix Punchograph 3005-D" + ] + ], + "4": [ + { + "1": "Tinkers' Court", + "SubZone": true + }, + { + "1": "Dark Iron Ambassador", + "cords": [ + 29, + 48 + ] + }, + { + "1": "Mekgineer Thermaplugg", + "cords": [ + 32, + 30 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Gnome", + "LevelRange": "24-32", + "Location": [ + "Dun Morogh", + 1 + ], + "MapName": "Gnomeregan", + "MinLevel": "20", + "PlayerLimit": "5", + "ZoneName": [ + "Gnomeregan", + 721 + ] + }, + "Maraudon": { + "1": [ + { + "1": "Caverns of Maraudon", + "SubZone": true + }, + { + "1": "Veng ", + "cords": [ + 60, + 24 + ] + }, + { + "1": "Noxxion", + "cords": [ + 36, + 15 + ], + "pinType": "dungeonskull" + }, + { + "1": "Razorlash", + "cords": [ + 18, + 33 + ], + "pinType": "dungeonskull" + }, + { + "1": "Maraudos ", + "cords": [ + 53, + 74 + ] + }, + { + "1": "Lord Vyletongue", + "cords": [ + 37, + 72 + ], + "pinType": "dungeonskull" + }, + [ + "Meshlok the Harvester (Rare)" + ], + { + "1": "Celebras the Cursed", + "cords": [ + 10, + 65 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Zaetar's Grave", + "SubZone": true + }, + { + "1": "Celebras the Cursed", + "cords": [ + 25, + 15 + ], + "pinType": "dungeonskull" + }, + { + "1": "Landslide", + "cords": [ + 40.6, + 48.2 + ] + }, + { + "1": "Tinkerer Gizlock", + "cords": [ + 48.4, + 68.6 + ] + }, + { + "1": "Rotgrip", + "cords": [ + 33.3, + 77.1 + ], + "pinType": "dungeonskull" + }, + { + "1": "Princess Theradras", + "cords": [ + 24.2, + 80.4 + ], + "pinType": "dungeonskull" + }, + [ + "Elder Splitrock (Lunar Festival)" + ] + ], + "Acronym": "Mara", + "LevelRange": "42-51", + "Location": [ + "Desolace", + 405 + ], + "MapName": "Maraudon", + "MinLevel": "30", + "PlayerLimit": "5", + "ZoneName": [ + "Maraudon", + 2100 + ] + }, + "MoltenCore": { + "1": [ + { + "1": "Molten Core", + "SubZone": true + }, + { + "1": "Lucifron", + "cords": [ + 63, + 44 + ], + "pinType": "dungeonskull" + }, + { + "1": "Magmadar", + "cords": [ + 69, + 24 + ], + "pinType": "dungeonskull" + }, + { + "1": "Gehennas", + "cords": [ + 34, + 49 + ], + "pinType": "dungeonskull" + }, + { + "1": "Garr", + "cords": [ + 32, + 70 + ], + "pinType": "dungeonskull" + }, + { + "1": "Shazzrah", + "cords": [ + 52, + 77 + ], + "pinType": "dungeonskull" + }, + { + "1": "Baron Geddon", + "cords": [ + 55, + 84 + ] + }, + { + "1": "Golemagg the Incinerator", + "cords": [ + 69, + 59 + ], + "pinType": "dungeonskull" + }, + { + "1": "Sulfuron Harbinger", + "cords": [ + 83, + 83 + ], + "pinType": "dungeonskull" + }, + { + "1": "Majordomo Executus", + "cords": [ + 84, + 65 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ragnaros", + "cords": [ + 56, + 54 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "MC", + "LevelRange": "60-70", + "Location": [ + "Searing Gorge / Burning Steppes", + 51, + 46 + ], + "MapName": "MoltenCore", + "MinLevel": "50", + "PlayerLimit": "10-25", + "Reputation": "Hydraxian Waterlords", + "ZoneName": [ + "Molten Core", + 2717 + ] + }, + "Naxxramas60": { + "1": [ + { + "1": "The Construct Quarter", + "SubZone": true + }, + { + "1": "Patchwerk", + "cords": [ + 54, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Grobbulus", + "cords": [ + 62, + 53 + ], + "pinType": "dungeonskull" + }, + { + "1": "Gluth", + "cords": [ + 46, + 44 + ], + "pinType": "dungeonskull" + }, + { + "1": "Thaddius", + "cords": [ + 28, + 15 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "The Arachnid Quarter", + "SubZone": true + }, + { + "1": "Anub'Rekhan", + "cords": [ + 31, + 47 + ], + "pinType": "dungeonskull" + }, + { + "1": "Grand Widow Faerlina", + "cords": [ + 44, + 36 + ], + "pinType": "dungeonskull" + }, + { + "1": "Maexxna", + "cords": [ + 69, + 15 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "The Military Quarter", + "SubZone": true + }, + { + "1": "|cffcc6666Instructor Razuvious", + "cords": [ + 43, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "|cffcc6666Gothik the Harvester", + "cords": [ + 67, + 60 + ], + "pinType": "dungeonskull" + }, + { + "1": "|cffcc6666Four Horsemen Chest", + "cords": [ + 30, + 77 + ], + "pinType": "dungeonskull" + } + ], + "4": [ + { + "1": "The Plague Quarter", + "SubZone": true + }, + { + "1": "Noth the Plaguebringer", + "cords": [ + 35, + 57 + ], + "pinType": "dungeonskull" + }, + { + "1": "Heigan the Unclean", + "cords": [ + 50, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "Loatheb", + "cords": [ + 76, + 28 + ], + "pinType": "dungeonskull" + } + ], + "5": [ + { + "1": "Overview", + "SubZone": true + } + ], + "6": [ + { + "1": "Frostwyrm Lair", + "SubZone": true + }, + { + "1": "Sapphiron", + "cords": [ + 37, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "Kel'Thuzad", + "cords": [ + 57, + 68 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Nax60", + "LevelRange": "60+", + "Location": [ + "Eastern Plaguelands", + 65 + ], + "MapName": "Naxxramas", + "MinLevel": "60", + "PlayerLimit": "10-25", + "ZoneName": [ + "Naxxramas", + 3456 + ] + }, + "Onyxia60": { + "1": [ + { + "1": "Onyxia's Lair", + "SubZone": true + }, + { + "1": "Whelp Eggs", + "cords": [ + 63.2, + 32 + ] + }, + { + "1": "Whelp Eggs", + "cords": [ + 77, + 32 + ] + }, + { + "1": "Basalthane", + "cords": [ + 44.5, + 65 + ], + "pinType": "dungeonskull" + }, + { + "1": "Onyxia", + "cords": [ + 70, + 23 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Ony", + "LevelRange": "60-70", + "Location": [ + "Dustwallow Marsh" + ], + "MapName": "OnyxiasLair", + "MinLevel": "60", + "PlayerLimit": "10/25", + "ZoneName": [ + "Onyxia's Lair" + ] + }, + "RagefireChasm": { + "1": [ + { + "1": "Ragefire Chasm", + "SubZone": true + }, + [ + "Maur Grimtotem" + ], + { + "1": "Oggleflint ", + "cords": [ + 56.2, + 38.6 + ], + "pinType": "dungeonskull" + }, + { + "1": "Taragaman the Hungerer", + "cords": [ + 40.9, + 59 + ], + "pinType": "dungeonskull" + }, + { + "1": "Jergosh the Invoker", + "cords": [ + 38.9, + 82.9 + ], + "pinType": "dungeonskull" + }, + [ + "Zelemar the Wrathful (Summon)" + ], + { + "1": "Bazzalan", + "cords": [ + 42.4, + 85.4 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "RFC", + "LevelRange": "12-18", + "Location": [ + "Orgrimmar", + 1637 + ], + "MapName": "ragefire", + "MinLevel": "8", + "PlayerLimit": "5", + "ZoneName": [ + "Ragefire Chasm", + 2437 + ] + }, + "RazorfenDowns": { + "1": [ + { + "1": "Razorfen Downs", + "SubZone": true + }, + { + "1": "Tuten'kash", + "cords": [ + 67.1, + 41.2 + ], + "pinType": "dungeonskull" + }, + [ + "Henry Stern" + ], + [ + "Belnistrasz" + ], + [ + "Sah'rhee" + ], + { + "1": "Mordresh Fire Eye", + "cords": [ + 85.8, + 45.7 + ], + "pinType": "dungeonskull" + }, + { + "1": "Glutton", + "cords": [ + 38.5, + 45.2 + ], + "pinType": "dungeonskull" + }, + [ + "Ragglesnout (Rare, Varies)" + ], + { + "1": "Amnennar the Coldbringer", + "cords": [ + 45, + 59.1 + ], + "pinType": "dungeonskull" + }, + [ + "Plaguemaw the Rotting" + ] + ], + "Acronym": "RFD", + "LevelRange": "33-42", + "Location": [ + "The Barrens", + 17 + ], + "MapName": "RazorfenDowns", + "MinLevel": "25", + "PlayerLimit": "5", + "ZoneName": [ + "Razorfen Downs", + 722 + ] + }, + "RazorfenKraul": { + "1": [ + [ + "Entrance" + ], + { + "1": "Roogug", + "cords": [ + 65, + 48 + ], + "pinType": "dungeonskull" + }, + { + "1": "Aggem Thorncurse ", + "cords": [ + 81, + 53 + ], + "pinType": "dungeonskull" + }, + { + "1": "Death Speaker Jargba ", + "cords": [ + 88, + 41 + ], + "pinType": "dungeonskull" + }, + { + "1": "Overlord Ramtusk", + "cords": [ + 56, + 30 + ], + "pinType": "dungeonskull" + }, + [ + "Razorfen Spearhide" + ], + { + "1": "Agathelos the Raging", + "cords": [ + 9, + 68 + ], + "pinType": "dungeonskull" + }, + { + "1": "Blind Hunter (Rare)", + "cords": [ + 11, + 31 + ] + }, + { + "1": "Charlga Razorflank ", + "cords": [ + 22, + 31 + ], + "pinType": "dungeonskull" + }, + [ + "Willix the Importer" + ], + [ + "Heralath Fallowbrook" + ], + { + "1": "Earthcaller Halmgar (Rare)", + "cords": [ + 49, + 45 + ] + } + ], + "Acronym": "RFK", + "LevelRange": "23-33", + "Location": [ + "The Barrens", + 17 + ], + "MapName": "RazorfenKraul", + "MinLevel": "17", + "PlayerLimit": "5", + "ZoneName": [ + "Razorfen Kraul", + 491 + ] + }, + "ScarletMonastery": { + "1": [ + { + "1": "Graveyard", + "SubZone": true + }, + { + "1": "Interrogator Vishas", + "cords": [ + 72, + 60 + ], + "pinType": "dungeonskull" + }, + [ + "Vorrel Sengutz" + ], + { + "1": "Pumpkin Shrine (Hallow's End)", + "cords": [ + 36, + 63 + ] + }, + [ + "Headless Horseman (Summon)" + ], + { + "1": "Bloodmage Thalnos", + "cords": [ + 24, + 56 + ], + "pinType": "dungeonskull" + }, + [ + "Ironspine (Rare)" + ], + [ + "Azshir the Sleepless (Rare)" + ], + [ + "Fallen Champion (Rare)" + ] + ], + "2": [ + { + "1": "Library", + "SubZone": true + }, + { + "1": "Houndmaster Loksey", + "cords": [ + 30, + 85 + ], + "pinType": "dungeonskull" + }, + { + "1": "Arcanist Doan", + "cords": [ + 83, + 77 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Armory", + "SubZone": true + }, + { + "1": "Herod ", + "cords": [ + 79, + 11 + ], + "pinType": "dungeonskull" + } + ], + "4": [ + { + "1": "Cathedral", + "SubZone": true + }, + { + "1": "High Inquisitor Fairbanks", + "cords": [ + 56, + 26 + ], + "pinType": "dungeonskull" + }, + { + "1": "Scarlet Commander Mograine", + "cords": [ + 49, + 29 + ], + "pinType": "dungeonskull" + }, + { + "1": "High Inquisitor Whitemane", + "cords": [ + 48, + 17 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SM", + "LevelRange": "33-40", + "Location": [ + "Tirisfal Glades", + 85 + ], + "MapName": "ScarletMonastery", + "MinLevel": "20", + "PlayerLimit": "5", + "ZoneName": [ + "Scarlet Monastery", + 796 + ] + }, + "Scholomance": { + "1": [ + { + "1": "The Reliquary", + "SubZone": true + }, + { + "1": "Blood Steward of Kirtonos", + "cords": [ + 82, + 46 + ], + "pinType": "dungeonskull" + }, + [ + "Blood of Innocents (Kirtonos the Herald)" + ], + [ + "The Deed to Southshore" + ] + ], + "2": [ + { + "1": "Chamber of Summoning", + "SubZone": true + }, + { + "1": "Kirtonos the Herald (Summon)", + "cords": [ + 49, + 12 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Deed to Tarren Mill", + "cords": [ + 31, + 57 + ], + "pinType": "questturnin" + }, + { + "1": "Torch Lever", + "cords": [ + 49, + 55 + ] + }, + [ + "Secret Chest" + ], + { + "1": "Marduk Blackpool", + "cords": [ + 49, + 66 + ], + "pinType": "dungeonskull" + }, + [ + "Vectus" + ] + ], + "3": [ + { + "1": "The Headmaster's Study", + "SubZone": true + }, + { + "1": "Jandice Barov", + "cords": [ + 54, + 15 + ], + "pinType": "dungeonskull" + }, + { + "1": "Instructor Malicia", + "cords": [ + 73, + 79 + ], + "pinType": "dungeonskull" + }, + { + "1": "Doctor Theolen Krastinov ", + "cords": [ + 89, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lorekeeper Polkelt", + "cords": [ + 73, + 24 + ], + "pinType": "dungeonskull" + }, + { + "1": "Rattlegore", + "cords": [ + 33, + 65 + ], + "pinType": "dungeonskull" + }, + [ + "Death Knight Darkreaver (Summon)" + ], + { + "1": "Darkmaster Gandling", + "cords": [ + 73, + 47 + ], + "pinType": "dungeonskull" + } + ], + "4": [ + { + "1": "Barov Family Vault", + "SubZone": true + }, + { + "1": "Ras Frostwhisper", + "cords": [ + 41, + 82 + ], + "pinType": "dungeonskull" + }, + [ + "The Deed to Brill" + ], + [ + "Alchemy Lab" + ], + [ + "Kormok (Summon)" + ], + { + "1": "The Ravenian", + "cords": [ + 67, + 49 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lord Alexei Barov", + "cords": [ + 79, + 30 + ], + "pinType": "dungeonskull" + }, + [ + "The Deed to Caer Darrow" + ], + { + "1": "Lady Illucia Barov", + "cords": [ + 67, + 16 + ], + "pinType": "dungeonskull" + }, + { + "1": "Darkmaster Gandling", + "cords": [ + 67, + 30 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Scholo", + "LevelRange": "58-60", + "Location": [ + "Western Plaguelands", + 28 + ], + "MapName": "Scholomance", + "MinLevel": "45", + "PlayerLimit": "5", + "ZoneName": [ + "Scholomance", + 2057 + ] + }, + "ShadowfangKeep": { + "1": [ + { + "1": "The Courtyard", + "SubZone": true + }, + { + "1": "Rethilgore ", + "cords": [ + 67, + 73 + ], + "pinType": "dungeonskull" + }, + [ + "Sorcerer Ashcrombe" + ], + [ + "Deathstalker Adamant" + ], + [ + "Landen Stilwell" + ], + { + "1": "Investigator Fezzen Brasstacks (Love is in the Air)", + "cords": [ + 54, + 66 + ] + }, + { + "1": "Deathstalker Vincent", + "cords": [ + 57, + 60 + ] + }, + { + "1": "Apothecary Trio (Love is in the Air)", + "cords": [ + 39, + 51 + ] + }, + [ + "Apothecary Hummel " + ], + [ + "Apothecary Baxter " + ], + [ + "Apothecary Frye " + ], + { + "1": "Fel Steed", + "cords": [ + 35, + 58 + ] + }, + [ + "Jordan's Hammer" + ], + [ + "Crate of Ingots" + ], + { + "1": "Commander Springvale", + "cords": [ + 27, + 61 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Dining Hall", + "SubZone": true + }, + { + "1": "Razorclaw the Butcher", + "cords": [ + 48, + 28 + ], + "pinType": "dungeonskull" + }, + { + "1": "Baron Silverlaine", + "cords": [ + 30, + 77 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "The Vacant Den", + "SubZone": true + } + ], + "4": [ + { + "1": "Lower Observatory", + "SubZone": true + }, + { + "1": "Fenrus the Devourer", + "cords": [ + 53, + 54 + ], + "pinType": "dungeonskull" + }, + [ + "Arugal's Voidwalker" + ], + [ + "The Book of Ur" + ] + ], + "5": [ + { + "1": "Upper Observatory", + "SubZone": true + } + ], + "6": [ + { + "1": "Lord Godfrey's Chamber", + "SubZone": true + }, + { + "1": "Wolf Master Nandos", + "cords": [ + 57, + 62 + ], + "pinType": "dungeonskull" + }, + { + "1": "Archmage Arugal", + "cords": [ + 65, + 20 + ], + "pinType": "dungeonskull" + } + ], + "7": [ + { + "1": "The Wall Walk", + "SubZone": true + }, + { + "1": "Odo the Blindwatcher", + "cords": [ + 55, + 81 + ], + "pinType": "dungeonskull" + }, + { + "1": "Deathsworn Captain (Rare)", + "cords": [ + 59, + 53 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "SFK", + "LevelRange": "17-25", + "Location": [ + "Silverpine Forest", + 130 + ], + "MapName": "ShadowfangKeep", + "MinLevel": "10", + "PlayerLimit": "5", + "ZoneName": [ + "Shadowfang Keep", + 209 + ] + }, + "Stratholme": { + "1": [ + { + "1": "Crusader's Square", + "SubZone": true + }, + { + "1": "Crusaders' Square Postbox", + "cords": [ + 36, + 35 + ] + }, + { + "1": "Market Row Postbox", + "cords": [ + 69, + 28 + ] + }, + { + "1": "Festival Lane Postbox", + "cords": [ + 80, + 29 + ] + }, + { + "1": "King's Square Postbox", + "cords": [ + 64, + 53 + ] + }, + { + "1": "Fras Siabi's Postbox", + "cords": [ + 55, + 69 + ] + }, + [ + "3rd Box Opened: Postmaster Malown" + ], + { + "1": "Skul (Rare)", + "cords": [ + 80, + 43 + ], + "pinType": "dungeonskull" + }, + [ + "Stratholme Courier" + ], + [ + "Fras Siabi" + ], + { + "1": "Atiesh (Summon)", + "cords": [ + 86, + 38 + ] + }, + { + "1": "Hearthsinger Forresten (Varies)", + "cords": [ + 81, + 34 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Unforgiven", + "cords": [ + 73, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "Elder Farwhisper (Lunar Festival)", + "cords": [ + 79, + 17 + ] + }, + { + "1": "Timmy the Cruel", + "cords": [ + 35, + 26 + ], + "pinType": "dungeonskull" + }, + { + "1": "Malor the Zealous", + "cords": [ + 30, + 42 + ], + "pinType": "dungeonskull" + }, + [ + "Malor's Strongbox" + ], + { + "1": "Crimson Hammersmith (Summon)", + "cords": [ + 13, + 48 + ] + }, + [ + "Blacksmithing Plans" + ], + { + "1": "Cannon Master Willey", + "cords": [ + 5, + 51 + ], + "pinType": "dungeonskull" + }, + { + "1": "Archivist Galford", + "cords": [ + 28, + 74 + ], + "pinType": "dungeonskull" + }, + { + "1": "Grand Crusader Dathrohan", + "cords": [ + 21, + 82 + ], + "pinType": "dungeonskull" + }, + [ + "Balnazzar" + ], + [ + "Sothos (Summon)" + ], + [ + "Jarien (Summon)" + ] + ], + "2": [ + { + "1": "The Gauntlet", + "SubZone": true + }, + { + "1": "Elders' Square Postbox", + "cords": [ + 67, + 76 + ] + }, + [ + "3rd Box Opened: Postmaster Malown" + ], + { + "1": "Magistrate Barthilas (Varies)", + "cords": [ + 66, + 77 + ], + "pinType": "dungeonskull" + }, + { + "1": "Aurius", + "cords": [ + 68, + 74 + ], + "pinType": "dungeonskull" + }, + { + "1": "Stonespine (Rare)", + "cords": [ + 70, + 51 + ], + "pinType": "dungeonskull" + }, + { + "1": "Baroness Anastari", + "cords": [ + 76, + 46 + ], + "pinType": "dungeonskull" + }, + [ + "Black Guard Swordsmith (Summon)" + ], + [ + "Blacksmithing Plans" + ], + { + "1": "Nerub'enkan", + "cords": [ + 58, + 48 + ], + "pinType": "dungeonskull" + }, + { + "1": "Maleki the Pallid", + "cords": [ + 67, + 22 + ], + "pinType": "dungeonskull" + }, + { + "1": "Magistrate Barthilas (Varies)", + "cords": [ + 57, + 16 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ramstein the Gorger", + "cords": [ + 45, + 21 + ], + "pinType": "dungeonskull" + }, + { + "1": "Baron Rivendare", + "cords": [ + 40, + 21 + ], + "pinType": "dungeonskull" + }, + [ + "Ysida Harmon" + ] + ], + "Acronym": "Strat", + "LevelRange": "58-60", + "Location": [ + "Eastern Plaguelands", + 139 + ], + "MapName": "Stratholme", + "MinLevel": "45", + "PlayerLimit": "5", + "ZoneName": [ + "Stratholme", + 2017 + ] + }, + "TheDeadmines": { + "1": [ + { + "1": "The Deadmines", + "SubZone": true + }, + { + "1": "Rhahk'Zor ", + "cords": [ + 36, + 62 + ], + "pinType": "dungeonskull" + }, + { + "1": "Miner Johnson (Rare)", + "cords": [ + 51, + 51 + ], + "pinType": "dungeonskull" + }, + { + "1": "Sneed ", + "cords": [ + 51, + 86 + ], + "pinType": "dungeonskull" + }, + [ + "Sneed's Shredder " + ], + { + "1": "Gilnid ", + "cords": [ + 61, + 57 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "Ironclad Cove", + "SubZone": true + }, + { + "1": "Defias Gunpowder", + "cords": [ + 20, + 55 + ] + }, + { + "1": "Captain Greenskin", + "cords": [ + 61, + 38 + ], + "pinType": "dungeonskull" + }, + { + "1": "Edwin VanCleef ", + "cords": [ + 61, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "Mr. Smite ", + "cords": [ + 56, + 26 + ], + "pinType": "dungeonskull" + }, + { + "1": "Cookie ", + "cords": [ + 68, + 43 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "VC", + "LevelRange": "16-22", + "Location": [ + "Westfall", + 40 + ], + "MapName": "TheDeadmines", + "MinLevel": "10", + "PlayerLimit": "5", + "ZoneName": [ + "The Deadmines", + 1581 + ] + }, + "TheRuinsofAhnQiraj": { + "1": [ + { + "1": "Ruins of Ahn'Qiraj", + "SubZone": true + }, + { + "1": "Kurinnaxx", + "cords": [ + 56, + 35 + ], + "pinType": "dungeonskull" + }, + [ + "Lieutenant General Andorov" + ], + [ + "Four Kaldorei Elites" + ], + { + "1": "General Rajaxx", + "cords": [ + 57, + 53 + ], + "pinType": "dungeonskull" + }, + [ + "Captain Qeez" + ], + [ + "Captain Tuubid" + ], + [ + "Captain Drenn" + ], + [ + "Captain Xurrem" + ], + [ + "Major Yeggeth" + ], + [ + "Major Pakkon" + ], + [ + "Colonel Zerran" + ], + { + "1": "Moam (Optional)", + "cords": [ + 32, + 38 + ], + "pinType": "dungeonskull" + }, + { + "1": "Buru the Gorger (Optional)", + "cords": [ + 71, + 60 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ayamiss the Hunter (Optional)", + "cords": [ + 60, + 89 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ossirian the Unscarred", + "cords": [ + 41, + 73 + ], + "pinType": "dungeonskull" + }, + { + "1": "Safe Room", + "cords": [ + 63, + 74 + ] + } + ], + "Acronym": "AQR", + "LevelRange": "60-70", + "Location": [ + "Silithus", + 1377 + ], + "MapName": "RuinsofAhnQiraj", + "MinLevel": "50", + "PlayerLimit": "10-25", + "Reputation": "Cenarion Circle", + "ZoneName": [ + "Ruins of Ahn'Qiraj", + 3429 + ] + }, + "TheStockade": { + "1": [ + { + "1": "The Stockade", + "SubZone": true + }, + { + "1": "Targorr the Dread", + "cords": [ + 50, + 25 + ], + "pinType": "dungeonskull" + }, + { + "1": "Kam Deepfury", + "cords": [ + 69, + 30 + ], + "pinType": "dungeonskull" + }, + { + "1": "Hamhock", + "cords": [ + 78, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "Bazil Thredd", + "cords": [ + 86, + 51 + ], + "pinType": "dungeonskull" + }, + { + "1": "Dextren Ward", + "cords": [ + 22, + 26 + ], + "pinType": "dungeonskull" + }, + { + "1": "Bruegal Ironknuckle (Rare)", + "cords": [ + 30, + 43 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "Stocks", + "LevelRange": "21-27", + "Location": [ + "Stormwind City", + 1519 + ], + "MapName": "TheStockade", + "MinLevel": "15", + "PlayerLimit": "5", + "ZoneName": [ + "The Stockade", + 717 + ] + }, + "TheSunkenTemple": { + "1": [ + { + "1": "Sunken Temple", + "SubZone": true + }, + { + "1": "Dreamscythe", + "cords": [ + 50, + 41 + ], + "pinType": "dungeonskull" + }, + [ + "Weaver" + ], + { + "1": "Avatar of Hakkar", + "cords": [ + 26, + 46 + ], + "pinType": "dungeonskull" + }, + { + "1": "Jammal'an the Prophet", + "cords": [ + 73, + 39 + ], + "pinType": "dungeonskull" + }, + [ + "Ogom the Wretched" + ], + { + "1": "Morphaz", + "cords": [ + 50, + 87 + ], + "pinType": "dungeonskull" + }, + [ + "Hazzas" + ], + { + "1": "Shade of Eranikus", + "cords": [ + 68, + 87 + ], + "pinType": "dungeonskull" + }, + [ + "Essence Font" + ], + { + "1": "Upper Ring", + "SubZone": true + }, + [ + "Spawn of Hakkar (Rare)" + ], + [ + "Elder Starsong (Lunar Festival)" + ], + [ + "1'-6') Statue Activation Order" + ], + [ + "7') Atal'ai Defenders" + ], + [ + "Gasher" + ], + [ + "Loro" + ], + [ + "Hukku" + ], + [ + "Zolo" + ], + [ + "Mijan" + ], + [ + "Zul'Lor" + ], + { + "1": "Lower Pool", + "SubZone": true + }, + [ + "Altar of Hakkar" + ], + [ + "Atal'alarion " + ] + ], + "Acronym": "ST", + "LevelRange": "46-53", + "Location": [ + "Swamp of Sorrows", + 8 + ], + "MapName": "TheTempleOfAtalHakkar", + "MinLevel": "35", + "PlayerLimit": "5", + "ZoneName": [ + "Sunken Temple", + 1417 + ] + }, + "TheTempleofAhnQiraj": { + "1": [ + { + "1": "The Hive Undergrounds", + "SubZone": true + }, + { + "1": "The Bug Family (Optional)", + "cords": [ + 28, + 50 + ], + "pinType": "dungeonskull" + }, + [ + "Vem" + ], + [ + "Lord Kri" + ], + [ + "Princess Yauj" + ], + { + "1": "Battleguard Sartura", + "cords": [ + 45, + 35 + ], + "pinType": "dungeonskull" + }, + { + "1": "Fankriss the Unyielding", + "cords": [ + 63, + 23 + ], + "pinType": "dungeonskull" + }, + { + "1": "Viscidus (Optional)", + "cords": [ + 73, + 18 + ], + "pinType": "dungeonskull" + }, + { + "1": "Princess Huhuran", + "cords": [ + 44, + 50 + ], + "pinType": "dungeonskull" + }, + { + "1": "Twin Emperors", + "cords": [ + 61, + 71 + ], + "pinType": "dungeonskull" + }, + [ + "Emperor Vek'lor" + ], + [ + "Emperor Vek'nilash" + ], + { + "1": "Ouro (Optional)", + "cords": [ + 31, + 81 + ], + "pinType": "dungeonskull" + } + ], + "2": [ + { + "1": "The Temple Gates", + "SubZone": true + }, + { + "1": "The Prophet Skeram (Outside)", + "cords": [ + 46, + 53 + ], + "pinType": "dungeonskull" + } + ], + "3": [ + { + "1": "Vault of C'Thun", + "SubZone": true + }, + { + "1": "Eye of C'Thun", + "cords": [ + 57, + 63 + ], + "pinType": "dungeonskull" + }, + [ + "C'Thun" + ], + { + "1": "Andorgos ", + "cords": [ + 50, + 19 + ] + }, + [ + "Vethsera " + ], + [ + "Kandrostrasz " + ], + { + "1": "Arygos", + "cords": [ + 41, + 26 + ] + }, + [ + "Caelestrasz" + ], + [ + "Merithra of the Dream" + ] + ], + "Acronym": "AQT", + "LevelRange": "60-70", + "Location": [ + "Silithus", + 1377 + ], + "MapName": "AhnQiraj", + "MinLevel": "50", + "PlayerLimit": "10-25", + "Reputation": "Brood of Nozdormu", + "ZoneName": [ + "Temple of Ahn'Qiraj", + 3428 + ] + }, + "Uldaman": { + "1": [ + { + "1": "Hall of the Keepers", + "SubZone": true + }, + [ + "Key: Staff of Prehistoria (Ironaya)" + ], + { + "1": "Baelog", + "cords": [ + 59, + 93 + ], + "pinType": "dungeonskull" + }, + [ + "Eric \"The Swift\"" + ], + [ + "Olaf" + ], + [ + "Baelog's Chest" + ], + [ + "Conspicuous Urn" + ], + { + "1": "Remains of a Paladin", + "cords": [ + 53, + 64 + ] + }, + { + "1": "Revelosh", + "cords": [ + 54, + 72 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ironaya", + "cords": [ + 37, + 74 + ], + "pinType": "dungeonskull" + }, + { + "1": "Obsidian Sentinel", + "cords": [ + 29, + 60 + ], + "pinType": "dungeonskull" + }, + { + "1": "Ancient Stone Keeper", + "cords": [ + 48, + 45 + ], + "pinType": "dungeonskull" + }, + { + "1": "Galgann Firehammer", + "cords": [ + 27, + 33 + ], + "pinType": "dungeonskull" + }, + [ + "Tablet of Will" + ], + [ + "Shadowforge Cache" + ], + { + "1": "Grimlok ", + "cords": [ + 22, + 26 + ], + "pinType": "dungeonskull" + }, + { + "1": "Archaedas (Lower)", + "cords": [ + 42, + 17 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Discs of Norgannon (Lower)", + "cords": [ + 40, + 9 + ] + }, + [ + "Ancient Treasure (Lower)" + ] + ], + "2": [ + { + "1": "Khaz'goroth's Seat", + "SubZone": true + }, + { + "1": "Archaedas (Lower)", + "cords": [ + 55, + 52 + ], + "pinType": "dungeonskull" + }, + { + "1": "The Discs of Norgannon (Lower)", + "cords": [ + 50, + 34 + ] + }, + [ + "Ancient Treasure (Lower)" + ] + ], + "Acronym": "Ulda", + "LevelRange": "36-42", + "Location": [ + "Badlands", + 3 + ], + "MapName": "Uldaman", + "MinLevel": "30", + "PlayerLimit": "5", + "ZoneName": [ + "Uldaman", + 1337 + ] + }, + "WailingCaverns": { + "1": [ + { + "1": "Wailing Caverns", + "SubZone": true + }, + { + "1": "Disciple of Naralex", + "cords": [ + 47, + 53 + ] + }, + { + "1": "Lord Cobrahn ", + "cords": [ + 16, + 57 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lady Anacondra (Varies)", + "cords": [ + 31, + 44 + ], + "pinType": "dungeonskull" + }, + { + "1": "Kresh (Varies)", + "cords": [ + 45, + 38 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lord Pythas ", + "cords": [ + 64, + 58 + ], + "pinType": "dungeonskull" + }, + { + "1": "Skum", + "cords": [ + 74, + 82 + ], + "pinType": "dungeonskull" + }, + { + "1": "Lord Serpentis (Upper)", + "cords": [ + 62, + 54 + ], + "pinType": "dungeonskull" + }, + { + "1": "Verdan the Everliving (Upper)", + "cords": [ + 56, + 47 + ], + "pinType": "dungeonskull" + }, + { + "1": "Mutanus the Devourer", + "cords": [ + 35, + 13 + ], + "pinType": "dungeonskull" + }, + [ + "Naralex" + ], + [ + "Deviate Faerie Dragon (Rare)" + ] + ], + "Acronym": "WC", + "LevelRange": "16-25", + "Location": [ + "The Barrens", + 17 + ], + "MapName": "WailingCaverns", + "MinLevel": "10", + "PlayerLimit": "5", + "ZoneName": [ + "Wailing Caverns", + 718 + ] + }, + "ZulFarrak": { + "1": [ + { + "1": "Zul'Farrak", + "SubZone": true + }, + { + "1": "Antu'sul ", + "cords": [ + 66, + 27 + ], + "pinType": "dungeonskull" + }, + { + "1": "Theka the Martyr", + "cords": [ + 54, + 28 + ], + "pinType": "dungeonskull" + }, + { + "1": "Witch Doctor Zum'rah", + "cords": [ + 45, + 17 + ], + "pinType": "dungeonskull" + }, + [ + "Zul'Farrak Dead Hero" + ], + { + "1": "Nekrum Gutchewer", + "cords": [ + 34, + 18 + ], + "pinType": "dungeonskull" + }, + [ + "Shadowpriest Sezz'ziz" + ], + [ + "Dustwraith (Rare)" + ], + { + "1": "Sergeant Bly", + "cords": [ + 28, + 18 + ], + "pinType": "dungeonskull" + }, + [ + "Weegli Blastfuse" + ], + [ + "Murta Grimgut" + ], + [ + "Raven" + ], + [ + "Oro Eyegouge" + ], + { + "1": "Sandfury Executioner", + "cords": [ + 25, + 18 + ], + "pinType": "dungeonskull" + }, + { + "1": "Hydromancer Velratha", + "cords": [ + 32, + 41 + ], + "pinType": "dungeonskull" + }, + [ + "Gahz'rilla (Summon)" + ], + [ + "Elder Wildmane (Lunar Festival)" + ], + { + "1": "Chief Ukorz Sandscalp", + "cords": [ + 42, + 34 + ], + "pinType": "dungeonskull" + }, + [ + "Ruuzlu" + ], + { + "1": "Zerillis (Rare, Wanders)", + "cords": [ + 53, + 42 + ], + "pinType": "dungeonskull" + }, + { + "1": "Sandarr Dunereaver (Rare)", + "cords": [ + 46, + 57 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "ZF", + "LevelRange": "42-48", + "Location": [ + "Tanaris", + 440 + ], + "MapName": "ZulFarrak", + "MinLevel": "35", + "PlayerLimit": "5", + "ZoneName": [ + "Zul'Farrak", + 978 + ] + }, + "ZulGurub": { + "1": [ + { + "1": "Zul'Gurub", + "SubZone": true + }, + { + "1": "High Priestess Jeklik (Bat)", + "cords": [ + 40, + 73 + ], + "pinType": "dungeonskull" + }, + { + "1": "High Priest Venoxis (Snake)", + "cords": [ + 51, + 54 + ], + "pinType": "dungeonskull" + }, + [ + "Zanza the Restless" + ], + { + "1": "High Priestess Mar'li (Spider)", + "cords": [ + 48, + 78 + ], + "pinType": "dungeonskull" + }, + { + "1": "Bloodlord Mandokir (Raptor)", + "cords": [ + 62, + 68 + ], + "pinType": "dungeonskull" + }, + [ + "Ohgan" + ], + { + "1": "Edge of Madness (Optional)", + "cords": [ + 60, + 51 + ], + "pinType": "dungeonskull" + }, + [ + "Gri'lek (Random)" + ], + [ + "Hazza'rah (Random)" + ], + [ + "Renataki (Random)" + ], + [ + "Wushoolay (Random)" + ], + { + "1": "Gahz'ranka (Optional)", + "cords": [ + 57, + 36 + ], + "pinType": "dungeonskull" + }, + { + "1": "High Priest Thekal (Tiger)", + "cords": [ + 62, + 35 + ], + "pinType": "dungeonskull" + }, + [ + "Zealot Zath" + ], + [ + "Zealot Lor'Khan" + ], + { + "1": "High Priestess Arlokk (Panther)", + "cords": [ + 48, + 22 + ], + "pinType": "dungeonskull" + }, + { + "1": "Jin'do the Hexxer (Undead)", + "cords": [ + 32, + 25 + ], + "pinType": "dungeonskull" + }, + { + "1": "Hakkar", + "cords": [ + 50, + 37 + ], + "pinType": "dungeonskull" + } + ], + "Acronym": "ZG", + "LevelRange": "56-70", + "Location": [ + "Stranglethorn Vale", + 33 + ], + "MapName": "ZulGurub", + "MinLevel": "50", + "PlayerLimit": "20", + "Reputation": "Zandalar Tribe", + "ZoneName": [ + "Zul'Gurub", + 19 + ] + } + }, + "WrathoftheLichKing": { + "AhnKahet": { + "1": [ + [ + "Entrance" + ], + [ + "Exit" + ], + [ + "Elder Nadox" + ], + [ + "Prince Taldaram" + ], + [ + "Amanitar (Heroic)" + ], + [ + "Jedoga Shadowseeker" + ], + [ + "Herald Volazj" + ], + [ + "Ahn'kahet Brazier" + ] + ], + "Acronym": "AK, Kahet", + "LevelRange": "71-78", + "Location": [ + "Dragonblight", + 65 + ], + "MapName": "DireMaul", + "MinLevel": "68", + "PlayerLimit": "5", + "ZoneName": [ + "Ahn'kahet: The Old Kingdom", + 4494 + ] + }, + "AzjolNerub": { + "1": [ + [ + "Entrance" + ], + [ + "Connection" + ], + [ + "Exit" + ], + [ + "Krik'thir the Gatewatcher" + ], + [ + "Watcher Gashra" + ], + [ + "Watcher Narjil" + ], + [ + "Watcher Silthik" + ], + [ + "Hadronox" + ], + [ + "Elder Nurgen (Lunar Festival)" + ], + [ + "Anub'arak" + ] + ], + "Acronym": "AN, Nerub", + "LevelRange": "70-77", + "Location": [ + "Dragonblight", + 65 + ], + "MapName": "DireMaul", + "MinLevel": "67", + "PlayerLimit": "5", + "ZoneName": [ + "Azjol-Nerub", + 3477 + ] + }, + "CoTOldStratholme": { + "1": [ + [ + "|cff9900ffEvent: The Culling of Stratholme" + ], + [ + "Entrance" + ], + [ + "Exit (Portal)" + ], + [ + "X) Scourge Invasion Points" + ], + [ + "Wave 5: Meathook" + ], + [ + "Wave 10: Salramm the Fleshcrafter" + ], + [ + "Chromie" + ], + [ + "Chromie" + ], + [ + "Chrono-Lord Epoch" + ], + [ + "Infinite Corruptor (Heroic)" + ], + [ + "Guardian of Time" + ], + [ + "Mal'Ganis" + ], + [ + "Chromie" + ] + ], + "Acronym": "CoT-Strat", + "LevelRange": "78-80", + "Location": [ + "Tanaris", + 440 + ], + "MapName": "DireMaul", + "MinLevel": "75", + "PlayerLimit": "5", + "ZoneName": [ + "Caverns of Time: The Culling of Stratholme", + 4100 + ] + }, + "DrakTharonKeep": { + "1": [ + [ + "Entrance" + ], + [ + "Connection" + ], + [ + "Connection" + ], + [ + "Trollgore" + ], + [ + "Kurzel" + ], + [ + "Novos the Summoner" + ], + [ + "Elder Kilias (Lunar Festival)" + ], + [ + "King Dred" + ], + [ + "The Prophet Tharon'ja" + ], + [ + "Drakuru's Brazier" + ] + ], + "Acronym": "DTK", + "LevelRange": "72-78", + "Location": [ + "Grizzly Hills", + 394 + ], + "MapName": "DireMaul", + "MinLevel": "69", + "PlayerLimit": "5", + "ZoneName": [ + "Drak'Tharon Keep", + 4196 + ] + }, + "FHHallsOfReflection": { + "1": [ + [ + "Attunement Required" + ], + [ + "Entrance" + ], + [ + "Portal (Dalaran)" + ], + [ + "Lady Jaina Proudmoore (Alliance)" + ], + [ + "Archmage Koreln (Alliance)" + ], + [ + "Lady Sylvanas Windrunner (Horde)" + ], + [ + "Dark Ranger Loralen (Horde)" + ], + [ + "Falric (Wave 5)" + ], + [ + "Marwyn (Wave 10)" + ], + [ + "Wrath of the Lich King (Event)" + ], + [ + "The Captain's Chest" + ] + ], + "Acronym": "HoR, FH3", + "LevelRange": "80+", + "Location": [ + "Icecrown Citadel", + 4812 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "5", + "ZoneName": [ + "The Frozen Halls: Halls of Reflection", + 4820 + ] + }, + "FHPitOfSaron": { + "1": [ + [ + "Attunement Required" + ], + [ + "Entrance" + ], + [ + "Portal (Halls of Reflection)" + ], + [ + "Lady Jaina Proudmoore (Alliance)" + ], + [ + "Archmage Koreln (Alliance)" + ], + [ + "Archmage Elandra (Alliance)" + ], + [ + "Lady Sylvanas Windrunner (Horde)" + ], + [ + "Dark Ranger Loralen (Horde)" + ], + [ + "Dark Ranger Kalira (Horde)" + ], + [ + "Forgemaster Garfrost" + ], + [ + "Martin Victus (Alliance)" + ], + [ + "Gorkun Ironskull (Horde)" + ], + [ + "Krick and Ick" + ], + [ + "Scourgelord Tyrannus" + ], + [ + "Rimefang" + ] + ], + "Acronym": "PoS, FH2", + "LevelRange": "80+", + "Location": [ + "Icecrown Citadel", + 4812 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "5", + "ZoneName": [ + "The Frozen Halls: Pit of Saron", + 4813 + ] + }, + "FHTheForgeOfSouls": { + "1": [ + [ + "Entrance" + ], + [ + "Portal (Pit of Saron)" + ], + [ + "Lady Jaina Proudmoore (Alliance)" + ], + [ + "Archmage Koreln (Alliance)" + ], + [ + "Archmage Elandra (Alliance)" + ], + [ + "Lady Sylvanas Windrunner (Horde)" + ], + [ + "Dark Ranger Loralen (Horde)" + ], + [ + "Dark Ranger Kalira (Horde)" + ], + [ + "Bronjahm " + ], + [ + "Devourer of Souls" + ] + ], + "Acronym": "FoS, FH1", + "LevelRange": "80+", + "Location": [ + "Icecrown Citadel", + 4812 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "5", + "ZoneName": [ + "The Frozen Halls: The Forge of Souls", + 4809 + ] + }, + "Gundrak": { + "1": [ + [ + "Entrance" + ], + [ + "Exit" + ], + [ + "Slad'ran " + ], + [ + "Drakkari Colossus" + ], + [ + "Elder Ohanzee (Lunar Festival)" + ], + [ + "Moorabi " + ], + [ + "Eck the Ferocious (Heroic, Summon)" + ], + [ + "Gal'darah " + ] + ], + "Acronym": "Gun", + "LevelRange": "76-80", + "Location": [ + "Zul'Drak", + 66 + ], + "MapName": "DireMaul", + "MinLevel": "71", + "PlayerLimit": "5", + "ZoneName": [ + "Gundrak", + 4375 + ] + }, + "IcecrownCitadelA": { + "1": [ + [ + "Reputation: The Ashen Verdict" + ], + [ + "Entrance" + ], + [ + "Connection" + ], + [ + "To next map" + ], + [ + "Lord Marrowgar" + ], + [ + "Lady Deathwhisper" + ], + [ + "Gunship Battle (Alliance)" + ], + [ + "Gunship Battle (Horde)" + ], + [ + "Deathbringer Saurfang" + ], + [ + "Light's Hammer (Teleporter)" + ], + [ + "Oratory of the Damned (Teleporter)" + ], + [ + "Rampart of Skulls (Teleporter, Lower)" + ], + [ + "Deathbringer's Rise (Teleporter, Upper)" + ] + ], + "Acronym": "IC", + "LevelRange": "80+", + "Location": [ + "Icecrown", + 210 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Icecrown Citadel [A] (Lower)", + 4812 + ] + }, + "IcecrownCitadelB": { + "1": [ + [ + "Reputation: The Ashen Verdict" + ], + [ + "From previous map" + ], + [ + "D-H) Connection" + ], + [ + "I) To next map" + ], + [ + "Festergut" + ], + [ + "Rotface" + ], + [ + "Professor Putricide" + ], + [ + "Blood Prince Council" + ], + [ + "Prince Keleseth" + ], + [ + "Prince Taldaram" + ], + [ + "Prince Valanar" + ], + [ + "Blood-Queen Lana'thel" + ], + [ + "Valithria Dreamwalker" + ], + [ + "Sindragosa " + ], + [ + "4') Upper Spire (Teleporter)" + ], + [ + "5') Sindragosa's Lair (Teleporter)" + ] + ], + "Acronym": "IC", + "LevelRange": "80+", + "Location": [ + "Icecrown", + 210 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Icecrown Citadel [B] (Upper)", + 4812 + ] + }, + "IcecrownCitadelC": { + "1": [ + [ + "Reputation: The Ashen Verdict" + ], + [ + "I) From previous map" + ], + [ + "The Lich King" + ] + ], + "Acronym": "IC", + "LevelRange": "80+", + "Location": [ + "Icecrown", + 210 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Icecrown Citadel [C] (The Frozen Throne)", + 4812 + ] + }, + "Naxxramas": { + "1": [ + [ + "Entrance" + ], + [ + "Mr. Bigglesworth (Wanders)" + ], + [ + "The Construct Quarter" + ], + [ + "Patchwerk" + ], + [ + "Grobbulus" + ], + [ + "Gluth" + ], + [ + "Thaddius" + ], + [ + "The Arachnid Quarter" + ], + [ + "Anub'Rekhan" + ], + [ + "Grand Widow Faerlina" + ], + [ + "Maexxna" + ], + [ + "|cffcc6666The Military Quarter" + ], + [ + "|cffcc6666Instructor Razuvious" + ], + [ + "|cffcc6666Gothik the Harvester" + ], + [ + "|cffcc6666The Four Horsemen" + ], + [ + "|cffcc6666Thane Korth'azz" + ], + [ + "|cffcc6666Lady Blaumeux" + ], + [ + "|cffcc6666Baron Rivendare" + ], + [ + "|cffcc6666Sir Zeliek" + ], + [ + "|cffcc6666Four Horsemen Chest" + ], + [ + "|cff9900ffThe Plague Quarter" + ], + [ + "|cff9900ffNoth the Plaguebringer" + ], + [ + "|cff9900ffHeigan the Unclean" + ], + [ + "|cff9900ffLoatheb" + ], + [ + "Frostwyrm Lair" + ], + [ + "Sapphiron" + ], + [ + "Kel'Thuzad" + ] + ], + "Acronym": "Nax", + "LevelRange": "80+", + "Location": [ + "Dragonblight", + 65 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Naxxramas", + 3456 + ] + }, + "ObsidianSanctum": { + "1": [ + [ + "AKA: Black Dragonflight Chamber" + ], + [ + "Entrance" + ], + [ + "Tenebron" + ], + [ + "Shadron" + ], + [ + "Vesperon" + ], + [ + "Sartharion " + ] + ], + "Acronym": "OS", + "LevelRange": "80", + "Location": [ + "Dragonblight", + 65 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Chamber of the Aspects: The Obsidian Sanctum", + 4493 + ] + }, + "OnyxiasLair": { + "1": [ + [ + "Entrance" + ], + [ + "Onyxian Warders" + ], + [ + "Whelp Eggs" + ], + [ + "Onyxia" + ] + ], + "Acronym": "Ony", + "LevelRange": "80+", + "Location": [ + "Dustwallow Marsh" + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Onyxia's Lair" + ] + }, + "RubySanctum": { + "1": [ + [ + "AKA: self.Colors.RED Dragonflight Chamber" + ], + [ + "Entrance" + ], + [ + "Baltharus the Warborn" + ], + [ + "Saviana Ragefire" + ], + [ + "General Zarithrian" + ], + [ + "Halion " + ] + ], + "Acronym": "RS", + "LevelRange": "80", + "Location": [ + "Dragonblight", + 65 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Chamber of the Aspects: The Ruby Sanctum", + 4987 + ] + }, + "TheEyeOfEternity": { + "1": [ + [ + "Key: Key to the Focusing Iris" + ], + [ + "Malygos" + ] + ], + "Acronym": "TEoE", + "LevelRange": "80+", + "Location": [ + "Borean Tundra", + 3537 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "The Nexus: The Eye of Eternity", + 4500 + ] + }, + "TheNexus": { + "1": [ + [ + "Entrance" + ], + [ + "Berinand's Research" + ], + [ + "Commander Stoutbeard (Horde, Heroic)" + ], + [ + "Commander Kolurg (Alliance, Heroic)" + ], + [ + "Grand Magus Telestra" + ], + [ + "Anomalus" + ], + [ + "Elder Igasho (Lunar Festival)" + ], + [ + "Ormorok the Tree-Shaper" + ], + [ + "Keristrasza" + ] + ], + "Acronym": "Nex, Nexus", + "LevelRange": "70-75", + "Location": [ + "Borean Tundra", + 3537 + ], + "MapName": "DireMaul", + "MinLevel": "66", + "PlayerLimit": "5", + "ZoneName": [ + "The Nexus: The Nexus", + 4120 + ] + }, + "TheOculus": { + "1": [ + [ + "Entrance" + ], + [ + "Portal" + ], + [ + "Drakos the Interrogator" + ], + [ + "Varos Cloudstrider " + ], + [ + "Mage-Lord Urom" + ], + [ + "Ley-Guardian Eregos" + ], + [ + "Cache of Eregos" + ], + [ + "Centrifuge Construct" + ] + ], + "Acronym": "Ocu", + "LevelRange": "78-80", + "Location": [ + "The Nexus", + 4120 + ], + "MapName": "DireMaul", + "MinLevel": "75", + "PlayerLimit": "5", + "ZoneName": [ + "The Nexus: The Oculus", + 4228 + ] + }, + "TrialOfTheChampion": { + "1": [ + [ + "Entrance" + ], + [ + "Grand Champions" + ], + [ + "Champions of the Alliance" + ], + [ + "Marshal Jacob Alerius" + ], + [ + "Ambrose Boltspark" + ], + [ + "Colosos" + ], + [ + "Jaelyne Evensong" + ], + [ + "Lana Stouthammer" + ], + [ + "Champions of the Horde" + ], + [ + "Mokra the Skullcrusher" + ], + [ + "Eressea Dawnsinger" + ], + [ + "Runok Wildmane" + ], + [ + "Zul'tore" + ], + [ + "Deathstalker Visceri" + ], + [ + "Eadric the Pure " + ], + [ + "Argent Confessor Paletress" + ], + [ + "The Black Knight" + ] + ], + "Acronym": "Champ", + "LevelRange": "80+", + "Location": [ + "Icecrown", + 210 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "5", + "ZoneName": [ + "Crusaders' Coliseum: Trial of the Champion", + 4723 + ] + }, + "TrialOfTheCrusader": { + "1": [ + [ + "Heroic: Trial of the Grand Crusader" + ], + [ + "Entrance" + ], + [ + "Cavern Entrance" + ], + [ + "Northrend Beasts" + ], + [ + "Gormok the Impaler" + ], + [ + "Acidmaw" + ], + [ + "Dreadscale" + ], + [ + "Icehowl" + ], + [ + "Lord Jaraxxus" + ], + [ + "Faction Champions" + ], + [ + "Twin Val'kyr" + ], + [ + "Fjola Lightbane" + ], + [ + "Eydis Darkbane" + ], + [ + "Anub'arak" + ] + ], + "Acronym": "Crus", + "LevelRange": "80+", + "Location": [ + "Icecrown", + 210 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Crusaders' Coliseum: Trial of the Crusader", + 4722 + ] + }, + "UlduarA": { + "1": [ + [ + "Entrance" + ], + [ + "The Antechamber" + ], + [ + "A') Tower of Life" + ], + [ + "B') Tower of Flame" + ], + [ + "C') Tower of Frost" + ], + [ + "D') Tower of Storms" + ], + [ + "Flame Leviathan" + ], + [ + "Razorscale (Optional)" + ], + [ + "Ignis the Furnace Master (Optional)" + ], + [ + "XT-002 Deconstructor" + ], + [ + "Expedition Base Camp (Teleporter)" + ], + [ + "Formation Grounds (Teleporter)" + ], + [ + "The Colossal Forge (Teleporter)" + ], + [ + "4') The Scrapyard (Teleporter)" + ] + ], + "Acronym": "Uldu", + "LevelRange": "80+", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Ulduar [A] (The Siege)", + 4273 + ] + }, + "UlduarB": { + "1": [ + [ + "Key: Celestial Planetarium Key" + ], + [ + "The Siege" + ], + [ + "The Keepers" + ], + [ + "Assembly of Iron (Optional)" + ], + [ + "Steelbreaker" + ], + [ + "Runemaster Molgeim" + ], + [ + "Stormcaller Brundir" + ], + [ + "Prospector Doren" + ], + [ + "Archivum Console" + ], + [ + "Kologarn" + ], + [ + "Algalon the Observer (Optional)" + ], + [ + "5') The Antechamber (Teleporter)" + ] + ], + "Acronym": "Uldu", + "LevelRange": "80+", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Ulduar [B] (The Antechamber)", + 4273 + ] + }, + "UlduarC": { + "1": [ + [ + "The Antechamber" + ], + [ + "D) The Spark of Imagination" + ], + [ + "E) The Descent into Madness" + ], + [ + "Auriaya (Optional)" + ], + [ + "Hodir" + ], + [ + "Thorim" + ], + [ + "Freya" + ], + [ + "6') The Shattered Walkway (Teleporter)" + ], + [ + "7') The Conservatory of Life (Teleporter)" + ] + ], + "Acronym": "Uldu", + "LevelRange": "80+", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Ulduar [C] (The Keepers)", + 4273 + ] + }, + "UlduarD": { + "1": [ + [ + "D) The Keepers" + ], + [ + "Mimiron" + ], + [ + "8') The Spark of Imagination (Teleporter)" + ] + ], + "Acronym": "Uldu", + "LevelRange": "80+", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Ulduar [D] (The Spark of Imagination)", + 4273 + ] + }, + "UlduarE": { + "1": [ + [ + "E) The Keepers" + ], + [ + "General Vezax" + ], + [ + "Yogg-Saron" + ], + [ + "9') The Prison of Yogg-Saron (Teleporter)" + ] + ], + "Acronym": "Uldu", + "LevelRange": "80+", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Ulduar [E] (The Descent into Madness)", + 4273 + ] + }, + "UlduarHallsofLightning": { + "1": [ + [ + "Entrance" + ], + [ + "General Bjarngrim (Wanders)" + ], + [ + "Volkhan" + ], + [ + "Ionar" + ], + [ + "Loken" + ] + ], + "Acronym": "HoL", + "LevelRange": "78-80", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "75", + "PlayerLimit": "5", + "ZoneName": [ + "Ulduar: Halls of Lightning", + 4272 + ] + }, + "UlduarHallsofStone": { + "1": [ + [ + "Entrance" + ], + [ + "Elder Yurauk (Lunar Festival)" + ], + [ + "Krystallus" + ], + [ + "Maiden of Grief" + ], + [ + "Brann Bronzebeard" + ], + [ + "Tribunal Chest" + ], + [ + "Sjonnir the Ironshaper" + ] + ], + "Acronym": "HoS", + "LevelRange": "75-80", + "Location": [ + "The Storm Peaks", + 67 + ], + "MapName": "DireMaul", + "MinLevel": "72", + "PlayerLimit": "5", + "ZoneName": [ + "Ulduar: Halls of Stone", + 4264 + ] + }, + "UtgardeKeep": { + "1": [ + [ + "Entrance" + ], + [ + "Dark Ranger Marrah" + ], + [ + "B-C) Connection" + ], + [ + "Prince Keleseth " + ], + [ + "Elder Jarten (Lunar Festival, Lower)" + ], + [ + "Dalronn the Controller" + ], + [ + "Skarvald the Constructor" + ], + [ + "Ingvar the Plunderer" + ] + ], + "Acronym": "UK, Keep", + "LevelRange": "68-75", + "Location": [ + "Howling Fjord", + 495 + ], + "MapName": "DireMaul", + "MinLevel": "65", + "PlayerLimit": "5", + "ZoneName": [ + "Utgarde Keep: Utgarde Keep", + 206 + ] + }, + "UtgardePinnacle": { + "1": [ + [ + "Entrance" + ], + [ + "Brigg Smallshanks" + ], + [ + "Svala Sorrowgrave" + ], + [ + "Gortok Palehoof" + ], + [ + "Skadi the Ruthless" + ], + [ + "Elder Chogan'gada (Lunar Festival)" + ], + [ + "King Ymiron" + ] + ], + "Acronym": "UP, Pinn", + "LevelRange": "78-80", + "Location": [ + "Utgarde Keep", + 206 + ], + "MapName": "DireMaul", + "MinLevel": "75", + "PlayerLimit": "5", + "ZoneName": [ + "Utgarde Keep: Utgarde Pinnacle", + 1196 + ] + }, + "VaultOfArchavon": { + "1": [ + [ + "Entrance" + ], + [ + "Archavon the Stone Watcher" + ], + [ + "Emalon the Storm Watcher" + ], + [ + "Koralon the Flame Watcher" + ], + [ + "Toravon the Ice Watcher" + ] + ], + "Acronym": "VoA", + "LevelRange": "80+", + "Location": [ + "Wintergrasp", + 4197 + ], + "MapName": "DireMaul", + "MinLevel": "80", + "PlayerLimit": "10/25", + "ZoneName": [ + "Vault of Archavon", + 4603 + ] + }, + "VioletHold": { + "1": [ + [ + "Key: The Violet Hold Key" + ], + [ + "Entrance" + ], + [ + "Erekem (Random)" + ], + [ + "Zuramat the Obliterator (Upper, Random)" + ], + [ + "Xevozz (Lower, Random)" + ], + [ + "Ichoron (Random)" + ], + [ + "Moragg (Random)" + ], + [ + "Lavanthor (Random)" + ], + [ + "Cyanigosa (Wave 18)" + ] + ], + "Acronym": "VH", + "LevelRange": "73-79", + "Location": [ + "Dalaran", + 4395 + ], + "MapName": "DireMaul", + "MinLevel": "70", + "PlayerLimit": "5", + "ZoneName": [ + "The Violet Hold", + 4415 + ] + } + } +} diff --git a/tools/build_data.py b/tools/build_data.py new file mode 100644 index 0000000..be4b349 --- /dev/null +++ b/tools/build_data.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +Combine AtlasLoot boss coords + stitched hi-res maps into web/assets/dungeons.json. + +Pipeline assumption: + data/maps_png_hires/[_floor].png ← stitch_uprez.py + web/assets/maps/[_floor].webp ← compress_for_web.py + +Resolution order for AtlasLoot dungeon-id → map file(s): + 1. data/aliases.json explicit override (keyed by AtlasLoot id OR MapName) + 2. exact basename match (case-sensitive) + 3. case-insensitive basename match + 4. nothing → dungeon listed without a map (still pickable) +""" +from __future__ import annotations +import json +import re +import sys +from pathlib import Path +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent +DATA = ROOT / "data" +WEB_ASSETS = ROOT / "web" / "assets" +WEB_MAPS = WEB_ASSETS / "maps" # holds the .webp files (and is also the source of truth for dimensions) +ALIASES_PATH = DATA / "aliases.json" +ATLASLOOT_PATH = DATA / "atlasloot_maps.json" +OUT_PATH = WEB_ASSETS / "dungeons.json" + +FLOOR_RE = re.compile(r"^(.+?)(?:_floor\d+|_overview)$") + + +def load_json(p: Path) -> dict: + with p.open() as f: + return json.load(f) + + +def join_zone_name(zn) -> str | None: + if isinstance(zn, list) and zn: + return str(zn[0]) + if isinstance(zn, str): + return zn + return None + + +def join_location(loc) -> str | None: + if isinstance(loc, list) and loc: + return str(loc[0]) + if isinstance(loc, str): + return loc + return None + + +def collect_bosses(dungeon: dict) -> list[dict]: + out: list[dict] = [] + seen = set() + for k, v in dungeon.items(): + if not k.isdigit() or not isinstance(v, list): + continue + for entry in v: + if not isinstance(entry, dict): + continue + if entry.get("SubZone"): + continue + cords = entry.get("cords") + name = entry.get("1") + if not (isinstance(cords, list) and len(cords) == 2 and name): + continue + key = (name, cords[0], cords[1]) + if key in seen: + continue + seen.add(key) + out.append({ + "name": name, + "x": cords[0], + "y": cords[1], + "type": entry.get("pinType", "boss"), + }) + return out + + +def floor_sort_key(stem: str): + """Sort _overview before any _floor; floor numbers in numeric order.""" + if stem.endswith("_overview"): + return (0, 0, stem) + m = re.match(r".+_floor(\d+)$", stem) + if m: + return (1, int(m.group(1)), stem) + return (2, 0, stem) + + +def index_map_files() -> dict[str, list[str]]: + """Group web .webp files by base map name → { base: [stem1, stem2, ...] }.""" + if not WEB_MAPS.exists(): + return {} + grouped: dict[str, list[str]] = {} + for p in WEB_MAPS.glob("*.webp"): + stem = p.stem + m = FLOOR_RE.match(stem) + base = m.group(1) if m else stem + grouped.setdefault(base, []).append(stem) + for base in grouped: + grouped[base].sort(key=floor_sort_key) + return grouped + + +def expand_bases(bases: list[str], grouped: dict[str, list[str]]) -> list[str]: + """An alias entry like "Karazhan" expands to all floors; a specific stem + like "Karazhan_floor1" passes through as-is.""" + out: list[str] = [] + for b in bases: + if b in grouped: + out.extend(grouped[b]) + else: + out.append(b) + return out + + +def resolve_basenames( + dungeon_id: str, + map_name: str | None, + aliases: dict, + grouped: dict[str, list[str]], +) -> list[str]: + """Return list of webp basenames to use for this dungeon entry. + Note: AtlasLoot's WotLK MapData has a known bug where every dungeon's + MapName is "DireMaul"; we therefore never resolve by MapName, only by + dungeon_id (with explicit aliases for the rest).""" + # Explicit alias override wins + if dungeon_id in aliases: + return expand_bases(aliases[dungeon_id], grouped) + if map_name and map_name in aliases: + return expand_bases(aliases[map_name], grouped) + + # Auto-resolve by AtlasLoot id, exact then case-insensitive + if dungeon_id in grouped: + return list(grouped[dungeon_id]) + lc_index = {k.lower(): k for k in grouped} + if dungeon_id and dungeon_id.lower() in lc_index: + return list(grouped[lc_index[dungeon_id.lower()]]) + + return [] + + +def main() -> int: + atlasloot = load_json(ATLASLOOT_PATH) + aliases = load_json(ALIASES_PATH) if ALIASES_PATH.exists() else {} + aliases = {k: v for k, v in aliases.items() if not k.startswith("_")} + + # Ascension M+ pool is classic-only. + atlasloot = {k: v for k, v in atlasloot.items() if k == "OriginalWoW"} + + grouped = index_map_files() + if not grouped: + print(f"warning: no hi-res PNGs in {PNG_HIRES}", file=sys.stderr) + + dungeons: list[dict] = [] + used_bases: set[str] = set() + no_map: list[str] = [] + + for expansion, entries in atlasloot.items(): + for did, dungeon in entries.items(): + if not isinstance(dungeon, dict): + continue + map_name = dungeon.get("MapName") + zone_name = join_zone_name(dungeon.get("ZoneName")) or did + bosses = collect_bosses(dungeon) + + basenames = resolve_basenames(did, map_name, aliases, grouped) + + maps_out: list[dict] = [] + for basename in basenames: + src = WEB_MAPS / f"{basename}.webp" + if not src.exists(): + continue + with Image.open(src) as im: + w, h = im.size + used_bases.add(basename) + maps_out.append({ + "image": f"maps/{basename}.webp", + "width": w, "height": h, + "label": basename, + "bosses": bosses if len(basenames) == 1 else [], + }) + + entry = { + "id": did, + "expansion": expansion, + "name": zone_name, + "acronym": dungeon.get("Acronym"), + "levelRange": dungeon.get("LevelRange"), + "playerLimit": dungeon.get("PlayerLimit"), + "location": join_location(dungeon.get("Location")), + "reputation": dungeon.get("Reputation"), + "maps": maps_out, + } + if not maps_out: + no_map.append(f"{expansion}/{did} (MapName={map_name})") + elif len(maps_out) > 1: + entry["unassignedBosses"] = bosses + dungeons.append(entry) + + dungeons.sort(key=lambda d: (d["expansion"], d["name"])) + + OUT_PATH.parent.mkdir(parents=True, exist_ok=True) + with OUT_PATH.open("w") as f: + json.dump({"dungeons": dungeons}, f, indent=2) + + mapped = sum(1 for d in dungeons if d["maps"]) + print(f"wrote {OUT_PATH} — {len(dungeons)} dungeons, {mapped} with maps") + if no_map: + print(f"\n{len(no_map)} AtlasLoot entries had no resolvable map:", file=sys.stderr) + for s in no_map: + print(f" {s}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_boss_alignment.py b/tools/check_boss_alignment.py new file mode 100644 index 0000000..c4fe2a6 --- /dev/null +++ b/tools/check_boss_alignment.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Render every classic dungeon's webp with AtlasLoot boss coords overlaid as +red rings, side-by-side with the bare texture. Output to /tmp/alignment/ for +human review. +""" +from __future__ import annotations +import json +import re +import sys +from pathlib import Path +from PIL import Image, ImageDraw + +ROOT = Path(__file__).resolve().parent.parent +WEB_MAPS = ROOT / "web" / "assets" / "maps" +DUNGEONS_JSON = ROOT / "web" / "assets" / "dungeons.json" +OUT_DIR = Path("/tmp/alignment") + + +def main() -> int: + if not DUNGEONS_JSON.exists(): + print("dungeons.json missing, run build_data.py first", file=sys.stderr) + return 1 + OUT_DIR.mkdir(parents=True, exist_ok=True) + + data = json.loads(DUNGEONS_JSON.read_text()) + rendered = [] + for d in data["dungeons"]: + if d.get("expansion") != "OriginalWoW": + continue + # Use bosses from the first map (single-floor) or unassignedBosses (multi) + bosses = d["maps"][0]["bosses"] if d["maps"] and d["maps"][0]["bosses"] else d.get("unassignedBosses", []) + if not d["maps"]: + continue + # Pick the first map for the alignment check + m = d["maps"][0] + src = WEB_MAPS / Path(m["image"]).name + if not src.exists(): + continue + + with Image.open(src) as im: + im = im.convert("RGBA") + W, H = im.size + draw = ImageDraw.Draw(im) + for b in bosses: + px = int((b["x"] / 100) * W) + py = int((b["y"] / 100) * H) + r = 80 + draw.ellipse([px - r, py - r, px + r, py + r], outline="red", width=14) + draw.ellipse([px - 4, py - 4, px + 4, py + 4], fill="red") + # Scale down for review + scale = 1024 / W + im_small = im.resize((1024, int(H * scale)), Image.LANCZOS) + out = OUT_DIR / f"{d['id']}.png" + im_small.save(out) + rendered.append((d["id"], d["name"], len(bosses), out)) + + print(f"Rendered {len(rendered)} dungeons → {OUT_DIR}") + for did, name, n, p in rendered: + print(f" {did:30s} {n:>2d} bosses → {p.name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/stitch_uprez.py b/tools/stitch_uprez.py new file mode 100644 index 0000000..724df72 --- /dev/null +++ b/tools/stitch_uprez.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +Stitch WoWMapUprez_Dungeons 4×3 BLP tile grids and emit BOTH a hi-res PNG +(lossless intermediate) and a web-ready WebP per dungeon-floor in parallel. + +Inputs: data/uprez/Interface/Worldmap//_.blp +Outputs: data/maps_png_hires/[_floor].png (lossless, ~8 MB) + web/assets/maps/[_floor].webp (q=82, ~600 KB-1.5 MB) +""" +from __future__ import annotations +import argparse +import os +import re +import sys +from collections import defaultdict +from concurrent.futures import ProcessPoolExecutor, as_completed +from pathlib import Path +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent +SRC = ROOT / "data" / "uprez" / "Interface" / "Worldmap" +DST_PNG = ROOT / "data" / "maps_png_hires" +DST_WEBP = ROOT / "web" / "assets" / "maps" + +# Two tile-naming conventions: +# _.blp — multi-level dungeons (e.g. Naxxramas2_5.blp) +# .blp — single-level dungeons (e.g. ZulFarrak5.blp) +# In both cases, idx is 1-12 (12 tiles per level in a 4×3 grid). +TILE_IDX_RE = re.compile(r"^(?P.+?)(?P1[0-2]|[1-9])\.blp$", re.IGNORECASE) +FLOOR_SUFFIX_RE = re.compile(r"^(?P.+?)(?P\d+)_$") +COLS, ROWS = 4, 3 +TILES_PER_FLOOR = COLS * ROWS + + +def parse_tile(filename: str): + """Return (mapname, floor, idx) or None. floor=0 indicates the + "single-level" form with no explicit floor in the filename.""" + m = TILE_IDX_RE.match(filename) + if not m: + return None + prefix = m.group("prefix") + idx = int(m.group("idx")) + fm = FLOOR_SUFFIX_RE.match(prefix) + if fm: + return fm.group("map"), int(fm.group("floor")), idx + return prefix, 0, idx + + +def stitch_one(args): + out_basename, tile_paths, png_dir, webp_dir, max_width, quality, write_png = args + # Open tile #1 to learn dimensions; assume uniform. + with Image.open(tile_paths[1]) as t1: + tw, th = t1.size + canvas = Image.new("RGBA", (COLS * tw, ROWS * th)) + for idx in range(1, TILES_PER_FLOOR + 1): + with Image.open(tile_paths[idx]) as tile: + col = (idx - 1) % COLS + row = (idx - 1) // COLS + canvas.paste(tile.convert("RGBA"), (col * tw, row * th)) + + if write_png: + canvas.save(png_dir / f"{out_basename}.png", optimize=False) + + # WebP needs RGB and may want a downscale + img = canvas.convert("RGB") + if max_width and img.width > max_width: + ratio = max_width / img.width + img = img.resize((max_width, int(img.height * ratio)), Image.LANCZOS) + img.save(webp_dir / f"{out_basename}.webp", "WEBP", quality=quality, method=4) + return out_basename, canvas.size + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--max-width", type=int, default=4096, + help="downscale WebP to this max width (default: keep 4096)") + ap.add_argument("--quality", type=int, default=82) + ap.add_argument("--workers", type=int, default=max(1, (os.cpu_count() or 4) - 1)) + ap.add_argument("--no-png", action="store_true", + help="skip lossless PNG intermediate (save disk + time)") + args = ap.parse_args() + + if not SRC.exists(): + print(f"missing {SRC} — clone WoWMapUprez_Dungeons first", file=sys.stderr) + return 1 + DST_PNG.mkdir(parents=True, exist_ok=True) + DST_WEBP.mkdir(parents=True, exist_ok=True) + + # Group: { (mapname, floor): {idx: path} }; floor=0 is "no floor in filename" + groups: dict[tuple[str, int], dict[int, Path]] = defaultdict(dict) + for dungeon_dir in sorted(SRC.iterdir()): + if not dungeon_dir.is_dir(): + continue + for blp in dungeon_dir.glob("*.blp"): + parsed = parse_tile(blp.name) + if not parsed: + continue + mapname, floor, idx = parsed + groups[(mapname, floor)][idx] = blp + + floors_per_map: dict[str, set[int]] = defaultdict(set) + for (mapname, floor) in groups: + floors_per_map[mapname].add(floor) + + jobs = [] + skipped = [] + for (mapname, floor), tiles in sorted(groups.items()): + if len(tiles) != TILES_PER_FLOOR: + skipped.append(f"{mapname} floor {floor}: {len(tiles)}/{TILES_PER_FLOOR}") + continue + floors = floors_per_map[mapname] + if len(floors) == 1: + suffix = "" # only one variant + elif floor == 0: + suffix = "_overview" # mixed: 0 + others + else: + suffix = f"_floor{floor}" + out_base = f"{mapname}{suffix}" + jobs.append((out_base, tiles, DST_PNG, DST_WEBP, args.max_width, args.quality, not args.no_png)) + + print(f"stitching {len(jobs)} dungeon-floors with {args.workers} workers...") + done = 0 + with ProcessPoolExecutor(max_workers=args.workers) as pool: + futures = [pool.submit(stitch_one, j) for j in jobs] + for fut in as_completed(futures): + try: + base, size = fut.result() + done += 1 + if done % 20 == 0 or done == len(jobs): + print(f" {done}/{len(jobs)} (latest: {base} {size[0]}×{size[1]})") + except Exception as e: + print(f" FAIL: {e}", file=sys.stderr) + + print(f"done: {done}/{len(jobs)} stitched") + if skipped: + print(f"\nskipped {len(skipped)} (incomplete tile sets):", file=sys.stderr) + for s in skipped[:10]: + print(f" {s}", file=sys.stderr) + if len(skipped) > 10: + print(f" ... and {len(skipped) - 10} more", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/web/app.js b/web/app.js new file mode 100644 index 0000000..7436180 --- /dev/null +++ b/web/app.js @@ -0,0 +1,616 @@ +// Ascension M+ Route Planner — vanilla JS, single-file SPA. + +const SVG_NS = "http://www.w3.org/2000/svg"; + +const state = { + dungeons: [], + current: null, // selected dungeon + floorIndex: 0, // index into dungeon.maps + tool: "route", // "route" | "pull" + // routes/pulls keyed by `${dungeonId}::${floorIndex}` + routes: {}, // { key: [{x,y}] } + pulls: {}, // { key: [{x,y,label?}] } + history: [], + drag: null, // { kind, key, idx } + view: { scale: 1, tx: 0, ty: 0 }, // canvas pan/zoom + pan: null, // active pan drag {x0,y0,tx0,ty0} +}; + +const $ = (id) => document.getElementById(id); + +async function init() { + const r = await fetch("assets/dungeons.json", { cache: "no-cache" }); + const data = await r.json(); + state.dungeons = data.dungeons; + + populateExpansionFilter(); + renderDungeonList(); + hookEvents(); + loadFromHash(); + + if (!state.current && state.dungeons.length) { + selectDungeon(state.dungeons[0].id); + } +} + +function populateExpansionFilter() { + const sel = $("expansion-filter"); + const exps = [...new Set(state.dungeons.map((d) => d.expansion))].sort(); + for (const e of exps) { + const opt = document.createElement("option"); + opt.value = e; + opt.textContent = labelExpansion(e); + sel.appendChild(opt); + } +} + +function labelExpansion(e) { + return ({ + OriginalWoW: "Classic", + BurningCrusade: "Burning Crusade", + WrathoftheLichKing: "Wrath of the Lich King", + Other: "Other / Unmapped", + })[e] || e; +} + +function renderDungeonList() { + const ul = $("dungeon-list"); + const q = $("search").value.trim().toLowerCase(); + const expFilter = $("expansion-filter").value; + ul.innerHTML = ""; + + const filtered = state.dungeons.filter((d) => { + if (expFilter && d.expansion !== expFilter) return false; + if (!q) return true; + return ( + d.name.toLowerCase().includes(q) || + (d.acronym || "").toLowerCase().includes(q) || + d.id.toLowerCase().includes(q) + ); + }); + + let lastExp = null; + for (const d of filtered) { + if (d.expansion !== lastExp) { + const h = document.createElement("li"); + h.className = "group-header"; + h.textContent = labelExpansion(d.expansion); + ul.appendChild(h); + lastExp = d.expansion; + } + const li = document.createElement("li"); + li.dataset.id = d.id; + li.dataset.expansion = d.expansion; + if (state.current && d.id === state.current.id && d.expansion === state.current.expansion) { + li.classList.add("active"); + } + const name = document.createElement("span"); + name.textContent = d.name; + const acr = document.createElement("span"); + acr.className = "acronym"; + acr.textContent = d.acronym || ""; + li.append(name, acr); + li.onclick = () => selectDungeon(d.id, d.expansion); + ul.appendChild(li); + } +} + +function selectDungeon(id, expansion) { + const d = state.dungeons.find( + (x) => x.id === id && (!expansion || x.expansion === expansion) + ); + if (!d) return; + state.current = d; + state.floorIndex = 0; + renderDungeonList(); + renderViewer(); + updateHash(); +} + +function currentKey() { + return state.current ? `${state.current.expansion}::${state.current.id}::${state.floorIndex}` : null; +} + +function currentMap() { + return state.current?.maps[state.floorIndex] || null; +} + +function renderViewer() { + const d = state.current; + if (!d) return; + $("dungeon-name").textContent = d.name; + const meta = [d.acronym, d.levelRange && `Lv ${d.levelRange}`, d.location, d.playerLimit && `${d.playerLimit} players`] + .filter(Boolean) + .join(" · "); + $("dungeon-meta").textContent = meta; + + // Floor tabs + const tabs = $("floor-tabs"); + tabs.innerHTML = ""; + if (d.maps.length > 1) { + d.maps.forEach((m, i) => { + const b = document.createElement("button"); + b.textContent = humanizeLabel(m.label); + if (i === state.floorIndex) b.classList.add("active"); + b.onclick = () => { state.floorIndex = i; renderViewer(); updateHash(); }; + tabs.appendChild(b); + }); + } + + // Map image + const m = currentMap(); + const img = $("map-img"); + if (m) { + img.src = "assets/" + m.image; + img.width = m.width; + img.height = m.height; + const svg = $("overlay"); + svg.setAttribute("width", m.width); + svg.setAttribute("height", m.height); + img.onload = () => { fitToScreen(); renderOverlay(); }; + if (img.complete) { fitToScreen(); } + } else { + img.removeAttribute("src"); + } + + renderOverlay(); + renderInfoPane(); +} + +function humanizeLabel(s) { + return s + .replace(/^CFR/, "") + .replace(/^HC/, "") + .replace(/^CoT/, "CoT ") + .replace(/^TempestKeep/, "TK ") + .replace(/^SM/, "SM ") + .replace(/^Auch/, "Auch ") + .replace(/^FH/, "") + .replace(/^Ulduar([A-Z])/, "Ulduar $1") + .replace(/^IcecrownCitadel([A-Z])/, "ICC $1") + .replace(/([a-z])([A-Z])/g, "$1 $2") + .trim(); +} + +function renderOverlay() { + const svg = $("overlay"); + svg.innerHTML = ""; + const m = currentMap(); + if (!m) return; + // SVG works in image-pixel space, so circles stay circular regardless of + // the map's aspect ratio. Coords on disk are 0-100 percent (AtlasLoot + // convention) and converted at render/click time. + svg.setAttribute("viewBox", `0 0 ${m.width} ${m.height}`); + svg.setAttribute("preserveAspectRatio", "none"); + + const key = currentKey(); + const wps = state.routes[key] || []; + const pulls = state.pulls[key] || []; + const px = (p) => ({ x: (p.x / 100) * m.width, y: (p.y / 100) * m.height }); + + // route line first (under pins) + if (wps.length > 1) { + const path = document.createElementNS(SVG_NS, "polyline"); + path.setAttribute("class", "route-line"); + path.setAttribute( + "points", + wps.map((p) => { const q = px(p); return `${q.x},${q.y}`; }).join(" ") + ); + svg.appendChild(path); + } + + // boss pins + for (const b of m.bosses || []) { + const q = px(b); + const g = document.createElementNS(SVG_NS, "g"); + g.setAttribute("class", "boss-pin"); + g.setAttribute("transform", `translate(${q.x},${q.y})`); + const c = document.createElementNS(SVG_NS, "circle"); + c.setAttribute("r", 32); + g.appendChild(c); + const t = document.createElementNS(SVG_NS, "title"); + t.textContent = b.name; + g.appendChild(t); + svg.appendChild(g); + } + + // pull markers + pulls.forEach((p, i) => { + const q = px(p); + svg.appendChild(makePin(q.x, q.y, i + 1, "pull", "pull", i)); + }); + + // user waypoints (on top) + wps.forEach((p, i) => { + const q = px(p); + svg.appendChild(makePin(q.x, q.y, i + 1, "waypoint", "route", i)); + }); + + svg.onclick = onCanvasClick; +} + +function makePin(x, y, label, cssClass, kind, idx) { + // x/y here are SVG pixel coords (already converted from percent). + const g = document.createElementNS(SVG_NS, "g"); + g.setAttribute("class", cssClass); + g.setAttribute("transform", `translate(${x},${y})`); + const c = document.createElementNS(SVG_NS, "circle"); + c.setAttribute("r", 36); + g.appendChild(c); + const t = document.createElementNS(SVG_NS, "text"); + t.setAttribute("y", 14); + t.setAttribute("font-size", 36); + t.textContent = label; + g.appendChild(t); + + g.addEventListener("pointerdown", (e) => { + e.stopPropagation(); + state.drag = { kind, idx }; + g.classList.add("dragging"); + g.setPointerCapture(e.pointerId); + }); + g.addEventListener("pointermove", (e) => { + if (!state.drag || state.drag.idx !== idx || state.drag.kind !== kind) return; + const m = currentMap(); + const pt = svgPointFromEvent(e); // percent (0-100) + const arr = kind === "route" ? state.routes[currentKey()] : state.pulls[currentKey()]; + arr[idx] = { ...arr[idx], x: pt.x, y: pt.y }; + const px = (pt.x / 100) * m.width; + const py = (pt.y / 100) * m.height; + g.setAttribute("transform", `translate(${px},${py})`); + if (kind === "route") { + const polyline = document.querySelector("#overlay .route-line"); + if (polyline) { + polyline.setAttribute( + "points", + state.routes[currentKey()] + .map((p) => `${(p.x / 100) * m.width},${(p.y / 100) * m.height}`) + .join(" ") + ); + } + } + }); + g.addEventListener("pointerup", () => { + if (!state.drag) return; + state.drag = null; + g.classList.remove("dragging"); + pushHistory(); + renderInfoPane(); + updateHash(); + }); + g.addEventListener("contextmenu", (e) => { + e.preventDefault(); + removePoint(kind, idx); + }); + return g; +} + +function onCanvasClick(e) { + if (state.drag) return; + const pt = svgPointFromEvent(e); + const key = currentKey(); + if (state.tool === "route") { + state.routes[key] = state.routes[key] || []; + state.routes[key].push({ x: pt.x, y: pt.y }); + } else if (state.tool === "pull") { + state.pulls[key] = state.pulls[key] || []; + state.pulls[key].push({ x: pt.x, y: pt.y }); + } + pushHistory(); + renderOverlay(); + renderInfoPane(); + updateHash(); +} + +function svgPointFromEvent(e) { + // Convert client coords → 0-100% of map (same coord space as AtlasLoot) + const svg = $("overlay"); + const rect = svg.getBoundingClientRect(); + const x = ((e.clientX - rect.left) / rect.width) * 100; + const y = ((e.clientY - rect.top) / rect.height) * 100; + return { x: clamp(x), y: clamp(y) }; +} +function clamp(v) { return Math.max(0, Math.min(100, +v.toFixed(2))); } + +/* --- pan / zoom ---------------------------------------------------------- */ + +function applyView() { + const stage = $("canvas-stage"); + stage.style.transform = + `translate(${state.view.tx}px, ${state.view.ty}px) scale(${state.view.scale})`; +} + +function fitToScreen() { + const m = currentMap(); + const host = $("canvas-host"); + if (!m || !host) return; + const pad = 24; + const sx = (host.clientWidth - pad * 2) / m.width; + const sy = (host.clientHeight - pad * 2) / m.height; + const s = Math.min(sx, sy); + // Center on host: stage origin is at host center (50/50 in CSS), so + // shift by -half-image-size * scale to center the image visually. + state.view.scale = s; + state.view.tx = -(m.width * s) / 2; + state.view.ty = -(m.height * s) / 2; + applyView(); +} + +function zoomBy(factor, anchorX, anchorY) { + const host = $("canvas-host"); + const rect = host.getBoundingClientRect(); + // Anchor defaults to host center + const ax = anchorX ?? rect.width / 2; + const ay = anchorY ?? rect.height / 2; + const cx = rect.width / 2 + state.view.tx; + const cy = rect.height / 2 + state.view.ty; + const dx = ax - cx; + const dy = ay - cy; + const before = state.view.scale; + const after = Math.max(0.05, Math.min(20, before * factor)); + const ratio = after / before; + state.view.scale = after; + state.view.tx -= dx * (ratio - 1); + state.view.ty -= dy * (ratio - 1); + applyView(); +} + +function hookCanvasPanZoom() { + const host = $("canvas-host"); + + host.addEventListener("wheel", (e) => { + e.preventDefault(); + const rect = host.getBoundingClientRect(); + const ax = e.clientX - rect.left; + const ay = e.clientY - rect.top; + const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15; + zoomBy(factor, ax, ay); + }, { passive: false }); + + host.addEventListener("pointerdown", (e) => { + // Skip if click is on a pin (those handle their own pointerdown). + if (e.target.closest("g.waypoint, g.pull, g.boss-pin")) return; + // Skip on SVG clicks (those are handled by onCanvasClick to drop pins). + // Use middle mouse, shift-drag, or right-drag for pan; plain left-click on + // empty area also pans if it falls outside the SVG. + const onSvg = e.target.closest("#overlay"); + const isPanIntent = e.button === 1 || e.shiftKey || e.button === 2 || !onSvg; + if (!isPanIntent) return; + e.preventDefault(); + state.pan = { + x0: e.clientX, y0: e.clientY, + tx0: state.view.tx, ty0: state.view.ty, + }; + host.classList.add("panning"); + host.setPointerCapture(e.pointerId); + }); + host.addEventListener("pointermove", (e) => { + if (!state.pan) return; + state.view.tx = state.pan.tx0 + (e.clientX - state.pan.x0); + state.view.ty = state.pan.ty0 + (e.clientY - state.pan.y0); + applyView(); + }); + host.addEventListener("pointerup", () => { + state.pan = null; + host.classList.remove("panning"); + }); + host.addEventListener("contextmenu", (e) => { + // suppress the menu when shift-right-clicking to allow pan + if (state.pan) e.preventDefault(); + }); + + // Buttons + $("zoom-in").addEventListener("click", () => zoomBy(1.25)); + $("zoom-out").addEventListener("click", () => zoomBy(1 / 1.25)); + $("zoom-reset").addEventListener("click", fitToScreen); + window.addEventListener("resize", () => { + // Refit on first resize, but keep user zoom afterwards. + if (!state._userZoomed) fitToScreen(); + }); +} + +function removePoint(kind, idx) { + const key = currentKey(); + const arr = kind === "route" ? state.routes[key] : state.pulls[key]; + if (!arr) return; + arr.splice(idx, 1); + pushHistory(); + renderOverlay(); + renderInfoPane(); + updateHash(); +} + +function renderInfoPane() { + const m = currentMap(); + const bossUl = $("boss-list"); + bossUl.innerHTML = ""; + if (m) { + for (const b of m.bosses || []) { + const li = document.createElement("li"); + li.innerHTML = `${escapeHtml(b.name)}`; + bossUl.appendChild(li); + } + if (!m.bosses?.length) { + const li = document.createElement("li"); + li.innerHTML = `no boss data`; + bossUl.appendChild(li); + } + } + + const wpUl = $("waypoint-list"); + wpUl.innerHTML = ""; + const wps = state.routes[currentKey()] || []; + wps.forEach((p, i) => { + const li = document.createElement("li"); + li.innerHTML = ` + + #${i + 1} (${p.x.toFixed(1)}, ${p.y.toFixed(1)}) + + `; + li.querySelector("button").onclick = () => removePoint("route", i); + wpUl.appendChild(li); + }); + if (!wps.length) { + const li = document.createElement("li"); + li.innerHTML = `click map to add`; + wpUl.appendChild(li); + } +} + +function escapeHtml(s) { + return s.replace(/[&<>"']/g, (c) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]) + ); +} + +function pushHistory() { + state.history.push(JSON.stringify({ routes: state.routes, pulls: state.pulls })); + if (state.history.length > 200) state.history.shift(); +} + +function undo() { + state.history.pop(); + const last = state.history[state.history.length - 1]; + if (last) { + const s = JSON.parse(last); + state.routes = s.routes; + state.pulls = s.pulls; + } else { + state.routes = {}; + state.pulls = {}; + } + renderOverlay(); + renderInfoPane(); + updateHash(); +} + +function clearCurrent() { + const key = currentKey(); + delete state.routes[key]; + delete state.pulls[key]; + pushHistory(); + renderOverlay(); + renderInfoPane(); + updateHash(); +} + +/* --- URL hash sharing ---------------------------------------------------- */ + +function updateHash() { + if (!state.current) return; + const payload = { + d: `${state.current.expansion}/${state.current.id}`, + f: state.floorIndex, + r: state.routes, + p: state.pulls, + }; + const json = JSON.stringify(payload); + const minified = compressForUrl(json); + const newHash = `#${minified}`; + if (location.hash !== newHash) { + history.replaceState(null, "", location.pathname + location.search + newHash); + } +} + +function loadFromHash() { + const h = location.hash.slice(1); + if (!h) return; + try { + const json = decompressFromUrl(h); + const p = JSON.parse(json); + if (p.d) { + const [expansion, id] = p.d.split("/"); + const d = state.dungeons.find((x) => x.id === id && x.expansion === expansion); + if (d) { + state.current = d; + state.floorIndex = p.f || 0; + state.routes = p.r || {}; + state.pulls = p.p || {}; + renderViewer(); + } + } + } catch (e) { + console.warn("hash parse failed", e); + } +} + +// Tiny base64-of-utf8 — small payloads make for OK-length URLs. +function compressForUrl(s) { + return btoa(unescape(encodeURIComponent(s))).replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"); +} +function decompressFromUrl(s) { + s = s.replace(/-/g, "+").replace(/_/g, "/"); + while (s.length % 4) s += "="; + return decodeURIComponent(escape(atob(s))); +} + +function shareUrl() { + updateHash(); + navigator.clipboard?.writeText(location.href); + toast("Link copied"); +} + +function exportJson() { + const d = state.current; + if (!d) return; + const payload = { + dungeon: { id: d.id, expansion: d.expansion, name: d.name }, + floors: d.maps.map((m, i) => ({ + label: m.label, + route: state.routes[`${d.expansion}::${d.id}::${i}`] || [], + pulls: state.pulls[`${d.expansion}::${d.id}::${i}`] || [], + })), + }; + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${d.id}-route.json`; + a.click(); + URL.revokeObjectURL(url); +} + +function toast(msg) { + let t = document.querySelector(".toast"); + if (!t) { + t = document.createElement("div"); + t.className = "toast"; + document.body.appendChild(t); + } + t.textContent = msg; + t.classList.add("show"); + clearTimeout(toast._h); + toast._h = setTimeout(() => t.classList.remove("show"), 1500); +} + +/* --- events -------------------------------------------------------------- */ + +function hookEvents() { + $("search").addEventListener("input", renderDungeonList); + $("expansion-filter").addEventListener("change", renderDungeonList); + $("undo").addEventListener("click", undo); + $("clear").addEventListener("click", clearCurrent); + $("share").addEventListener("click", shareUrl); + $("export").addEventListener("click", exportJson); + $("tool-route").addEventListener("click", () => setTool("route")); + $("tool-pull").addEventListener("click", () => setTool("pull")); + document.addEventListener("keydown", (e) => { + if ((e.metaKey || e.ctrlKey) && e.key === "z") { + e.preventDefault(); + undo(); + } + }); + window.addEventListener("hashchange", loadFromHash); + hookCanvasPanZoom(); +} + +function setTool(t) { + state.tool = t; + $("tool-route").classList.toggle("active", t === "route"); + $("tool-pull").classList.toggle("active", t === "pull"); +} + +init().catch((e) => { + console.error(e); + document.body.innerHTML = `
init failed: ${e.message}
`; +}); diff --git a/web/assets/dungeons.json b/web/assets/dungeons.json new file mode 100644 index 0000000..d5f88eb --- /dev/null +++ b/web/assets/dungeons.json @@ -0,0 +1,2597 @@ +{ + "dungeons": [ + { + "id": "BlackfathomDeeps", + "expansion": "OriginalWoW", + "name": "Blackfathom Deeps", + "acronym": "BFD", + "levelRange": "20-28", + "playerLimit": "5", + "location": "Ashenvale", + "reputation": null, + "maps": [ + { + "image": "maps/BlackFathomDeeps_floor1.webp", + "width": 4096, + "height": 3072, + "label": "BlackFathomDeeps_floor1", + "bosses": [] + }, + { + "image": "maps/BlackFathomDeeps_floor2.webp", + "width": 4096, + "height": 3072, + "label": "BlackFathomDeeps_floor2", + "bosses": [] + }, + { + "image": "maps/BlackFathomDeeps_floor3.webp", + "width": 4096, + "height": 3072, + "label": "BlackFathomDeeps_floor3", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Ghamoo-ra", + "x": 32.9, + "y": 59.9, + "type": "dungeonskull" + }, + { + "name": "Lorgalis Manuscript", + "x": 33, + "y": 41, + "type": "questturnin" + }, + { + "name": "Lady Sarevess", + "x": 12, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Argent Guard Thaelrid ", + "x": 21, + "y": 74, + "type": "boss" + }, + { + "name": "Gelihast", + "x": 53, + "y": 57, + "type": "dungeonskull" + }, + { + "name": "Lorgus Jett (Varies)", + "x": 46, + "y": 66.5, + "type": "boss" + }, + { + "name": "Old Serra'kis", + "x": 53.4, + "y": 70, + "type": "dungeonskull" + }, + { + "name": "Twilight Lord Kelris", + "x": 51, + "y": 81, + "type": "dungeonskull" + }, + { + "name": "Aku'mai", + "x": 81.7, + "y": 84.5, + "type": "dungeonskull" + }, + { + "name": "Old Serra'kis", + "x": 59.4, + "y": 31.4, + "type": "dungeonskull" + } + ] + }, + { + "id": "BlackrockDepths", + "expansion": "OriginalWoW", + "name": "Blackrock Depths", + "acronym": "BRD", + "levelRange": "52-58", + "playerLimit": "5", + "location": "Searing Gorge / Burning Steppes", + "reputation": null, + "maps": [ + { + "image": "maps/BlackrockDepths_floor1.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockDepths_floor1", + "bosses": [] + }, + { + "image": "maps/BlackrockDepths_floor2.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockDepths_floor2", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Lord Roccor", + "x": 57, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Kharan Mighthammer", + "x": 52, + "y": 82, + "type": "boss" + }, + { + "name": "Commander Gor'shak ", + "x": 54, + "y": 87, + "type": "questturnin" + }, + { + "name": "Marshal Windsor", + "x": 53, + "y": 94, + "type": "questturnin" + }, + { + "name": "High Interrogator Gerstahn ", + "x": 48, + "y": 91, + "type": "dungeonskull" + }, + { + "name": "Ring of Law", + "x": 51, + "y": 61, + "type": "dungeonskull" + }, + { + "name": "Pyromancer Loregrain", + "x": 57, + "y": 69, + "type": "dungeonskull" + }, + { + "name": "Fineous Darkvire ", + "x": 64, + "y": 21, + "type": "boss" + }, + { + "name": "Lord Incendius", + "x": 56, + "y": 31, + "type": "dungeonskull" + }, + { + "name": "Bael'Gar", + "x": 26, + "y": 53, + "type": "dungeonskull" + }, + { + "name": "The Vault", + "x": 60, + "y": 66, + "type": "dungeonskull" + }, + { + "name": "Ring of Law", + "x": 51, + "y": 89, + "type": "dungeonskull" + }, + { + "name": "Shadowforge Lock", + "x": 42, + "y": 91, + "type": "boss" + }, + { + "name": "General Angerforge", + "x": 36, + "y": 83, + "type": "dungeonskull" + }, + { + "name": "Golem Lord Argelmach", + "x": 36, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "The Grim Guzzler", + "x": 50, + "y": 63, + "type": "dungeonskull" + }, + { + "name": "Ambassador Flamelash", + "x": 55, + "y": 49, + "type": "dungeonskull" + }, + { + "name": "Panzor the Invincible (Rare)", + "x": 50, + "y": 35, + "type": "dungeonskull" + }, + { + "name": "Summoner's Tomb", + "x": 54, + "y": 25, + "type": "dungeonskull" + }, + { + "name": "The Lyceum", + "x": 67, + "y": 12, + "type": "dungeonskull" + }, + { + "name": "Magmus", + "x": 81, + "y": 12, + "type": "dungeonskull" + }, + { + "name": "Emperor Dagran Thaurissan", + "x": 91, + "y": 12, + "type": "dungeonskull" + }, + { + "name": "The Black Forge", + "x": 63, + "y": 25, + "type": "boss" + }, + { + "name": "Molten Core", + "x": 68, + "y": 36, + "type": "questturnin" + } + ] + }, + { + "id": "BlackrockSpire", + "expansion": "OriginalWoW", + "name": "Blackrock Spire", + "acronym": "LBRS", + "levelRange": "54-60", + "playerLimit": "5", + "location": "Searing Gorge / Burning Steppes", + "reputation": null, + "maps": [ + { + "image": "maps/BlackrockSpire_floor1.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor1", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor2.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor2", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor3.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor3", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor4.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor4", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor5.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor5", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor6.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor6", + "bosses": [] + }, + { + "image": "maps/BlackrockSpire_floor7.webp", + "width": 4096, + "height": 3072, + "label": "BlackrockSpire_floor7", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "War Master Voone", + "x": 52, + "y": 54, + "type": "dungeonskull" + }, + { + "name": "Mother Smolderweb", + "x": 64, + "y": 70, + "type": "dungeonskull" + }, + { + "name": "Burning Felguard (Rare, Summon)", + "x": 43, + "y": 54, + "type": "dungeonskull" + }, + { + "name": "Shadow Hunter Vosh'gajin", + "x": 56, + "y": 70, + "type": "dungeonskull" + }, + { + "name": "Vaelan (Upper)", + "x": 52, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Warosh (Wanders)", + "x": 61, + "y": 43, + "type": "dungeonskull" + }, + { + "name": "Bijou", + "x": 63, + "y": 56, + "type": "boss" + }, + { + "name": "Roughshod Pike", + "x": 67, + "y": 58, + "type": "boss" + }, + { + "name": "Spirestone Butcher (Rare)", + "x": 51, + "y": 58, + "type": "dungeonskull" + }, + { + "name": "Highlord Omokk", + "x": 39, + "y": 60, + "type": "dungeonskull" + }, + { + "name": "Human Remains (Lower)", + "x": 43, + "y": 56, + "type": "boss" + }, + { + "name": "Urok's Tribute Pile", + "x": 46, + "y": 55, + "type": "dungeonskull" + }, + { + "name": "Bannok Grimaxe (Rare)", + "x": 46, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Quartermaster Zigris ", + "x": 55, + "y": 85, + "type": "dungeonskull" + }, + { + "name": "Halycon", + "x": 38, + "y": 85, + "type": "boss" + }, + { + "name": "Ghok Bashguud (Rare)", + "x": 35, + "y": 68, + "type": "dungeonskull" + }, + { + "name": "Pyroguard Emberseer", + "x": 31, + "y": 27, + "type": "dungeonskull" + }, + { + "name": "Solakar Flamewreath", + "x": 39, + "y": 39, + "type": "dungeonskull" + }, + { + "name": "Darkstone Tablet", + "x": 32, + "y": 42, + "type": "boss" + }, + { + "name": "Overlord Wyrmthalak", + "x": 58, + "y": 64, + "type": "dungeonskull" + }, + { + "name": "Jed Runewatcher (Rare)", + "x": 38, + "y": 24, + "type": "dungeonskull" + }, + { + "name": "Goraluk Anvilcrack ", + "x": 33, + "y": 27, + "type": "dungeonskull" + }, + { + "name": "Warchief Rend Blackhand", + "x": 49, + "y": 27, + "type": "dungeonskull" + }, + { + "name": "Awbee", + "x": 49, + "y": 36, + "type": "boss" + }, + { + "name": "The Beast", + "x": 64, + "y": 32, + "type": "dungeonskull" + }, + { + "name": "General Drakkisath", + "x": 36, + "y": 50, + "type": "dungeonskull" + }, + { + "name": "Blackwing Lair", + "x": 55, + "y": 22, + "type": "boss" + } + ] + }, + { + "id": "BlackwingLair", + "expansion": "OriginalWoW", + "name": "Blackwing Lair", + "acronym": "BWL", + "levelRange": "60-70", + "playerLimit": "10-25", + "location": "Searing Gorge / Burning Steppes", + "reputation": null, + "maps": [ + { + "image": "maps/BlackwingLair_floor1.webp", + "width": 4096, + "height": 3072, + "label": "BlackwingLair_floor1", + "bosses": [] + }, + { + "image": "maps/BlackwingLair_floor2.webp", + "width": 4096, + "height": 3072, + "label": "BlackwingLair_floor2", + "bosses": [] + }, + { + "image": "maps/BlackwingLair_floor3.webp", + "width": 4096, + "height": 3072, + "label": "BlackwingLair_floor3", + "bosses": [] + }, + { + "image": "maps/BlackwingLair_floor4.webp", + "width": 4096, + "height": 3072, + "label": "BlackwingLair_floor4", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Razorgore the Untamed", + "x": 33, + "y": 28, + "type": "dungeonskull" + }, + { + "name": "Vaelastrasz the Corrupt", + "x": 42, + "y": 62, + "type": "dungeonskull" + }, + { + "name": "Broodlord Lashlayer", + "x": 48, + "y": 55, + "type": "dungeonskull" + }, + { + "name": "Broodlord Lashlayer", + "x": 50, + "y": 59, + "type": "dungeonskull" + }, + { + "name": "Draconic for Dummies (Chapter VII)", + "x": 32, + "y": 43, + "type": "boss" + }, + { + "name": "Master Elemental Shaper Krixix", + "x": 40, + "y": 41, + "type": "boss" + }, + { + "name": "Firemaw", + "x": 40, + "y": 38, + "type": "dungeonskull" + }, + { + "name": "Ebonroc", + "x": 34, + "y": 35, + "type": "dungeonskull" + }, + { + "name": "Flamegor", + "x": 40, + "y": 47, + "type": "dungeonskull" + }, + { + "name": "Chromaggus", + "x": 41, + "y": 72, + "type": "dungeonskull" + }, + { + "name": "Nefarian", + "x": 72, + "y": 72, + "type": "dungeonskull" + } + ] + }, + { + "id": "DireMaul", + "expansion": "OriginalWoW", + "name": "Dire Maul", + "acronym": "DM", + "levelRange": "57-60", + "playerLimit": "5", + "location": "Feralas", + "reputation": null, + "maps": [ + { + "image": "maps/DireMaul_overview.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_overview", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor1.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor1", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor2.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor2", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor3.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor3", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor4.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor4", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor5.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor5", + "bosses": [] + }, + { + "image": "maps/DireMaul_floor6.webp", + "width": 4096, + "height": 3072, + "label": "DireMaul_floor6", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Guard Mol'dar", + "x": 69.9, + "y": 76, + "type": "dungeonskull" + }, + { + "name": "Stomper Kreeg ", + "x": 61, + "y": 68, + "type": "dungeonskull" + }, + { + "name": "Guard Fengus", + "x": 42.9, + "y": 77.6, + "type": "dungeonskull" + }, + { + "name": "Guard Slip'kik", + "x": 26.5, + "y": 57.8, + "type": "dungeonskull" + }, + { + "name": "Captain Kromcrush", + "x": 31.8, + "y": 51.4, + "type": "dungeonskull" + }, + { + "name": "King Gordok", + "x": 32, + "y": 27.7, + "type": "dungeonskull" + }, + { + "name": "Cho'Rush the Observer", + "x": 31.4, + "y": 26.2, + "type": "boss" + }, + { + "name": "Pylons", + "x": 70, + "y": 51, + "type": "boss" + }, + { + "name": "Pylons", + "x": 40, + "y": 22, + "type": "boss" + }, + { + "name": "Pylons", + "x": 36, + "y": 80, + "type": "boss" + }, + { + "name": "Illyanna Ravenoak", + "x": 20, + "y": 78, + "type": "dungeonskull" + }, + { + "name": "Tendris Warpwood", + "x": 33.4, + "y": 53.5, + "type": "dungeonskull" + }, + { + "name": "Magister Kalendris", + "x": 32, + "y": 45.3, + "type": "dungeonskull" + }, + { + "name": "Tsu'zee (Rare)", + "x": 68.3, + "y": 23.9, + "type": "dungeonskull" + }, + { + "name": "Immol'thar", + "x": 35.2, + "y": 58.1, + "type": "dungeonskull" + }, + { + "name": "Lord Hel'nurath (Summon)", + "x": 14.9, + "y": 55.7, + "type": "dungeonskull" + }, + { + "name": "Prince Tortheldrin", + "x": 61.4, + "y": 15.1, + "type": "dungeonskull" + }, + { + "name": "Pylons", + "x": 44, + "y": 35, + "type": "boss" + }, + { + "name": "Pylons", + "x": 34, + "y": 81, + "type": "boss" + }, + { + "name": "Hydrospawn", + "x": 41, + "y": 49.2, + "type": "dungeonskull" + }, + { + "name": "Lethtendris", + "x": 44.5, + "y": 46.6, + "type": "dungeonskull" + }, + { + "name": "Old Ironbark", + "x": 40, + "y": 68, + "type": "boss" + }, + { + "name": "Alzzin the Wildshaper", + "x": 57.3, + "y": 28.9, + "type": "dungeonskull" + } + ] + }, + { + "id": "Gnomeregan", + "expansion": "OriginalWoW", + "name": "Gnomeregan", + "acronym": "Gnome", + "levelRange": "24-32", + "playerLimit": "5", + "location": "Dun Morogh", + "reputation": null, + "maps": [ + { + "image": "maps/Gnomeregan_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Gnomeregan_floor1", + "bosses": [] + }, + { + "image": "maps/Gnomeregan_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Gnomeregan_floor2", + "bosses": [] + }, + { + "image": "maps/Gnomeregan_floor3.webp", + "width": 4096, + "height": 3072, + "label": "Gnomeregan_floor3", + "bosses": [] + }, + { + "image": "maps/Gnomeregan_floor4.webp", + "width": 4096, + "height": 3072, + "label": "Gnomeregan_floor4", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Blastmaster Emi Shortfuse", + "x": 77, + "y": 68, + "type": "dungeonskull" + }, + { + "name": "Viscous Fallout", + "x": 50, + "y": 50, + "type": "dungeonskull" + }, + { + "name": "Clean Room", + "x": 64, + "y": 64, + "type": "boss" + }, + { + "name": "Kernobee", + "x": 72, + "y": 68, + "type": "boss" + }, + { + "name": "Electrocutioner 6000", + "x": 25, + "y": 69, + "type": "dungeonskull" + }, + { + "name": "Crowd Pummeler 9-60 (Upper)", + "x": 44, + "y": 87, + "type": "dungeonskull" + }, + { + "name": "Dark Iron Ambassador", + "x": 29, + "y": 48, + "type": "boss" + }, + { + "name": "Mekgineer Thermaplugg", + "x": 32, + "y": 30, + "type": "dungeonskull" + } + ] + }, + { + "id": "Maraudon", + "expansion": "OriginalWoW", + "name": "Maraudon", + "acronym": "Mara", + "levelRange": "42-51", + "playerLimit": "5", + "location": "Desolace", + "reputation": null, + "maps": [ + { + "image": "maps/Maraudon_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Maraudon_floor1", + "bosses": [] + }, + { + "image": "maps/Maraudon_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Maraudon_floor2", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Veng ", + "x": 60, + "y": 24, + "type": "boss" + }, + { + "name": "Noxxion", + "x": 36, + "y": 15, + "type": "dungeonskull" + }, + { + "name": "Razorlash", + "x": 18, + "y": 33, + "type": "dungeonskull" + }, + { + "name": "Maraudos ", + "x": 53, + "y": 74, + "type": "boss" + }, + { + "name": "Lord Vyletongue", + "x": 37, + "y": 72, + "type": "dungeonskull" + }, + { + "name": "Celebras the Cursed", + "x": 10, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Celebras the Cursed", + "x": 25, + "y": 15, + "type": "dungeonskull" + }, + { + "name": "Landslide", + "x": 40.6, + "y": 48.2, + "type": "boss" + }, + { + "name": "Tinkerer Gizlock", + "x": 48.4, + "y": 68.6, + "type": "boss" + }, + { + "name": "Rotgrip", + "x": 33.3, + "y": 77.1, + "type": "dungeonskull" + }, + { + "name": "Princess Theradras", + "x": 24.2, + "y": 80.4, + "type": "dungeonskull" + } + ] + }, + { + "id": "MoltenCore", + "expansion": "OriginalWoW", + "name": "Molten Core", + "acronym": "MC", + "levelRange": "60-70", + "playerLimit": "10-25", + "location": "Searing Gorge / Burning Steppes", + "reputation": "Hydraxian Waterlords", + "maps": [ + { + "image": "maps/MoltenCore.webp", + "width": 4096, + "height": 3072, + "label": "MoltenCore", + "bosses": [ + { + "name": "Lucifron", + "x": 63, + "y": 44, + "type": "dungeonskull" + }, + { + "name": "Magmadar", + "x": 69, + "y": 24, + "type": "dungeonskull" + }, + { + "name": "Gehennas", + "x": 34, + "y": 49, + "type": "dungeonskull" + }, + { + "name": "Garr", + "x": 32, + "y": 70, + "type": "dungeonskull" + }, + { + "name": "Shazzrah", + "x": 52, + "y": 77, + "type": "dungeonskull" + }, + { + "name": "Baron Geddon", + "x": 55, + "y": 84, + "type": "boss" + }, + { + "name": "Golemagg the Incinerator", + "x": 69, + "y": 59, + "type": "dungeonskull" + }, + { + "name": "Sulfuron Harbinger", + "x": 83, + "y": 83, + "type": "dungeonskull" + }, + { + "name": "Majordomo Executus", + "x": 84, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Ragnaros", + "x": 56, + "y": 54, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "Naxxramas60", + "expansion": "OriginalWoW", + "name": "Naxxramas", + "acronym": "Nax60", + "levelRange": "60+", + "playerLimit": "10-25", + "location": "Eastern Plaguelands", + "reputation": null, + "maps": [ + { + "image": "maps/Naxxramas_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor1", + "bosses": [] + }, + { + "image": "maps/Naxxramas_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor2", + "bosses": [] + }, + { + "image": "maps/Naxxramas_floor3.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor3", + "bosses": [] + }, + { + "image": "maps/Naxxramas_floor4.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor4", + "bosses": [] + }, + { + "image": "maps/Naxxramas_floor5.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor5", + "bosses": [] + }, + { + "image": "maps/Naxxramas_floor6.webp", + "width": 4096, + "height": 3072, + "label": "Naxxramas_floor6", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Patchwerk", + "x": 54, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Grobbulus", + "x": 62, + "y": 53, + "type": "dungeonskull" + }, + { + "name": "Gluth", + "x": 46, + "y": 44, + "type": "dungeonskull" + }, + { + "name": "Thaddius", + "x": 28, + "y": 15, + "type": "dungeonskull" + }, + { + "name": "Anub'Rekhan", + "x": 31, + "y": 47, + "type": "dungeonskull" + }, + { + "name": "Grand Widow Faerlina", + "x": 44, + "y": 36, + "type": "dungeonskull" + }, + { + "name": "Maexxna", + "x": 69, + "y": 15, + "type": "dungeonskull" + }, + { + "name": "|cffcc6666Instructor Razuvious", + "x": 43, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "|cffcc6666Gothik the Harvester", + "x": 67, + "y": 60, + "type": "dungeonskull" + }, + { + "name": "|cffcc6666Four Horsemen Chest", + "x": 30, + "y": 77, + "type": "dungeonskull" + }, + { + "name": "Noth the Plaguebringer", + "x": 35, + "y": 57, + "type": "dungeonskull" + }, + { + "name": "Heigan the Unclean", + "x": 50, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Loatheb", + "x": 76, + "y": 28, + "type": "dungeonskull" + }, + { + "name": "Sapphiron", + "x": 37, + "y": 23, + "type": "dungeonskull" + }, + { + "name": "Kel'Thuzad", + "x": 57, + "y": 68, + "type": "dungeonskull" + } + ] + }, + { + "id": "Onyxia60", + "expansion": "OriginalWoW", + "name": "Onyxia's Lair", + "acronym": "Ony", + "levelRange": "60-70", + "playerLimit": "10/25", + "location": "Dustwallow Marsh", + "reputation": null, + "maps": [ + { + "image": "maps/OnyxiasLair.webp", + "width": 4096, + "height": 3072, + "label": "OnyxiasLair", + "bosses": [ + { + "name": "Whelp Eggs", + "x": 63.2, + "y": 32, + "type": "boss" + }, + { + "name": "Whelp Eggs", + "x": 77, + "y": 32, + "type": "boss" + }, + { + "name": "Basalthane", + "x": 44.5, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Onyxia", + "x": 70, + "y": 23, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "RagefireChasm", + "expansion": "OriginalWoW", + "name": "Ragefire Chasm", + "acronym": "RFC", + "levelRange": "12-18", + "playerLimit": "5", + "location": "Orgrimmar", + "reputation": null, + "maps": [ + { + "image": "maps/Ragefire.webp", + "width": 4096, + "height": 3072, + "label": "Ragefire", + "bosses": [ + { + "name": "Oggleflint ", + "x": 56.2, + "y": 38.6, + "type": "dungeonskull" + }, + { + "name": "Taragaman the Hungerer", + "x": 40.9, + "y": 59, + "type": "dungeonskull" + }, + { + "name": "Jergosh the Invoker", + "x": 38.9, + "y": 82.9, + "type": "dungeonskull" + }, + { + "name": "Bazzalan", + "x": 42.4, + "y": 85.4, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "RazorfenDowns", + "expansion": "OriginalWoW", + "name": "Razorfen Downs", + "acronym": "RFD", + "levelRange": "33-42", + "playerLimit": "5", + "location": "The Barrens", + "reputation": null, + "maps": [ + { + "image": "maps/RazorfenDowns.webp", + "width": 4096, + "height": 3072, + "label": "RazorfenDowns", + "bosses": [ + { + "name": "Tuten'kash", + "x": 67.1, + "y": 41.2, + "type": "dungeonskull" + }, + { + "name": "Mordresh Fire Eye", + "x": 85.8, + "y": 45.7, + "type": "dungeonskull" + }, + { + "name": "Glutton", + "x": 38.5, + "y": 45.2, + "type": "dungeonskull" + }, + { + "name": "Amnennar the Coldbringer", + "x": 45, + "y": 59.1, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "RazorfenKraul", + "expansion": "OriginalWoW", + "name": "Razorfen Kraul", + "acronym": "RFK", + "levelRange": "23-33", + "playerLimit": "5", + "location": "The Barrens", + "reputation": null, + "maps": [ + { + "image": "maps/RazorfenKraul.webp", + "width": 4096, + "height": 3072, + "label": "RazorfenKraul", + "bosses": [ + { + "name": "Roogug", + "x": 65, + "y": 48, + "type": "dungeonskull" + }, + { + "name": "Aggem Thorncurse ", + "x": 81, + "y": 53, + "type": "dungeonskull" + }, + { + "name": "Death Speaker Jargba ", + "x": 88, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Overlord Ramtusk", + "x": 56, + "y": 30, + "type": "dungeonskull" + }, + { + "name": "Agathelos the Raging", + "x": 9, + "y": 68, + "type": "dungeonskull" + }, + { + "name": "Blind Hunter (Rare)", + "x": 11, + "y": 31, + "type": "boss" + }, + { + "name": "Charlga Razorflank ", + "x": 22, + "y": 31, + "type": "dungeonskull" + }, + { + "name": "Earthcaller Halmgar (Rare)", + "x": 49, + "y": 45, + "type": "boss" + } + ] + } + ] + }, + { + "id": "TheRuinsofAhnQiraj", + "expansion": "OriginalWoW", + "name": "Ruins of Ahn'Qiraj", + "acronym": "AQR", + "levelRange": "60-70", + "playerLimit": "10-25", + "location": "Silithus", + "reputation": "Cenarion Circle", + "maps": [ + { + "image": "maps/RuinsofAhnQiraj.webp", + "width": 4096, + "height": 3072, + "label": "RuinsofAhnQiraj", + "bosses": [ + { + "name": "Kurinnaxx", + "x": 56, + "y": 35, + "type": "dungeonskull" + }, + { + "name": "General Rajaxx", + "x": 57, + "y": 53, + "type": "dungeonskull" + }, + { + "name": "Moam (Optional)", + "x": 32, + "y": 38, + "type": "dungeonskull" + }, + { + "name": "Buru the Gorger (Optional)", + "x": 71, + "y": 60, + "type": "dungeonskull" + }, + { + "name": "Ayamiss the Hunter (Optional)", + "x": 60, + "y": 89, + "type": "dungeonskull" + }, + { + "name": "Ossirian the Unscarred", + "x": 41, + "y": 73, + "type": "dungeonskull" + }, + { + "name": "Safe Room", + "x": 63, + "y": 74, + "type": "boss" + } + ] + } + ] + }, + { + "id": "ScarletMonastery", + "expansion": "OriginalWoW", + "name": "Scarlet Monastery", + "acronym": "SM", + "levelRange": "33-40", + "playerLimit": "5", + "location": "Tirisfal Glades", + "reputation": null, + "maps": [ + { + "image": "maps/ScarletMonastery_floor1.webp", + "width": 4096, + "height": 3072, + "label": "ScarletMonastery_floor1", + "bosses": [] + }, + { + "image": "maps/ScarletMonastery_floor2.webp", + "width": 4096, + "height": 3072, + "label": "ScarletMonastery_floor2", + "bosses": [] + }, + { + "image": "maps/ScarletMonastery_floor3.webp", + "width": 4096, + "height": 3072, + "label": "ScarletMonastery_floor3", + "bosses": [] + }, + { + "image": "maps/ScarletMonastery_floor4.webp", + "width": 4096, + "height": 3072, + "label": "ScarletMonastery_floor4", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Interrogator Vishas", + "x": 72, + "y": 60, + "type": "dungeonskull" + }, + { + "name": "Pumpkin Shrine (Hallow's End)", + "x": 36, + "y": 63, + "type": "boss" + }, + { + "name": "Bloodmage Thalnos", + "x": 24, + "y": 56, + "type": "dungeonskull" + }, + { + "name": "Houndmaster Loksey", + "x": 30, + "y": 85, + "type": "dungeonskull" + }, + { + "name": "Arcanist Doan", + "x": 83, + "y": 77, + "type": "dungeonskull" + }, + { + "name": "Herod ", + "x": 79, + "y": 11, + "type": "dungeonskull" + }, + { + "name": "High Inquisitor Fairbanks", + "x": 56, + "y": 26, + "type": "dungeonskull" + }, + { + "name": "Scarlet Commander Mograine", + "x": 49, + "y": 29, + "type": "dungeonskull" + }, + { + "name": "High Inquisitor Whitemane", + "x": 48, + "y": 17, + "type": "dungeonskull" + } + ] + }, + { + "id": "Scholomance", + "expansion": "OriginalWoW", + "name": "Scholomance", + "acronym": "Scholo", + "levelRange": "58-60", + "playerLimit": "5", + "location": "Western Plaguelands", + "reputation": null, + "maps": [ + { + "image": "maps/Scholomance_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Scholomance_floor1", + "bosses": [] + }, + { + "image": "maps/Scholomance_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Scholomance_floor2", + "bosses": [] + }, + { + "image": "maps/Scholomance_floor3.webp", + "width": 4096, + "height": 3072, + "label": "Scholomance_floor3", + "bosses": [] + }, + { + "image": "maps/Scholomance_floor4.webp", + "width": 4096, + "height": 3072, + "label": "Scholomance_floor4", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Blood Steward of Kirtonos", + "x": 82, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Kirtonos the Herald (Summon)", + "x": 49, + "y": 12, + "type": "dungeonskull" + }, + { + "name": "The Deed to Tarren Mill", + "x": 31, + "y": 57, + "type": "questturnin" + }, + { + "name": "Torch Lever", + "x": 49, + "y": 55, + "type": "boss" + }, + { + "name": "Marduk Blackpool", + "x": 49, + "y": 66, + "type": "dungeonskull" + }, + { + "name": "Jandice Barov", + "x": 54, + "y": 15, + "type": "dungeonskull" + }, + { + "name": "Instructor Malicia", + "x": 73, + "y": 79, + "type": "dungeonskull" + }, + { + "name": "Doctor Theolen Krastinov ", + "x": 89, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Lorekeeper Polkelt", + "x": 73, + "y": 24, + "type": "dungeonskull" + }, + { + "name": "Rattlegore", + "x": 33, + "y": 65, + "type": "dungeonskull" + }, + { + "name": "Darkmaster Gandling", + "x": 73, + "y": 47, + "type": "dungeonskull" + }, + { + "name": "Ras Frostwhisper", + "x": 41, + "y": 82, + "type": "dungeonskull" + }, + { + "name": "The Ravenian", + "x": 67, + "y": 49, + "type": "dungeonskull" + }, + { + "name": "Lord Alexei Barov", + "x": 79, + "y": 30, + "type": "dungeonskull" + }, + { + "name": "Lady Illucia Barov", + "x": 67, + "y": 16, + "type": "dungeonskull" + }, + { + "name": "Darkmaster Gandling", + "x": 67, + "y": 30, + "type": "dungeonskull" + } + ] + }, + { + "id": "ShadowfangKeep", + "expansion": "OriginalWoW", + "name": "Shadowfang Keep", + "acronym": "SFK", + "levelRange": "17-25", + "playerLimit": "5", + "location": "Silverpine Forest", + "reputation": null, + "maps": [ + { + "image": "maps/ShadowfangKeep_floor1.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor1", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor2.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor2", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor3.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor3", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor4.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor4", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor5.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor5", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor6.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor6", + "bosses": [] + }, + { + "image": "maps/ShadowfangKeep_floor7.webp", + "width": 4096, + "height": 3072, + "label": "ShadowfangKeep_floor7", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Rethilgore ", + "x": 67, + "y": 73, + "type": "dungeonskull" + }, + { + "name": "Investigator Fezzen Brasstacks (Love is in the Air)", + "x": 54, + "y": 66, + "type": "boss" + }, + { + "name": "Deathstalker Vincent", + "x": 57, + "y": 60, + "type": "boss" + }, + { + "name": "Apothecary Trio (Love is in the Air)", + "x": 39, + "y": 51, + "type": "boss" + }, + { + "name": "Fel Steed", + "x": 35, + "y": 58, + "type": "boss" + }, + { + "name": "Commander Springvale", + "x": 27, + "y": 61, + "type": "dungeonskull" + }, + { + "name": "Razorclaw the Butcher", + "x": 48, + "y": 28, + "type": "dungeonskull" + }, + { + "name": "Baron Silverlaine", + "x": 30, + "y": 77, + "type": "dungeonskull" + }, + { + "name": "Fenrus the Devourer", + "x": 53, + "y": 54, + "type": "dungeonskull" + }, + { + "name": "Wolf Master Nandos", + "x": 57, + "y": 62, + "type": "dungeonskull" + }, + { + "name": "Archmage Arugal", + "x": 65, + "y": 20, + "type": "dungeonskull" + }, + { + "name": "Odo the Blindwatcher", + "x": 55, + "y": 81, + "type": "dungeonskull" + }, + { + "name": "Deathsworn Captain (Rare)", + "x": 59, + "y": 53, + "type": "dungeonskull" + } + ] + }, + { + "id": "Stratholme", + "expansion": "OriginalWoW", + "name": "Stratholme", + "acronym": "Strat", + "levelRange": "58-60", + "playerLimit": "5", + "location": "Eastern Plaguelands", + "reputation": null, + "maps": [ + { + "image": "maps/Stratholme_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Stratholme_floor1", + "bosses": [] + }, + { + "image": "maps/Stratholme_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Stratholme_floor2", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Crusaders' Square Postbox", + "x": 36, + "y": 35, + "type": "boss" + }, + { + "name": "Market Row Postbox", + "x": 69, + "y": 28, + "type": "boss" + }, + { + "name": "Festival Lane Postbox", + "x": 80, + "y": 29, + "type": "boss" + }, + { + "name": "King's Square Postbox", + "x": 64, + "y": 53, + "type": "boss" + }, + { + "name": "Fras Siabi's Postbox", + "x": 55, + "y": 69, + "type": "boss" + }, + { + "name": "Skul (Rare)", + "x": 80, + "y": 43, + "type": "dungeonskull" + }, + { + "name": "Atiesh (Summon)", + "x": 86, + "y": 38, + "type": "boss" + }, + { + "name": "Hearthsinger Forresten (Varies)", + "x": 81, + "y": 34, + "type": "dungeonskull" + }, + { + "name": "The Unforgiven", + "x": 73, + "y": 23, + "type": "dungeonskull" + }, + { + "name": "Elder Farwhisper (Lunar Festival)", + "x": 79, + "y": 17, + "type": "boss" + }, + { + "name": "Timmy the Cruel", + "x": 35, + "y": 26, + "type": "dungeonskull" + }, + { + "name": "Malor the Zealous", + "x": 30, + "y": 42, + "type": "dungeonskull" + }, + { + "name": "Crimson Hammersmith (Summon)", + "x": 13, + "y": 48, + "type": "boss" + }, + { + "name": "Cannon Master Willey", + "x": 5, + "y": 51, + "type": "dungeonskull" + }, + { + "name": "Archivist Galford", + "x": 28, + "y": 74, + "type": "dungeonskull" + }, + { + "name": "Grand Crusader Dathrohan", + "x": 21, + "y": 82, + "type": "dungeonskull" + }, + { + "name": "Elders' Square Postbox", + "x": 67, + "y": 76, + "type": "boss" + }, + { + "name": "Magistrate Barthilas (Varies)", + "x": 66, + "y": 77, + "type": "dungeonskull" + }, + { + "name": "Aurius", + "x": 68, + "y": 74, + "type": "dungeonskull" + }, + { + "name": "Stonespine (Rare)", + "x": 70, + "y": 51, + "type": "dungeonskull" + }, + { + "name": "Baroness Anastari", + "x": 76, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Nerub'enkan", + "x": 58, + "y": 48, + "type": "dungeonskull" + }, + { + "name": "Maleki the Pallid", + "x": 67, + "y": 22, + "type": "dungeonskull" + }, + { + "name": "Magistrate Barthilas (Varies)", + "x": 57, + "y": 16, + "type": "dungeonskull" + }, + { + "name": "Ramstein the Gorger", + "x": 45, + "y": 21, + "type": "dungeonskull" + }, + { + "name": "Baron Rivendare", + "x": 40, + "y": 21, + "type": "dungeonskull" + } + ] + }, + { + "id": "TheSunkenTemple", + "expansion": "OriginalWoW", + "name": "Sunken Temple", + "acronym": "ST", + "levelRange": "46-53", + "playerLimit": "5", + "location": "Swamp of Sorrows", + "reputation": null, + "maps": [ + { + "image": "maps/TheTempleofAtalhakkar.webp", + "width": 4096, + "height": 3072, + "label": "TheTempleofAtalhakkar", + "bosses": [ + { + "name": "Dreamscythe", + "x": 50, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Avatar of Hakkar", + "x": 26, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Jammal'an the Prophet", + "x": 73, + "y": 39, + "type": "dungeonskull" + }, + { + "name": "Morphaz", + "x": 50, + "y": 87, + "type": "dungeonskull" + }, + { + "name": "Shade of Eranikus", + "x": 68, + "y": 87, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "TheTempleofAhnQiraj", + "expansion": "OriginalWoW", + "name": "Temple of Ahn'Qiraj", + "acronym": "AQT", + "levelRange": "60-70", + "playerLimit": "10-25", + "location": "Silithus", + "reputation": "Brood of Nozdormu", + "maps": [ + { + "image": "maps/AhnQiraj_floor1.webp", + "width": 4096, + "height": 3072, + "label": "AhnQiraj_floor1", + "bosses": [] + }, + { + "image": "maps/AhnQiraj_floor2.webp", + "width": 4096, + "height": 3072, + "label": "AhnQiraj_floor2", + "bosses": [] + }, + { + "image": "maps/AhnQiraj_floor3.webp", + "width": 4096, + "height": 3072, + "label": "AhnQiraj_floor3", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "The Bug Family (Optional)", + "x": 28, + "y": 50, + "type": "dungeonskull" + }, + { + "name": "Battleguard Sartura", + "x": 45, + "y": 35, + "type": "dungeonskull" + }, + { + "name": "Fankriss the Unyielding", + "x": 63, + "y": 23, + "type": "dungeonskull" + }, + { + "name": "Viscidus (Optional)", + "x": 73, + "y": 18, + "type": "dungeonskull" + }, + { + "name": "Princess Huhuran", + "x": 44, + "y": 50, + "type": "dungeonskull" + }, + { + "name": "Twin Emperors", + "x": 61, + "y": 71, + "type": "dungeonskull" + }, + { + "name": "Ouro (Optional)", + "x": 31, + "y": 81, + "type": "dungeonskull" + }, + { + "name": "The Prophet Skeram (Outside)", + "x": 46, + "y": 53, + "type": "dungeonskull" + }, + { + "name": "Eye of C'Thun", + "x": 57, + "y": 63, + "type": "dungeonskull" + }, + { + "name": "Andorgos ", + "x": 50, + "y": 19, + "type": "boss" + }, + { + "name": "Arygos", + "x": 41, + "y": 26, + "type": "boss" + } + ] + }, + { + "id": "TheDeadmines", + "expansion": "OriginalWoW", + "name": "The Deadmines", + "acronym": "VC", + "levelRange": "16-22", + "playerLimit": "5", + "location": "Westfall", + "reputation": null, + "maps": [ + { + "image": "maps/TheDeadmines_floor1.webp", + "width": 4096, + "height": 3072, + "label": "TheDeadmines_floor1", + "bosses": [] + }, + { + "image": "maps/TheDeadmines_floor2.webp", + "width": 4096, + "height": 3072, + "label": "TheDeadmines_floor2", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Rhahk'Zor ", + "x": 36, + "y": 62, + "type": "dungeonskull" + }, + { + "name": "Miner Johnson (Rare)", + "x": 51, + "y": 51, + "type": "dungeonskull" + }, + { + "name": "Sneed ", + "x": 51, + "y": 86, + "type": "dungeonskull" + }, + { + "name": "Gilnid ", + "x": 61, + "y": 57, + "type": "dungeonskull" + }, + { + "name": "Defias Gunpowder", + "x": 20, + "y": 55, + "type": "boss" + }, + { + "name": "Captain Greenskin", + "x": 61, + "y": 38, + "type": "dungeonskull" + }, + { + "name": "Edwin VanCleef ", + "x": 61, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Mr. Smite ", + "x": 56, + "y": 26, + "type": "dungeonskull" + }, + { + "name": "Cookie ", + "x": 68, + "y": 43, + "type": "dungeonskull" + } + ] + }, + { + "id": "TheStockade", + "expansion": "OriginalWoW", + "name": "The Stockade", + "acronym": "Stocks", + "levelRange": "21-27", + "playerLimit": "5", + "location": "Stormwind City", + "reputation": null, + "maps": [ + { + "image": "maps/TheStockade.webp", + "width": 4096, + "height": 3072, + "label": "TheStockade", + "bosses": [ + { + "name": "Targorr the Dread", + "x": 50, + "y": 25, + "type": "dungeonskull" + }, + { + "name": "Kam Deepfury", + "x": 69, + "y": 30, + "type": "dungeonskull" + }, + { + "name": "Hamhock", + "x": 78, + "y": 46, + "type": "dungeonskull" + }, + { + "name": "Bazil Thredd", + "x": 86, + "y": 51, + "type": "dungeonskull" + }, + { + "name": "Dextren Ward", + "x": 22, + "y": 26, + "type": "dungeonskull" + }, + { + "name": "Bruegal Ironknuckle (Rare)", + "x": 30, + "y": 43, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "Uldaman", + "expansion": "OriginalWoW", + "name": "Uldaman", + "acronym": "Ulda", + "levelRange": "36-42", + "playerLimit": "5", + "location": "Badlands", + "reputation": null, + "maps": [ + { + "image": "maps/Uldaman_floor1.webp", + "width": 4096, + "height": 3072, + "label": "Uldaman_floor1", + "bosses": [] + }, + { + "image": "maps/Uldaman_floor2.webp", + "width": 4096, + "height": 3072, + "label": "Uldaman_floor2", + "bosses": [] + }, + { + "image": "maps/Uldaman_floor18.webp", + "width": 4096, + "height": 3072, + "label": "Uldaman_floor18", + "bosses": [] + } + ], + "unassignedBosses": [ + { + "name": "Baelog", + "x": 59, + "y": 93, + "type": "dungeonskull" + }, + { + "name": "Remains of a Paladin", + "x": 53, + "y": 64, + "type": "boss" + }, + { + "name": "Revelosh", + "x": 54, + "y": 72, + "type": "dungeonskull" + }, + { + "name": "Ironaya", + "x": 37, + "y": 74, + "type": "dungeonskull" + }, + { + "name": "Obsidian Sentinel", + "x": 29, + "y": 60, + "type": "dungeonskull" + }, + { + "name": "Ancient Stone Keeper", + "x": 48, + "y": 45, + "type": "dungeonskull" + }, + { + "name": "Galgann Firehammer", + "x": 27, + "y": 33, + "type": "dungeonskull" + }, + { + "name": "Grimlok ", + "x": 22, + "y": 26, + "type": "dungeonskull" + }, + { + "name": "Archaedas (Lower)", + "x": 42, + "y": 17, + "type": "dungeonskull" + }, + { + "name": "The Discs of Norgannon (Lower)", + "x": 40, + "y": 9, + "type": "boss" + }, + { + "name": "Archaedas (Lower)", + "x": 55, + "y": 52, + "type": "dungeonskull" + }, + { + "name": "The Discs of Norgannon (Lower)", + "x": 50, + "y": 34, + "type": "boss" + } + ] + }, + { + "id": "WailingCaverns", + "expansion": "OriginalWoW", + "name": "Wailing Caverns", + "acronym": "WC", + "levelRange": "16-25", + "playerLimit": "5", + "location": "The Barrens", + "reputation": null, + "maps": [ + { + "image": "maps/WailingCaverns.webp", + "width": 4096, + "height": 3072, + "label": "WailingCaverns", + "bosses": [ + { + "name": "Disciple of Naralex", + "x": 47, + "y": 53, + "type": "boss" + }, + { + "name": "Lord Cobrahn ", + "x": 16, + "y": 57, + "type": "dungeonskull" + }, + { + "name": "Lady Anacondra (Varies)", + "x": 31, + "y": 44, + "type": "dungeonskull" + }, + { + "name": "Kresh (Varies)", + "x": 45, + "y": 38, + "type": "dungeonskull" + }, + { + "name": "Lord Pythas ", + "x": 64, + "y": 58, + "type": "dungeonskull" + }, + { + "name": "Skum", + "x": 74, + "y": 82, + "type": "dungeonskull" + }, + { + "name": "Lord Serpentis (Upper)", + "x": 62, + "y": 54, + "type": "dungeonskull" + }, + { + "name": "Verdan the Everliving (Upper)", + "x": 56, + "y": 47, + "type": "dungeonskull" + }, + { + "name": "Mutanus the Devourer", + "x": 35, + "y": 13, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "ZulFarrak", + "expansion": "OriginalWoW", + "name": "Zul'Farrak", + "acronym": "ZF", + "levelRange": "42-48", + "playerLimit": "5", + "location": "Tanaris", + "reputation": null, + "maps": [ + { + "image": "maps/ZulFarrak.webp", + "width": 4096, + "height": 3072, + "label": "ZulFarrak", + "bosses": [ + { + "name": "Antu'sul ", + "x": 66, + "y": 27, + "type": "dungeonskull" + }, + { + "name": "Theka the Martyr", + "x": 54, + "y": 28, + "type": "dungeonskull" + }, + { + "name": "Witch Doctor Zum'rah", + "x": 45, + "y": 17, + "type": "dungeonskull" + }, + { + "name": "Nekrum Gutchewer", + "x": 34, + "y": 18, + "type": "dungeonskull" + }, + { + "name": "Sergeant Bly", + "x": 28, + "y": 18, + "type": "dungeonskull" + }, + { + "name": "Sandfury Executioner", + "x": 25, + "y": 18, + "type": "dungeonskull" + }, + { + "name": "Hydromancer Velratha", + "x": 32, + "y": 41, + "type": "dungeonskull" + }, + { + "name": "Chief Ukorz Sandscalp", + "x": 42, + "y": 34, + "type": "dungeonskull" + }, + { + "name": "Zerillis (Rare, Wanders)", + "x": 53, + "y": 42, + "type": "dungeonskull" + }, + { + "name": "Sandarr Dunereaver (Rare)", + "x": 46, + "y": 57, + "type": "dungeonskull" + } + ] + } + ] + }, + { + "id": "ZulGurub", + "expansion": "OriginalWoW", + "name": "Zul'Gurub", + "acronym": "ZG", + "levelRange": "56-70", + "playerLimit": "20", + "location": "Stranglethorn Vale", + "reputation": "Zandalar Tribe", + "maps": [] + } + ] +} \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..32af4d5 --- /dev/null +++ b/web/index.html @@ -0,0 +1,64 @@ + + + + + +Ascension M+ Route Planner + + + + + +
+
+
+

Pick a dungeon

+

+
+
+
+ + + + + + +
+
+ +
+
+
+ + +
+
+ + + +
+
+ +
+
+ + + + diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..a167112 --- /dev/null +++ b/web/style.css @@ -0,0 +1,386 @@ +/* Ascension M+ Route Planner — dark, gold-accented, no frameworks. */ + +:root { + --bg: #0e0d10; + --panel: #17151b; + --panel-2: #1f1c25; + --line: #2e2932; + --text: #d8d2c4; + --text-dim: #8d8576; + --accent: #d4a44a; /* WoW gold */ + --accent-2: #f0c674; + --boss: #d63b3b; /* boss pin */ + --waypoint: #6ea8ff; /* route point */ + --pull: #6ad17b; /* pull marker */ + --route: #f0c674; + --shadow: 0 1px 0 rgba(0,0,0,.4), 0 6px 18px rgba(0,0,0,.35); +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + height: 100%; + background: var(--bg); + color: var(--text); + font: 14px/1.45 -apple-system, "SF Pro Text", "Inter", "Segoe UI", system-ui, sans-serif; + overflow: hidden; +} + +body { + display: grid; + grid-template-columns: 280px 1fr; +} + +/* --- sidebar -------------------------------------------------------------- */ + +.sidebar { + background: var(--panel); + border-right: 1px solid var(--line); + display: flex; + flex-direction: column; + min-height: 0; +} + +.brand { + padding: 18px 20px 12px; + border-bottom: 1px solid var(--line); +} +.brand h1 { + margin: 0; + font-size: 18px; + letter-spacing: .04em; + color: var(--accent); + font-weight: 600; +} +.brand .sub { + margin: 2px 0 0; + font-size: 11px; + color: var(--text-dim); + letter-spacing: .08em; + text-transform: uppercase; +} + +.filters { + padding: 10px 14px; + display: flex; + flex-direction: column; + gap: 8px; + border-bottom: 1px solid var(--line); +} +.filters input, +.filters select { + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--line); + border-radius: 4px; + padding: 6px 10px; + font: inherit; + outline: none; +} +.filters input:focus, +.filters select:focus { border-color: var(--accent); } + +.dungeon-list { + list-style: none; + margin: 0; + padding: 6px 0; + overflow-y: auto; + flex: 1 1 auto; + scrollbar-width: thin; + scrollbar-color: var(--line) transparent; +} +.dungeon-list li { + padding: 7px 16px; + cursor: pointer; + display: flex; + justify-content: space-between; + gap: 8px; + border-left: 3px solid transparent; + font-size: 13px; +} +.dungeon-list li:hover { background: var(--panel-2); } +.dungeon-list li.active { + background: var(--panel-2); + border-left-color: var(--accent); + color: var(--accent-2); +} +.dungeon-list li .acronym { + color: var(--text-dim); + font-size: 11px; + font-variant-numeric: tabular-nums; +} +.dungeon-list .group-header { + padding: 12px 16px 4px; + color: var(--text-dim); + font-size: 10px; + text-transform: uppercase; + letter-spacing: .12em; + cursor: default; +} +.dungeon-list .group-header:hover { background: transparent; } + +/* --- main viewer ---------------------------------------------------------- */ + +.viewer { + display: flex; + flex-direction: column; + min-width: 0; + min-height: 0; +} + +.viewer-header { + padding: 14px 18px; + border-bottom: 1px solid var(--line); + display: flex; + align-items: center; + gap: 18px; + flex-wrap: wrap; +} +.title-block { flex: 0 1 auto; min-width: 200px; } +.title-block h2 { + margin: 0; + font-size: 18px; + color: var(--accent-2); + font-weight: 600; +} +.title-block .meta { + margin: 2px 0 0; + font-size: 12px; + color: var(--text-dim); +} + +.floor-tabs { + display: flex; + gap: 4px; + flex-wrap: wrap; +} +.floor-tabs button { + background: var(--panel); + color: var(--text-dim); + border: 1px solid var(--line); + border-radius: 4px; + padding: 5px 10px; + font: inherit; + font-size: 12px; + cursor: pointer; +} +.floor-tabs button:hover { color: var(--text); } +.floor-tabs button.active { + border-color: var(--accent); + color: var(--accent-2); + background: var(--panel-2); +} + +.toolbar { + margin-left: auto; + display: flex; + gap: 6px; +} +.toolbar button { + background: var(--panel); + border: 1px solid var(--line); + color: var(--text); + border-radius: 4px; + padding: 5px 12px; + font: inherit; + font-size: 12px; + cursor: pointer; +} +.toolbar button:hover { border-color: var(--accent); color: var(--accent-2); } +.toolbar .tool.active { + background: var(--accent); + color: #1a1208; + border-color: var(--accent); + font-weight: 600; +} + +/* --- canvas / overlay ----------------------------------------------------- */ + +.canvas-wrap { + flex: 1 1 auto; + display: grid; + grid-template-columns: 1fr 240px; + min-height: 0; +} + +.canvas-host { + position: relative; + background: #050507; + background-image: + linear-gradient(rgba(255,255,255,.015) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.015) 1px, transparent 1px); + background-size: 32px 32px; + overflow: hidden; + user-select: none; + cursor: grab; +} +.canvas-host.panning { cursor: grabbing; } + +/* The pan/zoom transform is applied to this wrapper. Image and SVG share it. */ +.canvas-stage { + position: absolute; + top: 50%; + left: 50%; + transform-origin: 0 0; + /* set by JS: transform: translate(...) scale(...) */ + will-change: transform; +} +.canvas-stage img { + display: block; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} +.canvas-stage svg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: auto; + cursor: crosshair; +} + +.zoom-controls { + position: absolute; + right: 12px; + bottom: 12px; + display: flex; + flex-direction: column; + gap: 4px; + z-index: 5; +} +.zoom-controls button { + background: var(--panel-2); + border: 1px solid var(--line); + color: var(--text); + border-radius: 4px; + width: 32px; + height: 32px; + font-size: 16px; + cursor: pointer; + font-weight: 600; +} +.zoom-controls button:hover { border-color: var(--accent); color: var(--accent-2); } + +/* boss pins (non-interactive markers from AtlasLoot data) */ +.boss-pin circle { + fill: var(--boss); + stroke: #200; + stroke-width: 6; +} + +/* user-placed waypoints */ +.waypoint circle { + fill: var(--waypoint); + stroke: #002; + stroke-width: 6; + cursor: grab; +} +.waypoint.dragging circle { cursor: grabbing; } + +.pull circle { + fill: var(--pull); + stroke: #020; + stroke-width: 6; + cursor: grab; +} + +/* labels for all pin types */ +.boss-pin text, +.waypoint text, +.pull text { + fill: #fff; + font-weight: 700; + font-family: system-ui, sans-serif; + text-anchor: middle; + paint-order: stroke; + stroke: #000; + stroke-width: 6; + pointer-events: none; +} + +.route-line { + fill: none; + stroke: var(--route); + stroke-width: 8; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: 16 12; + opacity: .85; + pointer-events: none; +} + +/* --- info pane ----------------------------------------------------------- */ + +.info-pane { + border-left: 1px solid var(--line); + background: var(--panel); + padding: 14px 16px; + overflow-y: auto; + scrollbar-width: thin; +} +.info-pane h3 { + margin: 14px 0 6px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: .12em; + color: var(--text-dim); +} +.info-pane h3:first-child { margin-top: 0; } + +.boss-list, .waypoint-list { + list-style: none; + margin: 0; + padding: 0; + font-size: 13px; +} +.boss-list li, +.waypoint-list li { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px; + border-radius: 3px; +} +.boss-list li:hover, +.waypoint-list li:hover { background: var(--panel-2); } +.boss-list .swatch, +.waypoint-list .swatch { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} +.boss-list .swatch { background: var(--boss); } +.waypoint-list .swatch { background: var(--waypoint); } +.waypoint-list li button { + margin-left: auto; + background: transparent; + border: 0; + color: var(--text-dim); + cursor: pointer; + font-size: 14px; +} +.waypoint-list li button:hover { color: var(--boss); } + +/* --- toast ---------------------------------------------------------------- */ +.toast { + position: fixed; + bottom: 18px; + left: 50%; + transform: translateX(-50%); + background: var(--panel-2); + color: var(--accent-2); + border: 1px solid var(--accent); + padding: 8px 18px; + border-radius: 4px; + font-size: 13px; + box-shadow: var(--shadow); + pointer-events: none; + opacity: 0; + transition: opacity .2s ease; + z-index: 99; +} +.toast.show { opacity: 1; }