diff --git a/data/atlasloot_extras.json b/data/atlasloot_extras.json new file mode 100644 index 0000000..3a8879e --- /dev/null +++ b/data/atlasloot_extras.json @@ -0,0 +1,1102 @@ +{ + "_comment": "Supplemental rare bosses + interactives lifted from AtlasLootAscension and transformed into keystone.guru pixel space. Generated by tools/atlasloot_extras.py.", + "transforms": { + "RagefireChasm\u2192ragefire_chasm": [ + 73.72443668498734, + -643.6866761504352, + 42.96310607515248, + -122.87047634576129 + ], + "TheDeadmines\u2192deadmines": [ + 38.86529733570145, + 795.3768071048042, + 42.675714797527434, + -4.190671073545933 + ], + "WailingCaverns\u2192wailing_caverns": [ + 81.43623828914878, + -747.3569080929979, + 34.84088492074497, + 115.17092826840326 + ], + "ShadowfangKeep\u2192shadowfang_keep": [ + 61.65109512912707, + 44.20626976136464, + 40.982516838487975, + -50.47485979381463 + ], + "TheStockade\u2192the_stockade": [ + 59.842452519084006, + 61.74031633587583, + 39.897596145904984, + 21.673417205782425 + ], + "Gnomeregan\u2192gnomeregan": [ + 111.07253811659204, + -1434.4782521973132, + 38.1758651250788, + 220.98302089552416 + ], + "RazorfenKraul\u2192razorfen_kraul": [ + 60.513804000979846, + 88.27979514983198, + 43.452935250680056, + -74.45083218033032 + ], + "RazorfenDowns\u2192razorfen_downs": [ + 63.04291758924619, + -290.16402952444923, + 41.07698629536258, + 102.3108550816687 + ], + "ScarletMonastery\u2192scarlet_monastery_cathedral": [ + 50.76749473684181, + 557.1932350877345, + 32.20170256410254, + 167.73567179487236 + ], + "ScarletMonastery\u2192scarlet_monastery_graveyard": [ + 60.84316666666665, + 37.29200000000128, + 23.45159999999305, + 995.9040000004034 + ], + "ScarletMonastery\u2192scarlet_monastery_library": [ + 59.125464150943436, + 212.3352754716957, + 74.09100000000035, + -2662.1046000000283 + ], + "Uldaman\u2192uldaman": [ + 63.19322527727629, + -86.87969584730672, + 41.52615397709141, + -41.35400744707431 + ], + "ZulFarrak\u2192zul_farrak": [ + 71.87339593345645, + -512.6565965671986, + 39.668194090909076, + -1.3930740909086776 + ], + "Maraudon\u2192maraudon": [ + 61.77912121471568, + 25.543185142141738, + 42.19282336665975, + -65.8431254967295 + ], + "BlackrockDepths\u2192blackrock_depths": [ + 63.390884474140194, + -129.9214250730094, + 43.72115533672626, + -43.96230823056116 + ], + "BlackrockSpire\u2192lower_blackrock_spire": [ + 70.31609043478278, + -550.6383026087042, + 47.429734477825605, + -521.5857562231853 + ], + "BlackrockSpire\u2192upper_blackrock_spire": [ + 61.504197773872264, + -76.49282249560493, + 34.005210582010584, + 222.27234962962956 + ], + "Stratholme\u2192stratholme": [ + 47.28673540151238, + 713.5643322963654, + 42.71814474125836, + -161.87918672865834 + ], + "Scholomance\u2192scholomance": [ + 68.16995676302486, + -376.1030967834516, + 46.6690874521118, + -292.56897919878884 + ], + "DireMaul\u2192dire_maul_north": [ + 63.69477659517125, + -52.97349769615799, + 40.323820514843405, + -15.287907437179715 + ], + "DireMaul\u2192dire_maul_east": [ + 23.186187151976394, + 2032.82655823259, + 72.13990548255808, + -968.0175378916643 + ], + "DireMaul\u2192dire_maul_west": [ + 50.567135013423375, + 526.7297255113892, + 37.66845336733329, + 254.15845163333543 + ], + "MoltenCore\u2192moltencore": [ + 63.802558332453295, + -161.93769244746218, + 46.40771496165673, + -344.9887211648951 + ], + "BlackwingLair\u2192blackwinglair": [ + 56.096746961325906, + 325.5217337016602, + 24.000187425480892, + 738.8363241595489 + ], + "Naxxramas60\u2192naxxramas_classic": [ + 54.729314117996196, + 329.7412320416867, + 13.38947624976002, + 1066.3540111552927 + ] + }, + "extras": { + "deadmines": [ + { + "name": "Defias Gunpowder", + "x": 1572.7, + "y": 2343.0, + "rare": false, + "source": "atlasloot" + } + ], + "wailing_caverns": [ + { + "name": "Disciple of Naralex", + "x": 3080.1, + "y": 1961.7, + "rare": false, + "source": "atlasloot" + } + ], + "shadowfang_keep": [ + { + "name": "Investigator Fezzen Brasstacks (Love is in the Air)", + "x": 3373.4, + "y": 2654.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Deathstalker Vincent", + "x": 3558.3, + "y": 2408.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Apothecary Trio (Love is in the Air)", + "x": 2448.6, + "y": 2039.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Fel Steed", + "x": 2202.0, + "y": 2326.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Deathsworn Captain (Rare)", + "x": 3681.6, + "y": 2121.6, + "rare": true, + "source": "atlasloot" + } + ], + "gnomeregan": [ + { + "name": "Clean Room", + "x": 5674.2, + "y": 2664.2, + "rare": false, + "source": "atlasloot" + } + ], + "razorfen_kraul": [ + { + "name": "Roogug", + "x": 4021.7, + "y": 2011.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Blind Hunter (Rare)", + "x": 753.9, + "y": 1272.6, + "rare": true, + "source": "atlasloot" + } + ], + "scarlet_monastery_cathedral": [ + { + "name": "Interrogator Vishas", + "x": 4212.5, + "y": 2099.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pumpkin Shrine (Hallow's End)", + "x": 2384.8, + "y": 2196.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bloodmage Thalnos", + "x": 1775.6, + "y": 1971.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Houndmaster Loksey", + "x": 2080.2, + "y": 2904.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Arcanist Doan", + "x": 4770.9, + "y": 2647.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Herod ", + "x": 4567.8, + "y": 522.0, + "rare": false, + "source": "atlasloot" + } + ], + "scarlet_monastery_graveyard": [ + { + "name": "Houndmaster Loksey", + "x": 1862.6, + "y": 2989.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Arcanist Doan", + "x": 5087.3, + "y": 2801.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Herod ", + "x": 4843.9, + "y": 1253.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "High Inquisitor Fairbanks", + "x": 3444.5, + "y": 1605.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Scarlet Commander Mograine", + "x": 3018.6, + "y": 1676.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "High Inquisitor Whitemane", + "x": 2957.8, + "y": 1394.6, + "rare": false, + "source": "atlasloot" + } + ], + "scarlet_monastery_library": [ + { + "name": "Interrogator Vishas", + "x": 4469.4, + "y": 1783.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bloodmage Thalnos", + "x": 1631.3, + "y": 1487.0, + "rare": false, + "source": "atlasloot" + } + ], + "uldaman": [ + { + "name": "Remains of a Paladin", + "x": 3262.4, + "y": 2616.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Discs of Norgannon (Lower)", + "x": 2440.8, + "y": 332.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Discs of Norgannon (Lower)", + "x": 3072.8, + "y": 1370.5, + "rare": false, + "source": "atlasloot" + } + ], + "zul_farrak": [ + { + "name": "Sandfury Executioner", + "x": 1284.2, + "y": 712.6, + "rare": false, + "source": "atlasloot" + } + ], + "maraudon": [ + { + "name": "Veng ", + "x": 3732.3, + "y": 946.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Maraudos ", + "x": 3299.8, + "y": 3056.4, + "rare": false, + "source": "atlasloot" + } + ], + "blackrock_depths": [ + { + "name": "Kharan Mighthammer", + "x": 3166.4, + "y": 3541.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Commander Gor'shak ", + "x": 3293.2, + "y": 3759.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Marshal Windsor", + "x": 3229.8, + "y": 4065.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Ring of Law", + "x": 3103.0, + "y": 2623.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Vault", + "x": 3673.5, + "y": 2841.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Ring of Law", + "x": 3103.0, + "y": 3847.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Shadowforge Lock", + "x": 2532.5, + "y": 3934.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Grim Guzzler", + "x": 3039.6, + "y": 2710.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Summoner's Tomb", + "x": 3293.2, + "y": 1049.1, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Lyceum", + "x": 4117.3, + "y": 480.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Black Forge", + "x": 3863.7, + "y": 1049.1, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Molten Core", + "x": 4180.7, + "y": 1530.0, + "rare": false, + "source": "atlasloot" + } + ], + "lower_blackrock_spire": [ + { + "name": "Mother Smolderweb", + "x": 3949.6, + "y": 2798.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Burning Felguard (Rare, Summon)", + "x": 2473.0, + "y": 2039.6, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Vaelan (Upper)", + "x": 3105.8, + "y": 1423.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warosh (Wanders)", + "x": 3738.6, + "y": 1517.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bijou", + "x": 3879.3, + "y": 2134.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Roughshod Pike", + "x": 4160.5, + "y": 2229.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Spirestone Butcher (Rare)", + "x": 3035.5, + "y": 2229.3, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Human Remains (Lower)", + "x": 2473.0, + "y": 2134.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Urok's Tribute Pile", + "x": 2683.9, + "y": 2087.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Halycon", + "x": 2121.4, + "y": 3509.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pyroguard Emberseer", + "x": 1629.2, + "y": 759.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Solakar Flamewreath", + "x": 2191.7, + "y": 1328.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Goraluk Anvilcrack ", + "x": 1769.8, + "y": 759.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warchief Rend Blackhand", + "x": 2894.9, + "y": 759.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Beast", + "x": 3949.6, + "y": 996.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "General Drakkisath", + "x": 1980.7, + "y": 1849.9, + "rare": false, + "source": "atlasloot" + } + ], + "upper_blackrock_spire": [ + { + "name": "War Master Voone", + "x": 3121.7, + "y": 2058.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Mother Smolderweb", + "x": 3859.8, + "y": 2602.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Burning Felguard (Rare, Summon)", + "x": 2568.2, + "y": 2058.6, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Shadow Hunter Vosh'gajin", + "x": 3367.7, + "y": 2602.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Vaelan (Upper)", + "x": 3121.7, + "y": 1616.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warosh (Wanders)", + "x": 3675.3, + "y": 1684.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Highlord Omokk", + "x": 2322.2, + "y": 2262.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Urok's Tribute Pile", + "x": 2752.7, + "y": 2092.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bannok Grimaxe (Rare)", + "x": 2752.7, + "y": 2432.6, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Quartermaster Zigris ", + "x": 3306.2, + "y": 3112.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Darkstone Tablet", + "x": 1891.6, + "y": 1650.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Overlord Wyrmthalak", + "x": 3490.8, + "y": 2398.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Jed Runewatcher (Rare)", + "x": 2260.7, + "y": 1038.4, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Goraluk Anvilcrack ", + "x": 1953.1, + "y": 1140.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Awbee", + "x": 2937.2, + "y": 1446.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Blackwing Lair", + "x": 3306.2, + "y": 970.4, + "rare": false, + "source": "atlasloot" + } + ], + "stratholme": [ + { + "name": "Crusaders' Square Postbox", + "x": 2415.9, + "y": 1333.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Market Row Postbox", + "x": 3976.3, + "y": 1034.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Festival Lane Postbox", + "x": 4496.5, + "y": 1076.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "King's Square Postbox", + "x": 3739.9, + "y": 2102.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Fras Siabi's Postbox", + "x": 3314.3, + "y": 2785.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Atiesh (Summon)", + "x": 4780.2, + "y": 1461.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Elder Farwhisper (Lunar Festival)", + "x": 4449.2, + "y": 564.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Malor the Zealous", + "x": 2132.2, + "y": 1632.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Crimson Hammersmith (Summon)", + "x": 1328.3, + "y": 1888.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Cannon Master Willey", + "x": 950.0, + "y": 2016.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Archivist Galford", + "x": 2037.6, + "y": 2999.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Elders' Square Postbox", + "x": 3881.8, + "y": 3084.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Aurius", + "x": 3929.1, + "y": 2999.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Stonespine (Rare)", + "x": 4023.6, + "y": 2016.7, + "rare": true, + "source": "atlasloot" + } + ], + "scholomance": [ + { + "name": "Blood Steward of Kirtonos", + "x": 5213.8, + "y": 1854.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Deed to Tarren Mill", + "x": 1737.2, + "y": 2367.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Torch Lever", + "x": 2964.2, + "y": 2274.2, + "rare": false, + "source": "atlasloot" + } + ], + "dire_maul_north": [ + { + "name": "Illyanna Ravenoak", + "x": 1220.9, + "y": 3130.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tendris Warpwood", + "x": 2074.4, + "y": 2142.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Magister Kalendris", + "x": 1985.3, + "y": 1811.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Immol'thar", + "x": 2189.1, + "y": 2327.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "x": 896.1, + "y": 2230.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Prince Tortheldrin", + "x": 3857.9, + "y": 593.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Hydrospawn", + "x": 2558.5, + "y": 1968.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lethtendris", + "x": 2781.4, + "y": 1863.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Alzzin the Wildshaper", + "x": 3596.7, + "y": 1150.1, + "rare": false, + "source": "atlasloot" + } + ], + "dire_maul_east": [ + { + "name": "Stomper Kreeg ", + "x": 3447.2, + "y": 3937.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Slip'kik", + "x": 2647.3, + "y": 3201.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Captain Kromcrush", + "x": 2770.1, + "y": 2740.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "King Gordok", + "x": 2774.8, + "y": 1030.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "x": 3655.9, + "y": 2711.1, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "x": 2960.3, + "y": 619.1, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tendris Warpwood", + "x": 2807.2, + "y": 2891.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Magister Kalendris", + "x": 2774.8, + "y": 2299.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tsu'zee (Rare)", + "x": 3616.4, + "y": 756.1, + "rare": true, + "source": "atlasloot" + }, + { + "name": "Immol'thar", + "x": 2849.0, + "y": 3223.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "x": 2378.3, + "y": 3050.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Prince Tortheldrin", + "x": 3456.5, + "y": 121.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "x": 3053.0, + "y": 1556.9, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Old Ironbark", + "x": 2960.3, + "y": 3937.5, + "rare": false, + "source": "atlasloot" + } + ], + "dire_maul_west": [ + { + "name": "Guard Mol'dar", + "x": 4061.4, + "y": 3117.0, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Stomper Kreeg ", + "x": 3611.3, + "y": 2815.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Fengus", + "x": 2696.1, + "y": 3177.2, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Slip'kik", + "x": 1866.8, + "y": 2431.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Captain Kromcrush", + "x": 2134.8, + "y": 2190.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "King Gordok", + "x": 2144.9, + "y": 1297.6, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "x": 1280.2, + "y": 2352.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Hydrospawn", + "x": 2600.0, + "y": 2107.4, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lethtendris", + "x": 2777.0, + "y": 2009.5, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Alzzin the Wildshaper", + "x": 3424.2, + "y": 1342.8, + "rare": false, + "source": "atlasloot" + } + ], + "blackwinglair": [ + { + "name": "Draconic for Dummies (Chapter VII)", + "x": 2120.6, + "y": 1770.8, + "rare": false, + "source": "atlasloot" + }, + { + "name": "Master Elemental Shaper Krixix", + "x": 2569.4, + "y": 1722.8, + "rare": false, + "source": "atlasloot" + } + ], + "naxxramas_classic": [ + { + "name": "|cffcc6666Instructor Razuvious", + "x": 2683.1, + "y": 1682.3, + "rare": false, + "source": "atlasloot" + }, + { + "name": "|cffcc6666Gothik the Harvester", + "x": 3996.6, + "y": 1869.7, + "rare": false, + "source": "atlasloot" + }, + { + "name": "|cffcc6666Four Horsemen Chest", + "x": 1971.6, + "y": 2097.3, + "rare": false, + "source": "atlasloot" + } + ] + } +} \ No newline at end of file diff --git a/tools/atlasloot_extras.py b/tools/atlasloot_extras.py new file mode 100644 index 0000000..9ca20ba --- /dev/null +++ b/tools/atlasloot_extras.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +Pull AtlasLootAscension's "rare" bosses (and selected interactives) into +keystone.guru pixel space so they can be rendered alongside kg's data. + +Approach: + 1. Match bosses between AL and kg by name (kg cls>=3 + AL dungeonskull) + 2. Per (AL-dungeon, kg-tile-key) pair: fit an affine x/y transform + pixel = al * scale + offset using 2+ matched pairs. + 3. Apply transform to AL entries we don't already have in kg + (pinType=None entries explicitly tagged "(Rare)" plus a curated + subset of named non-rare entries we still want — graveyards, etc.). + 4. Emit data/atlasloot_extras.json keyed by kg tile_key. + +Notes: + - AL coords are 0-100 percent of the Atlas-addon BLP. kg pixels are + 0..6144 / 0..4096 at z=4. + - Multi-wing AL dungeons (DireMaul, ScarletMonastery, BlackrockSpire) + have a single coord space; we'd need per-wing transforms keyed by + boss-presence. Handled by AL_TO_KG mapping below — we point each AL + dungeon at the kg wing(s) it overlaps with and only apply matches + that fall in that wing's content area (loose check on transformed + pixel bounds). +""" +from __future__ import annotations +import json +import re +import sys +from collections import defaultdict +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +KG_DIR = ROOT / "data" / "kg" +REGISTRY = ROOT / "data" / "kg_dungeons.json" +OUT = ROOT / "data" / "atlasloot_extras.json" +ATLASLOOT_PATH = Path("/tmp/atlasloot_maps.json") + +# For multi-wing kg dungeons we run the transform per wing; the same AL +# boss can plausibly land inside more than one wing's image. WING_FORCE +# pins specific bosses to one wing so we don't render duplicates. +# Keys: kg tile_key. Values: substrings of AL boss names that belong here. +WING_FORCE = { + "lower_blackrock_spire": [ + "Spirestone Butcher", "Bijou", "Halycon", "Roughshod Pike", + "Human Remains", "Ghok Bashguud", + ], + "upper_blackrock_spire": [ + "Bannok Grimaxe", "Jed Runewatcher", "Awbee", + "Darkstone Tablet", "Blackwing Lair", + ], + "dire_maul_east": ["Tsu'zee", "Pylons", "Old Ironbark"], + "dire_maul_north": ["Cho'Rush"], + "scarlet_monastery_cathedral": ["Pumpkin Shrine"], +} +# Bosses appearing in any WING_FORCE list are excluded from every OTHER wing +# of their parent dungeon. Bosses not in any list still go to all wings +# they fit (we'll see "Burning Felguard (Summon)" in both BRS wings, which +# is actually correct — there's a summon stone in each). + +# AL dungeon-id → list of kg tile_keys (one or more for split dungeons). +AL_TO_KG = { + "RagefireChasm": ["ragefire_chasm"], + "TheDeadmines": ["deadmines"], + "WailingCaverns": ["wailing_caverns"], + "ShadowfangKeep": ["shadowfang_keep"], + "TheStockade": ["the_stockade"], + "BlackFathomDeeps": ["blackfathom_deeps"], + "Gnomeregan": ["gnomeregan"], + "RazorfenKraul": ["razorfen_kraul"], + "RazorfenDowns": ["razorfen_downs"], + "ScarletMonastery": ["scarlet_monastery_armory", "scarlet_monastery_cathedral", + "scarlet_monastery_graveyard", "scarlet_monastery_library"], + "Uldaman": ["uldaman"], + "ZulFarrak": ["zul_farrak"], + "Maraudon": ["maraudon"], + "BlackrockDepths": ["blackrock_depths"], + "BlackrockSpire": ["lower_blackrock_spire", "upper_blackrock_spire"], + "Stratholme": ["stratholme"], + "Scholomance": ["scholomance"], + "DireMaul": ["dire_maul_north", "dire_maul_east", "dire_maul_west"], + "MoltenCore": ["moltencore"], + "BlackwingLair": ["blackwinglair"], + "ZulGurub": ["zulgurub"], + "Naxxramas60": ["naxxramas_classic"], +} + +# Names we strip when matching ("Boss Name " → "Boss Name", "(Rare)", etc.) +NAME_STRIPS = re.compile(r"\s*<[^>]+>|\s*\([^)]+\)") + + +def norm_name(s: str) -> str: + return NAME_STRIPS.sub("", s).strip().lower() + + +def parse_js_var(p: Path) -> dict: + s = p.read_text() + return json.loads(s[s.find("{"):].rstrip(" ;\n")) + + +def get_kg_bosses_per_floor(tile_key: str): + """Return list of (npc_id, name, x_px, y_px, classification) for + boss-like enemies in the dungeon's first floor only — enough to fit + a transform; later floors share the same coord scale anyway.""" + sf = parse_js_var(KG_DIR / tile_key / "split_floors.js") + lang = parse_js_var(KG_DIR / tile_key / "lang.js") + name_by_id = {n["id"]: n["name"] for n in lang.get("dungeonNpcs", [])} + cls_by_id = {n["id"]: n.get("classification_id") for n in lang.get("dungeonNpcs", [])} + # First floor: pick the lowest floor_id present + enemies = sf["dungeon"].get("enemies", []) + if not enemies: + return [] + out = [] + for e in enemies: + npc_id = e.get("npc_id") + name = name_by_id.get(npc_id, "?") + cls = cls_by_id.get(npc_id, 0) + if cls < 3: + continue # only bosses + # kg coord transform: pixel_x = lng*16, pixel_y = -lat*16 (z=4) + pix_x = e["lng"] * 16 + pix_y = -e["lat"] * 16 + out.append((npc_id, name, pix_x, pix_y, cls, e.get("floor_id"))) + return out + + +def fit_transform(al_pts, kg_pts): + """Least-squares fit of pixel = al * scale + offset, separately per axis. + Returns (sx, ox, sy, oy) or None if too few matches / degenerate.""" + if len(al_pts) < 2: + return None + # Build linear system + n = len(al_pts) + sum_x = sum(p[0] for p in al_pts) + sum_y = sum(p[1] for p in al_pts) + sum_X = sum(p[0] for p in kg_pts) + sum_Y = sum(p[1] for p in kg_pts) + sum_xX = sum(a[0] * b[0] for a, b in zip(al_pts, kg_pts)) + sum_yY = sum(a[1] * b[1] for a, b in zip(al_pts, kg_pts)) + sum_xx = sum(p[0] ** 2 for p in al_pts) + sum_yy = sum(p[1] ** 2 for p in al_pts) + + den_x = n * sum_xx - sum_x * sum_x + den_y = n * sum_yy - sum_y * sum_y + if abs(den_x) < 1e-9 or abs(den_y) < 1e-9: + return None + sx = (n * sum_xX - sum_x * sum_X) / den_x + ox = (sum_X - sx * sum_x) / n + sy = (n * sum_yY - sum_y * sum_Y) / den_y + oy = (sum_Y - sy * sum_y) / n + return sx, ox, sy, oy + + +def collect_al_entries(al_dungeon: dict): + """Yield (name, x, y, is_rare, raw_name) for entries with cords.""" + for k, v in al_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 + is_rare = "(rare" in name.lower() + yield name, cords[0], cords[1], is_rare, name + + +def main() -> int: + if not ATLASLOOT_PATH.exists(): + print(f"missing {ATLASLOOT_PATH}; export with bisbeard/export_atlasloot_maps.lua", file=sys.stderr) + return 1 + atlasloot = json.loads(ATLASLOOT_PATH.read_text())["OriginalWoW"] + + # Pre-load kg bosses per relevant tile_key + kg_bosses = {} + for kgs in AL_TO_KG.values(): + for k in kgs: + try: + kg_bosses[k] = get_kg_bosses_per_floor(k) + except FileNotFoundError: + kg_bosses[k] = [] + + extras = defaultdict(list) + transforms = {} + summary = [] + + for al_id, kg_keys in AL_TO_KG.items(): + al_dungeon = atlasloot.get(al_id) + if not al_dungeon: + continue + al_entries = list(collect_al_entries(al_dungeon)) + # Match AL entries to kg bosses by normalized name + al_by_norm = {norm_name(e[0]): e for e in al_entries if not e[3]} # non-rare bosses for fitting + # For each kg wing, fit a transform from anchors that match + for kg_key in kg_keys: + kg_b = kg_bosses.get(kg_key, []) + if not kg_b: + continue + kg_by_norm = {norm_name(b[1]): b for b in kg_b} + common_names = set(al_by_norm) & set(kg_by_norm) + if len(common_names) < 2: + summary.append(f"{al_id} → {kg_key}: only {len(common_names)} anchor(s); skipping") + continue + al_pts = [] + kg_pts = [] + for n in sorted(common_names): + al_e = al_by_norm[n] + kg_e = kg_by_norm[n] + al_pts.append((al_e[1], al_e[2])) + kg_pts.append((kg_e[2], kg_e[3])) + tr = fit_transform(al_pts, kg_pts) + if not tr: + summary.append(f"{al_id} → {kg_key}: degenerate fit") + continue + sx, ox, sy, oy = tr + transforms[(al_id, kg_key)] = tr + summary.append( + f"{al_id} → {kg_key}: {len(common_names)} anchors, " + f"scale=({sx:.2f},{sy:.2f}) offset=({ox:.0f},{oy:.0f})" + ) + + # Apply transform to AL entries that don't already exist as kg bosses + kg_existing_norms = {norm_name(b[1]) for b in kg_b} + # Build the set of "this dungeon's bosses pinned to a different + # wing" so we can exclude them here. + other_wings = [k for k in kg_keys if k != kg_key] + forced_elsewhere = set() + for w in other_wings: + for needle in WING_FORCE.get(w, []): + forced_elsewhere.add(needle.lower()) + this_wing_pins = {n.lower() for n in WING_FORCE.get(kg_key, [])} + + for e in al_entries: + name = e[0] + lname = name.lower() + if norm_name(name) in kg_existing_norms: + continue # already in kg + # If this boss is force-pinned to another wing, skip here. + if any(p in lname for p in forced_elsewhere) and \ + not any(p in lname for p in this_wing_pins): + continue + px = e[1] * sx + ox + py = e[2] * sy + oy + # Reject points outside the kg image bounds (multi-wing + # dungeons: a boss in a wing is OOB on a different wing's + # image). + if not (-200 <= px <= 6344 and -200 <= py <= 4296): + continue + # Also reject points that are >1.5x the diagonal away from + # all anchors — likely a wrong-wing match. + anchor_dists = [ + ((px - a[0]) ** 2 + (py - a[1]) ** 2) ** 0.5 for a in kg_pts + ] + if min(anchor_dists) > 4500: + continue + extras[kg_key].append({ + "name": name, + "x": round(px, 1), + "y": round(py, 1), + "rare": e[3], + "source": "atlasloot", + }) + + OUT.write_text(json.dumps({ + "_comment": "Supplemental rare bosses + interactives lifted from AtlasLootAscension and transformed into keystone.guru pixel space. Generated by tools/atlasloot_extras.py.", + "transforms": {f"{k[0]}→{k[1]}": v for k, v in transforms.items()}, + "extras": dict(extras), + }, indent=2)) + + print("transform summary:") + for s in summary: + print(f" {s}") + print(f"\ntotal extras: {sum(len(v) for v in extras.values())} across {len(extras)} dungeon-wings") + for k in sorted(extras): + rares = [e for e in extras[k] if e["rare"]] + print(f" {k:32s} {len(extras[k]):3d} entries ({len(rares)} rare)") + print(f"\nwrote {OUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/kg_build_data.py b/tools/kg_build_data.py index f4ada9a..6acd59c 100644 --- a/tools/kg_build_data.py +++ b/tools/kg_build_data.py @@ -20,10 +20,12 @@ import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -KG_DIR = ROOT / "data" / "kg" -REGISTRY = ROOT / "data" / "kg_dungeons.json" +DATA = ROOT / "data" +KG_DIR = DATA / "kg" +REGISTRY = DATA / "kg_dungeons.json" WEB_ASSETS = ROOT / "web" / "assets" WEB_MAPS = WEB_ASSETS / "maps" +EXTRAS_PATH = DATA / "atlasloot_extras.json" OUT_PATH = WEB_ASSETS / "dungeons.json" # Map kg's mapIconType names → simple labels we render in the UI. @@ -216,6 +218,11 @@ def main() -> int: registry = json.loads(REGISTRY.read_text()) summary = json.loads((KG_DIR / "_summary.json").read_text()) + # AtlasLoot supplemental rares + interactives, keyed by kg tile_key. + extras_db = {} + if EXTRAS_PATH.exists(): + extras_db = json.loads(EXTRAS_PATH.read_text()).get("extras", {}) + # Build a global icon-type index from the static.js if present, else # fall back to known IDs. icon_index: dict[int, str] = { @@ -246,6 +253,17 @@ def main() -> int: entry = build_one(d, summary, npc_index, icon_index) if entry: + extras = extras_db.get(d["tile_key"], []) + if extras: + entry["extras"] = [ + { + "name": e["name"], + "pos": [e["x"], e["y"]], + "rare": bool(e.get("rare")), + "source": e.get("source", "atlasloot"), + } + for e in extras + ] dungeons.append(entry) dungeons.sort(key=lambda d: d["name"]) diff --git a/web/app.js b/web/app.js index f558f1f..497332d 100644 --- a/web/app.js +++ b/web/app.js @@ -194,6 +194,17 @@ function renderOverlay() { } } + // AtlasLoot-derived extras: rare bosses + interactives kg doesn't ship. + // These are dungeon-level (not per-floor) and only render on floor 0 + // unless we get richer data; for single-floor dungeons that's all that + // matters, and for multi-floor we leave them on the first floor by + // default — the user can drag a Note over them on any floor. + if (state.floorIndex === 0 && state.current?.extras) { + for (const ex of state.current.extras) { + svg.appendChild(makeExtraPin(ex)); + } + } + // Route polyline if (wps.length > 1) { const path = document.createElementNS(SVG_NS, "polyline"); @@ -419,6 +430,42 @@ function makeEnemyPin(e) { return g; } +function makeExtraPin(ex) { + // Silver-blue ring with skull glyph for rares; muted square for non-rare + // interactives (postboxes, summon spots, etc.). Strip the trailing + // "(Rare)" / "(Rare, Wanders)" tag from the displayed tooltip — we + // surface "rare" via the visual treatment. + const g = document.createElementNS(SVG_NS, "g"); + g.setAttribute("class", ex.rare ? "extra rare" : "extra"); + g.setAttribute("transform", `translate(${ex.pos[0]},${ex.pos[1]})`); + if (ex.rare) { + const c = document.createElementNS(SVG_NS, "circle"); + c.setAttribute("r", 26); + c.setAttribute("fill", "#bfd6f0"); + c.setAttribute("stroke", "#3b6db0"); + c.setAttribute("stroke-width", "5"); + g.appendChild(c); + const t = document.createElementNS(SVG_NS, "text"); + t.setAttribute("y", 11); + t.setAttribute("font-size", "30"); + t.setAttribute("text-anchor", "middle"); + t.setAttribute("fill", "#1a3360"); + t.setAttribute("font-weight", "900"); + t.textContent = "☠"; + g.appendChild(t); + } else { + const r = document.createElementNS(SVG_NS, "rect"); + r.setAttribute("x", -10); r.setAttribute("y", -10); + r.setAttribute("width", 20); r.setAttribute("height", 20); + r.setAttribute("fill", "#7e8290"); + r.setAttribute("stroke", "#1a1208"); + r.setAttribute("stroke-width", "2"); + g.appendChild(r); + } + g.dataset.tooltip = ex.name + (ex.rare ? "" : ""); + return g; +} + function makeIconMarker(ic) { const g = document.createElementNS(SVG_NS, "g"); g.setAttribute("class", `icon icon-${ic.type}`); @@ -438,14 +485,54 @@ function makeIconMarker(ic) { t.setAttribute("font-weight", "700"); t.textContent = "i"; g.appendChild(t); - } else if (ic.type === "door") { + } else if (ic.type === "door" || ic.type === "door_locked") { const r = document.createElementNS(SVG_NS, "rect"); r.setAttribute("x", -10); r.setAttribute("y", -14); r.setAttribute("width", 20); r.setAttribute("height", 28); - r.setAttribute("fill", "#B58A3F"); + r.setAttribute("fill", ic.type === "door_locked" ? "#7a4a1a" : "#B58A3F"); r.setAttribute("stroke", "#000"); r.setAttribute("stroke-width", "2"); g.appendChild(r); + } else if (ic.type === "start") { + // Green entry-flag triangle + const p = document.createElementNS(SVG_NS, "polygon"); + p.setAttribute("points", "-12,-14 14,0 -12,14"); + p.setAttribute("fill", "#6ad17b"); + p.setAttribute("stroke", "#0a2a12"); + p.setAttribute("stroke-width", "2"); + g.appendChild(p); + } else if (ic.type === "graveyard") { + // Tombstone: rounded-top rect with a cross + const r = document.createElementNS(SVG_NS, "rect"); + r.setAttribute("x", -12); r.setAttribute("y", -14); + r.setAttribute("width", 24); r.setAttribute("height", 28); + r.setAttribute("rx", 10); r.setAttribute("ry", 10); + r.setAttribute("fill", "#9aa1aa"); + r.setAttribute("stroke", "#000"); + r.setAttribute("stroke-width", "2"); + g.appendChild(r); + const t = document.createElementNS(SVG_NS, "text"); + t.setAttribute("y", 6); + t.setAttribute("font-size", "20"); + t.setAttribute("text-anchor", "middle"); + t.setAttribute("fill", "#1a1208"); + t.setAttribute("font-weight", "900"); + t.textContent = "✝"; + g.appendChild(t); + } else if (ic.type === "dot_yellow") { + const c = document.createElementNS(SVG_NS, "circle"); + c.setAttribute("r", 10); + c.setAttribute("fill", "#f0c674"); + c.setAttribute("stroke", "#1a1208"); + c.setAttribute("stroke-width", "2"); + g.appendChild(c); + } else if (ic.type === "gateway") { + const c = document.createElementNS(SVG_NS, "circle"); + c.setAttribute("r", 14); + c.setAttribute("fill", "#9b59b6"); + c.setAttribute("stroke", "#000"); + c.setAttribute("stroke-width", "2"); + g.appendChild(c); } if (ic.comment) { g.dataset.tooltip = ic.comment; diff --git a/web/assets/dungeons.json b/web/assets/dungeons.json index 04b4dd9..a14ac34 100644 --- a/web/assets/dungeons.json +++ b/web/assets/dungeons.json @@ -24981,6 +24981,116 @@ } ] } + ], + "extras": [ + { + "name": "Kharan Mighthammer", + "pos": [ + 3166.4, + 3541.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Commander Gor'shak <Kargath Expeditionary Force>", + "pos": [ + 3293.2, + 3759.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Marshal Windsor", + "pos": [ + 3229.8, + 4065.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Ring of Law", + "pos": [ + 3103.0, + 2623.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Vault", + "pos": [ + 3673.5, + 2841.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Ring of Law", + "pos": [ + 3103.0, + 3847.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Shadowforge Lock", + "pos": [ + 2532.5, + 3934.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Grim Guzzler", + "pos": [ + 3039.6, + 2710.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Summoner's Tomb", + "pos": [ + 3293.2, + 1049.1 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Lyceum", + "pos": [ + 4117.3, + 480.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Black Forge", + "pos": [ + 3863.7, + 1049.1 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Molten Core", + "pos": [ + 4180.7, + 1530.0 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -27343,6 +27453,26 @@ "patrols": [], "icons": [] } + ], + "extras": [ + { + "name": "Draconic for Dummies (Chapter VII)", + "pos": [ + 2120.6, + 1770.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Master Elemental Shaper Krixix", + "pos": [ + 2569.4, + 1722.8 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -34598,6 +34728,134 @@ } ] } + ], + "extras": [ + { + "name": "Stomper Kreeg <The Drunk>", + "pos": [ + 3447.2, + 3937.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Slip'kik", + "pos": [ + 2647.3, + 3201.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Captain Kromcrush", + "pos": [ + 2770.1, + 2740.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "King Gordok", + "pos": [ + 2774.8, + 1030.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "pos": [ + 3655.9, + 2711.1 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "pos": [ + 2960.3, + 619.1 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tendris Warpwood", + "pos": [ + 2807.2, + 2891.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Magister Kalendris", + "pos": [ + 2774.8, + 2299.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tsu'zee (Rare)", + "pos": [ + 3616.4, + 756.1 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Immol'thar", + "pos": [ + 2849.0, + 3223.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "pos": [ + 2378.3, + 3050.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Prince Tortheldrin", + "pos": [ + 3456.5, + 121.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pylons", + "pos": [ + 3053.0, + 1556.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Old Ironbark", + "pos": [ + 2960.3, + 3937.5 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -40724,6 +40982,89 @@ } ] } + ], + "extras": [ + { + "name": "Illyanna Ravenoak", + "pos": [ + 1220.9, + 3130.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Tendris Warpwood", + "pos": [ + 2074.4, + 2142.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Magister Kalendris", + "pos": [ + 1985.3, + 1811.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Immol'thar", + "pos": [ + 2189.1, + 2327.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "pos": [ + 896.1, + 2230.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Prince Tortheldrin", + "pos": [ + 3857.9, + 593.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Hydrospawn", + "pos": [ + 2558.5, + 1968.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lethtendris", + "pos": [ + 2781.4, + 1863.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Alzzin the Wildshaper", + "pos": [ + 3596.7, + 1150.1 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -46345,6 +46686,98 @@ } ] } + ], + "extras": [ + { + "name": "Guard Mol'dar", + "pos": [ + 4061.4, + 3117.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Stomper Kreeg <The Drunk>", + "pos": [ + 3611.3, + 2815.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Fengus", + "pos": [ + 2696.1, + 3177.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Guard Slip'kik", + "pos": [ + 1866.8, + 2431.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Captain Kromcrush", + "pos": [ + 2134.8, + 2190.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "King Gordok", + "pos": [ + 2144.9, + 1297.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lord Hel'nurath (Summon)", + "pos": [ + 1280.2, + 2352.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Hydrospawn", + "pos": [ + 2600.0, + 2107.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Lethtendris", + "pos": [ + 2777.0, + 2009.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Alzzin the Wildshaper", + "pos": [ + 3424.2, + 1342.8 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -54156,6 +54589,17 @@ } ] } + ], + "extras": [ + { + "name": "Clean Room", + "pos": [ + 5674.2, + 2664.2 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -63770,6 +64214,152 @@ "patrols": [], "icons": [] } + ], + "extras": [ + { + "name": "Mother Smolderweb", + "pos": [ + 3949.6, + 2798.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Burning Felguard (Rare, Summon)", + "pos": [ + 2473.0, + 2039.6 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Vaelan (Upper)", + "pos": [ + 3105.8, + 1423.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warosh <The Cursed> (Wanders)", + "pos": [ + 3738.6, + 1517.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bijou", + "pos": [ + 3879.3, + 2134.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Roughshod Pike", + "pos": [ + 4160.5, + 2229.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Spirestone Butcher (Rare)", + "pos": [ + 3035.5, + 2229.3 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Human Remains (Lower)", + "pos": [ + 2473.0, + 2134.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Urok's Tribute Pile", + "pos": [ + 2683.9, + 2087.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Halycon", + "pos": [ + 2121.4, + 3509.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pyroguard Emberseer", + "pos": [ + 1629.2, + 759.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Solakar Flamewreath", + "pos": [ + 2191.7, + 1328.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Goraluk Anvilcrack <Blackhand Legion Armorsmith>", + "pos": [ + 1769.8, + 759.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warchief Rend Blackhand", + "pos": [ + 2894.9, + 759.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Beast", + "pos": [ + 3949.6, + 996.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "General Drakkisath", + "pos": [ + 1980.7, + 1849.9 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -77515,6 +78105,26 @@ } ] } + ], + "extras": [ + { + "name": "Veng <The Fifth Khan>", + "pos": [ + 3732.3, + 946.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Maraudos <The Fourth Khan>", + "pos": [ + 3299.8, + 3056.4 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -99653,6 +100263,35 @@ "patrols": [], "icons": [] } + ], + "extras": [ + { + "name": "|cffcc6666Instructor Razuvious", + "pos": [ + 2683.1, + 1682.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "|cffcc6666Gothik the Harvester", + "pos": [ + 3996.6, + 1869.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "|cffcc6666Four Horsemen Chest", + "pos": [ + 1971.6, + 2097.3 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -113935,6 +114574,26 @@ } ] } + ], + "extras": [ + { + "name": "Roogug", + "pos": [ + 4021.7, + 2011.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Blind Hunter (Rare)", + "pos": [ + 753.9, + 1272.6 + ], + "rare": true, + "source": "atlasloot" + } ] }, { @@ -118385,6 +119044,62 @@ } ] } + ], + "extras": [ + { + "name": "Interrogator Vishas", + "pos": [ + 4212.5, + 2099.8 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Pumpkin Shrine (Hallow's End)", + "pos": [ + 2384.8, + 2196.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bloodmage Thalnos", + "pos": [ + 1775.6, + 1971.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Houndmaster Loksey", + "pos": [ + 2080.2, + 2904.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Arcanist Doan", + "pos": [ + 4770.9, + 2647.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Herod <The Scarlet Champion>", + "pos": [ + 4567.8, + 522.0 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -120603,6 +121318,62 @@ } ] } + ], + "extras": [ + { + "name": "Houndmaster Loksey", + "pos": [ + 1862.6, + 2989.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Arcanist Doan", + "pos": [ + 5087.3, + 2801.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Herod <The Scarlet Champion>", + "pos": [ + 4843.9, + 1253.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "High Inquisitor Fairbanks", + "pos": [ + 3444.5, + 1605.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Scarlet Commander Mograine", + "pos": [ + 3018.6, + 1676.0 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "High Inquisitor Whitemane", + "pos": [ + 2957.8, + 1394.6 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -122759,6 +123530,26 @@ } ] } + ], + "extras": [ + { + "name": "Interrogator Vishas", + "pos": [ + 4469.4, + 1783.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bloodmage Thalnos", + "pos": [ + 1631.3, + 1487.0 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -131195,6 +131986,35 @@ } ] } + ], + "extras": [ + { + "name": "Blood Steward of Kirtonos", + "pos": [ + 5213.8, + 1854.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Deed to Tarren Mill", + "pos": [ + 1737.2, + 2367.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Torch Lever", + "pos": [ + 2964.2, + 2274.2 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -134850,6 +135670,53 @@ } ] } + ], + "extras": [ + { + "name": "Investigator Fezzen Brasstacks (Love is in the Air)", + "pos": [ + 3373.4, + 2654.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Deathstalker Vincent", + "pos": [ + 3558.3, + 2408.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Apothecary Trio (Love is in the Air)", + "pos": [ + 2448.6, + 2039.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Fel Steed", + "pos": [ + 2202.0, + 2326.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Deathsworn Captain (Rare)", + "pos": [ + 3681.6, + 2121.6 + ], + "rare": true, + "source": "atlasloot" + } ] }, { @@ -144302,6 +145169,134 @@ } ] } + ], + "extras": [ + { + "name": "Crusaders' Square Postbox", + "pos": [ + 2415.9, + 1333.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Market Row Postbox", + "pos": [ + 3976.3, + 1034.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Festival Lane Postbox", + "pos": [ + 4496.5, + 1076.9 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "King's Square Postbox", + "pos": [ + 3739.9, + 2102.2 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Fras Siabi's Postbox", + "pos": [ + 3314.3, + 2785.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Atiesh <Hand of Sargeras> (Summon)", + "pos": [ + 4780.2, + 1461.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Elder Farwhisper (Lunar Festival)", + "pos": [ + 4449.2, + 564.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Malor the Zealous", + "pos": [ + 2132.2, + 1632.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Crimson Hammersmith (Summon)", + "pos": [ + 1328.3, + 1888.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Cannon Master Willey", + "pos": [ + 950.0, + 2016.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Archivist Galford", + "pos": [ + 2037.6, + 2999.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Elders' Square Postbox", + "pos": [ + 3881.8, + 3084.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Aurius", + "pos": [ + 3929.1, + 2999.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Stonespine (Rare)", + "pos": [ + 4023.6, + 2016.7 + ], + "rare": true, + "source": "atlasloot" + } ] }, { @@ -149838,6 +150833,17 @@ } ] } + ], + "extras": [ + { + "name": "Defias Gunpowder", + "pos": [ + 1572.7, + 2343.0 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -157792,6 +158798,35 @@ } ] } + ], + "extras": [ + { + "name": "Remains of a Paladin", + "pos": [ + 3262.4, + 2616.3 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Discs of Norgannon (Lower)", + "pos": [ + 2440.8, + 332.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "The Discs of Norgannon (Lower)", + "pos": [ + 3072.8, + 1370.5 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -163317,6 +164352,152 @@ } ] } + ], + "extras": [ + { + "name": "War Master Voone", + "pos": [ + 3121.7, + 2058.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Mother Smolderweb", + "pos": [ + 3859.8, + 2602.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Burning Felguard (Rare, Summon)", + "pos": [ + 2568.2, + 2058.6 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Shadow Hunter Vosh'gajin", + "pos": [ + 3367.7, + 2602.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Vaelan (Upper)", + "pos": [ + 3121.7, + 1616.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Warosh <The Cursed> (Wanders)", + "pos": [ + 3675.3, + 1684.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Highlord Omokk", + "pos": [ + 2322.2, + 2262.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Urok's Tribute Pile", + "pos": [ + 2752.7, + 2092.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Bannok Grimaxe <Firebrand Legion Champion> (Rare)", + "pos": [ + 2752.7, + 2432.6 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Quartermaster Zigris <Bloodaxe Legion>", + "pos": [ + 3306.2, + 3112.7 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Darkstone Tablet", + "pos": [ + 1891.6, + 1650.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Overlord Wyrmthalak", + "pos": [ + 3490.8, + 2398.6 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Jed Runewatcher <Blackhand Legion> (Rare)", + "pos": [ + 2260.7, + 1038.4 + ], + "rare": true, + "source": "atlasloot" + }, + { + "name": "Goraluk Anvilcrack <Blackhand Legion Armorsmith>", + "pos": [ + 1953.1, + 1140.4 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Awbee", + "pos": [ + 2937.2, + 1446.5 + ], + "rare": false, + "source": "atlasloot" + }, + { + "name": "Blackwing Lair", + "pos": [ + 3306.2, + 970.4 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -171086,6 +172267,17 @@ } ] } + ], + "extras": [ + { + "name": "Disciple of Naralex", + "pos": [ + 3080.1, + 1961.7 + ], + "rare": false, + "source": "atlasloot" + } ] }, { @@ -177799,6 +178991,17 @@ } ] } + ], + "extras": [ + { + "name": "Sandfury Executioner", + "pos": [ + 1284.2, + 712.6 + ], + "rare": false, + "source": "atlasloot" + } ] }, {