v9.8.9: Badlands Trophy quest, 7:XT NPC data, fix AreaId CRITICAL spam

This commit is contained in:
Xurkon
2026-03-02 21:11:30 -06:00
parent 50101a8e8e
commit b13833935a
20 changed files with 4112 additions and 3155 deletions
+114
View File
@@ -0,0 +1,114 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"Lua.runtime.version": "Lua 5.1",
"Lua.diagnostics.globals": [
"QuestieLoader",
"QuestieCompat",
"Questie",
"MAX_QUEST_LOG_INDEX",
"GetNumQuestLogEntries",
"GetQuestLogTitle",
"GetQuestLogQuestText",
"GetQuestObjectives",
"GetNumQuestLeaderBoards",
"GetQuestIndexForWatch",
"IsQuestWatched",
"QuestWatch_Update",
"WatchFrame_Update",
"HaveQuestData",
"GetTitleText",
"GetObjectiveText",
"GetQuestID",
"GetItemInfoFromHyperlink",
"GetQuestLink",
"GetQuestIndexByID",
"ChatEdit_GetActiveWindow",
"ChatEdit_InsertLink",
"QuestLog_SetSelection",
"QuestLog_Update",
"QuestLogTitleButton_OnClick",
"QuestLogListScrollFrame",
"FauxScrollFrame_GetOffset",
"QuestLogExFrame",
"ClassicQuestLog",
"QuestLogEx",
"StaticPopupDialogs",
"StaticPopup_Show",
"STATICPOPUP_NUMDIALOGS",
"QUEST_WATCH_NO_EXPIRE",
"RemoveQuestWatch",
"AddQuestWatch",
"GetNumLootItems",
"GetLootSlotInfo",
"GetLootSlotLink",
"GetBestMapForUnit",
"GetPlayerMapPosition",
"GetRealZoneText",
"GetInstanceInfo",
"GetUnitName",
"UnitGUID",
"UnitName",
"UnitLevel",
"UnitExists",
"UnitIsPlayer",
"UnitIsVisible",
"UnitClass",
"UnitReaction",
"UnitFactionGroup",
"GetFactionInfoByID",
"GetQuestLogRewardMoney",
"GetCoinTextureString",
"GetClassColor",
"IsModifiedClick",
"GetTime",
"IsShiftKeyDown",
"Minimap",
"GetCVar",
"GameTooltip",
"LibStub",
"Questie_BaseFrame",
"VoiceOver",
"format",
"strsplit",
"tinsert",
"tremove",
"wipe",
"hooksecurefunc",
"WorldMapFrame",
"C_Map",
"CreateFrame",
"InterfaceOptionsFrame",
"GetAddOnMetadata",
"GetBuildInfo",
"IsInInstance",
"UnitIsPVP",
"GetNumPartyMembers",
"GetNumRaidMembers",
"GetPartyMember",
"GetRaidRosterInfo",
"GetSpellInfo",
"GetSpellLink",
"GetContainerNumSlots",
"GetContainerItemInfo",
"GetContainerItemLink",
"PickupContainerItem",
"DeleteCursorItem",
"GetItemInfo",
"GetItemCount",
"IsEquippedItem",
"GetInventoryItemID",
"GetItemQualityColor",
"geterrorhandler",
"seterrorhandler",
"GetFramerate",
"GetLatency"
],
"Lua.diagnostics.disable": [
"duplicate-set-field",
"undefined-field"
],
"Lua.workspace.ignoreDir": [
"docs",
"workflow"
]
}
+78 -26
View File
@@ -1,8 +1,40 @@
# Changelog
## v9.8.9
### New Ebonhold Quests
- **Azeroth**: Badlands Trophy (50178) — Kill 1 Rare in Badlands. Includes tracking data for 10 Badlands rares: Broken Tooth, Zaricotl, 7:XT, Anathemus, Barnabus, Rumbler, Siege Golem, Digmaster Shovelphlange, Shadowforge Commander, War Golem.
### NPC Data
- **classicNPCFixes.lua**: Injected spawn coordinates for NPC 14224 (7:XT \<Long Distance Recovery Unit\>, Badlands). The native `wotlkNpcDB` entry for ID 14224 is a different NPC (Gnomeregan's Instance Recovery Unit) and `classicNpcDB` had no entry — all 74 Wowhead patrol coordinates for zone 3 (Badlands) were injected.
### Bug Fixes
- **[UI]** Fixed spammy `[CRITICAL] No AreaId found for UiMapId` errors appearing in chat when entering Ascension hub cities (Stormwind, Darnassus, Shattrath, etc.). On Ascension, `C_Map.GetBestMapForUnit("player")` returns a continent-level UiMapId for certain cities (e.g. `1415` = Eastern Kingdoms, `1414` = Kalimdor, `1945` = Outland) — Questie logged a CRITICAL error since these have no AreaId mapping, even though the `nil` return was already handled gracefully by callers. Downgraded from `DEBUG_CRITICAL` to `DEBUG_DEVELOP` in `zoneDB.lua`.
## v9.8.8
### New Ebonhold Quests
- **Azeroth**: Alterac Trophy (includes missing Alterac Mountains spawn tracking data for Narillasanz, Cranky Benj, Gravis Slipknot, Araga, Lo'Grosh, Stone Fury, Jimmy the Bleeder, Skhowl). Northern Jungle Trophy (verified Stranglethorn Vale spawn tracking data). Southern Jungle Trophy (verified Stranglethorn Vale spawn tracking data for 4 rares).
- **Kalimdor**: Ashen Corruption (includes missing Ashenvale spawn tracking data for Demon NPCs), Spires of Chaos (includes missing Thousand Needles spawn tracking data for Elemental NPCs).
- **Outland**: Shadowmoon Trophy (verified Shadowmoon Valley spawn tracking data for Collidus the Warp-Watcher, Ambassador Jerrikar, Kraator). Demon's at the Edge (verified Blade's Edge Mountains spawn tracking data for 37 demons).
- **Northrend**: Fjord Trophy (includes missing Howling Fjord spawn tracking data for King Ping, Perobas the Bloodthirster, Vigdis the War Maiden). Frostbound Brood (verified Borean Tundra spawn tracking data for 16 dragonkin).
## v9.8.7
### Fixes
- **[Quest]** Corrected `GetQuestLogTitle` return value indices across multiple modules to match the WoW 3.3.5 client API. The 3.3.5 client returns `suggestedGroup` at index 4, shifting `isHeader` to index 5 and `questId` to index 9. Affected modules previously used indices 4 and 8 (or `select(8, ...)`) and would misidentify quest headers as regular quests and incorrectly assign `isDaily` as `questId`.
- **[Quest]** Removed premature `break` on nil `title` in quest log iteration loops. On this server, quest log slots can be non-contiguous, causing early loop termination to silently skip valid quests. Loops now use a `if title and (not isHeader) then` guard to safely skip empty slots without aborting iteration.
- **Affected modules:** `QuestieValidateGameCache`, `QuestEventHandler`, `QuestLogCache`, `TooltipHandler`, `TrackerUtils`, `QuestieTracker`, `QuestieLearner`.
## v9.8.6
### New Ebonhold Quests
- **Outland**: Blade's Edge Trophy (includes missing Blade's Edge Mountains spawn tracking data for Morcrush, Hemathion, Speaker Mar'grom). Fel Scars of Nagrand (includes missing Nagrand spawn tracking data for Voidwalker Minions, etc). Netherstorm Trophy (includes missing Netherstorm spawn tracking data for Nuramoc, Ever-Core the Punisher, Chief Engineer Lorthander).
- **Northrend**: Borean Trophy (includes missing Borean Tundra spawn tracking data for Fumblub Gearwind, Icehorn, Old Crystalbark).
- **Kalimdor**: Teldrassil Trophy (includes missing Teldrassil spawn tracking data for Threggil, Blackmoss the Fetid, Duskstalker, Uruson, Fury Shelda, Grimmaw). Darkshore Defense (complete 6 quests in Darkshore). Bloodmyst Recovery (complete 6 quests in Bloodmyst Isle).
@@ -11,11 +43,13 @@
## v9.8.4
### Fixes
- **[Quest]** Fixed a Lua crash ("attempt to index global 'QuestiePlayer'") occurring during chat message processing for auto-completing quests, by explicitly requiring the `QuestiePlayer` module in `QuestEventHandler`.
## v9.8.3
### Fixes
- **[Quest]** Extended `HideCondition` support to regular monster, object, and item objectives in the database.
- **[Quest]** Added `hideIfQuestActive` conditions to quest 13010 objectives to resolve icon overlap at King Jokkum.
- **[Database]** Corrected the finisher for "You Can't Miss Him" (12966) to NPC 30127, properly moving the arrow to Fjorn's Anvil.
@@ -24,32 +58,36 @@
## v9.8.2
### Fixes
- **[Quest]** Updated the objective tooltip for "Forging an Alliance" (12924) at King Jokkum to explicitly instruct players to pick up the breadcrumb quest "You Can't Miss Him" (12966).
## v9.8.1
### Fixes
- **[Quest]** Fixed QuestieArrow direction for "Forging an Alliance" (12924). Added an extra objective to speak with King Jokkum, ensuring the arrow points toward the breadcrumb quest start at the beginning of the quest.
## v9.8.0
### Fixes
- **[Quest]** Modified `BAG_UPDATE_DELAYED` to trigger a quest log update and instantly refresh the tracker when items are deposited directly into bags by autoloot bots that bypass standard item events.
- **[Map]** Fixed an issue where "Special Objectives" (e.g., source item drops) failed to check player bag contents upon completion, causing their map icons to linger indefinitely until the main quest was turned in.
## v9.7.12
### Fixes
- **[Quest]** Fixed an issue where tracking icons for dynamically updated custom quests like Peak Predators would prematurely disappear due to sync delays on WotLK servers.
- **[Map]** Resolved a frame pool leak that prevented the yellow Finisher icon from appearing immediately on the map after turning in or completing a quest.
- **[Database]** Corrected an error that improperly identified Item Finishers as GameObject Finishers on custom servers, causing map pinpointing errors (e.g. `[QuestieDB:GetObject] rawdata is nil for objectID:`).
- **[Database]** Appended correct fallback spawn data for the "Thorim" listen bunny (`NPC 30514`) so Sibling Rivalry's turn in/listen point functions correctly on WotLK clients.
## v9.7.11
### Fixes
- **[Quest]** Fixed an issue where quest objective icons and waypoints failed to clear from the map after a quest was completed or abandoned.
- **[Tracker]** Prevented quests from falsely flagging as complete and wiping tracking data when the World of Warcraft server drops its objective arrays. Fixes "Peak Predators" icons disappearing randomly.
- **[Database]** Corrected an error that improperly identified Item Finishers as GameObject Finishers on custom servers, causing map pinpointing errors (e.g. `[QuestieDB:GetObject] rawdata is nil for objectID:`).
@@ -58,6 +96,7 @@
## v9.7.10
### Fixes
- **[Arrow]** Refactored Arrow logic to drastically improve target distance calculations and prioritize targets correctly based on the player's current zone.
- **[Arrow]** Fixed a bug where the Arrow would mistakenly point to previously completed objective locations instead of the Quest Finisher's exact location.
- **[Tracker]** Fixed `QuestieDB.IsComplete` edge case returning incomplete incorrectly; now verifies `numFulfilled == numRequired` to immediately acknowledge completed quests while awaiting the server flag.
@@ -68,89 +107,100 @@
## v9.7.9
### Fixes
- **[Quest]** Fixed tracker objective count staying stale (e.g. stuck at 14/16 when quest is complete) when an autoloot bot bypasses the standard loot frame. Registered `BAG_UPDATE_DELAYED` event to force a full quest log scan on bag changes, catching progress updates that `QUEST_WATCH_UPDATE` would normally fire for manual looting.
## v9.7.8
### Fixes
- **[Quest]** Fixed "There was an error populating objectives" error in chat for `triggerEnd` quests with no map coordinates (e.g., "complete N quests in zone"). `_RegisterObjectiveTooltips` now silently returns for `event`-type objectives with no `spawnList` — these have nothing to register a tooltip for.
## v9.7.7
### Fixes
- **[Quest]** Fixed "Missing event data for Objective" error appearing in chat for all `triggerEnd` quests with no map coordinates (e.g., "complete N quests in zone" types). Nil coordinates are valid for server-tracked objectives with no pin — the handler now returns silently instead of logging a visible error.
## v9.7.6
### Fixes
- **[Database]** Fixed "Missing objective data" error for all "complete N quests in zone" quest types (IDs 50151, 50145, 50098, 50100, 50149, 50099, 50108, 50111, 50150). Added `triggerEnd` (`[9]`) field so Questie correctly registers the server-tracked objective without drawing map pins.
## v9.7.5
### Fixes
- **[Quest]** Fixed objective pins/icons persisting on the map after a quest is completed or abandoned. Added `CleanupRemovedQuestsFallback` which diffs Questie's quest log against the game's actual quest log on every `QUEST_LOG_UPDATE` and correctly calls `CompleteQuest` or `AbandonedQuest` for any quest that silently disappeared, ensuring map icons are removed.
- **[Quest]** Fixed re-accepted repeatable custom quests (e.g., Stormforged Scales) not showing objective icons on the map after being accepted a second time.
### New Quests
- **[Database]** Added **Storm Peak Orders** (ID 50150) - *The Storm Peaks*
- Objective: Complete any 6 quests in The Storm Peaks.
- Objective: Complete any 6 quests in The Storm Peaks.
- **[Database]** Added **Wild Basin** (ID 50094) - *Sholazar Basin*
- Objective: Kill 75 Beasts. Includes 29 Beast NPC types with full spawn coordinates.
- NPCs: King Krush, Shardhorn Rhino, Aotona, Pitch, Serfex the Reaver, Dreadsaber, Hardknuckle Matriarch, Shango, Venomtip, Bushwhacker, Hardknuckle Charger, Ravenous Mangal Crocolisk, Farunn, Zeptek the Destroyer, Goretalon Matriarch, Sapphire Hive Wasp, Emperor Cobra, Sapphire Hive Drone, Shattertusk Bull, Siltslither Eel, Spirit of Atha, Stranded Thresher, Mangal Crocolisk, Spirit of Koosu, Longneck Grazer, Goretalon Roc, Sapphire Hive Queen, Spirit of Ha-Khalan, Bittertide Hydra
- Objective: Kill 75 Beasts. Includes 29 Beast NPC types with full spawn coordinates.
- NPCs: King Krush, Shardhorn Rhino, Aotona, Pitch, Serfex the Reaver, Dreadsaber, Hardknuckle Matriarch, Shango, Venomtip, Bushwhacker, Hardknuckle Charger, Ravenous Mangal Crocolisk, Farunn, Zeptek the Destroyer, Goretalon Matriarch, Sapphire Hive Wasp, Emperor Cobra, Sapphire Hive Drone, Shattertusk Bull, Siltslither Eel, Spirit of Atha, Stranded Thresher, Mangal Crocolisk, Spirit of Koosu, Longneck Grazer, Goretalon Roc, Sapphire Hive Queen, Spirit of Ha-Khalan, Bittertide Hydra
## v9.7.4
### Fixes
- **[Tooltips]** Fixed "attempt to concatenate local 'name' (a nil value)" error when quest starters/finishers have missing names in the database.
- **[Database]** Added missing spawn coordinates for Quest 50031 "Stormbound" elementals in Storm Peaks (zone 67).
- **[Database]** Fixed "Unknown Zone" issue for custom quests by correcting Zone ID index usage (swapped `[6]` RequiredRaces for `[17]` ZoneID).
- **[Database]** Corrected Dragonblight Zone ID in custom quest definitions.
### New Quests
- **[Database]** Added **Morogh Missions** (ID 50098) - *Dun Morogh*
- Objective: Complete any 6 quests in Dun Morogh. Auto-completes upon reaching the objective.
- Objective: Complete any 6 quests in Dun Morogh. Auto-completes upon reaching the objective.
- **[Database]** Added **Azuremyst Aid** (ID 50100) - *Azuremyst Isle*
- Objective: Complete any 6 quests in Azuremyst Isle. Auto-completes upon reaching the objective.
- Objective: Complete any 6 quests in Azuremyst Isle. Auto-completes upon reaching the objective.
- **[Database]** Added **Stormforged Scales** (ID 50066) - *The Storm Peaks*
- Objective: Kill 30 Dragonkin. Includes 8 Dragonkin NPC types with full spawn coordinates.
- Objective: Kill 30 Dragonkin. Includes 8 Dragonkin NPC types with full spawn coordinates.
- **[Database]** Added **Peak Predators** (ID 50095) - *The Storm Peaks*
- Objective: Kill 75 Beasts. Includes 24 Beast NPC types with full spawn coordinates.
- Objective: Kill 75 Beasts. Includes 24 Beast NPC types with full spawn coordinates.
- **[Database]** Added **Peak Predators** (ID 50096) - *Icecrown*
- Objective: Kill 75 Beasts. Includes 12 Beast NPC types with full spawn coordinates.
- Objective: Kill 75 Beasts. Includes 12 Beast NPC types with full spawn coordinates.
- **[Database]** Added **Icecrown Advance** (ID 50151) - *Icecrown*
- Objective: Complete any 6 quests in Icecrown. Auto-completes upon reaching the objective.
- Objective: Complete any 6 quests in Icecrown. Auto-completes upon reaching the objective.
- **[Database]** Added **Storm Peaks Trophy** (ID 50205) - *The Storm Peaks*
- Objective: Kill 1 Rare in The Storm Peaks. Includes 4 Rare NPC types (Skoll, Time-Lost Proto-Drake, Vyragosa, Dirkee) with full spawn coordinates.
- Objective: Kill 1 Rare in The Storm Peaks. Includes 4 Rare NPC types (Skoll, Time-Lost Proto-Drake, Vyragosa, Dirkee) with full spawn coordinates.
## v9.7.3
### New Features
- **[Database]** Implemented **Ebonhold Database Module**.
- Created dedicated `Database/Ebonhold/` structure for custom server data.
- Added `EbonholdLoader` to inject custom Quests, NPCs, Objects, and Items as overrides.
- **Note:** This structure preserves custom data during upstream Questie updates.
- Created dedicated `Database/Ebonhold/` structure for custom server data.
- Added `EbonholdLoader` to inject custom Quests, NPCs, Objects, and Items as overrides.
- **Note:** This structure preserves custom data during upstream Questie updates.
- **[Objectives]** Implemented **Automated Text Retrieval**.
- Questie now attempts to fetch quest text from the server at runtime for custom quests that are missing from the database.
- Added "Objectives Board" (ID 600600) as a global quest starter.
- Questie now attempts to fetch quest text from the server at runtime for custom quests that are missing from the database.
- Added "Objectives Board" (ID 600600) as a global quest starter.
### Quests (Custom Content)
- **[New]** Added **Heart of the Dragonflights** (ID 50064) - *Dragonblight*
- Objective: Kill 30 Dragonkin. Includes 49 Dragonkin NPC types.
- Objective: Kill 30 Dragonkin. Includes 49 Dragonkin NPC types.
- **[New]** Added **Skies of Blade's Edge** (ID 50060) - *Blade's Edge Mountains*
- Objective: Kill 75 Dragonkin. Includes 17 Dragonkin NPC types.
- Objective: Kill 75 Dragonkin. Includes 17 Dragonkin NPC types.
- **[New]** Added **Shadowed Beasts** (ID 50087) - *Shadowmoon Valley*
- Objective: Kill 75 Beasts. Includes 25 Beast NPC types.
- Objective: Kill 75 Beasts. Includes 25 Beast NPC types.
- **[New]** Added **Forest Stalkers** (ID 50083) - *Terokkar Forest*
- Objective: Kill 75 Beasts. Includes 53 Beast NPC types.
- Objective: Kill 75 Beasts. Includes 53 Beast NPC types.
- **[New]** Added **Savage Heights** (ID 50085) - *Blade's Edge Mountains*
- Objective: Kill 75 Beasts. Includes 47 Beast NPC types.
- Objective: Kill 75 Beasts. Includes 47 Beast NPC types.
- **[New]** Added **Unstable Fauna** (ID 50086) - *Netherstorm*
- Objective: Kill 75 Beasts. Includes 18 Beast NPC types.
- Objective: Kill 75 Beasts. Includes 18 Beast NPC types.
- **[New]** Added **Elemental Balance** (ID 50026) - *Nagrand*
- Objective: Kill 30 Elementals. Includes 12 Elemental NPC types.
- Objective: Kill 30 Elementals. Includes 12 Elemental NPC types.
- **[New]** Added **Redridge Trophy** (ID 50160) and **Zangarmarsh Trophy** (ID 50192).
### New Ascension Quests
- **Westfall**: Agria's Medicine, Seven Years of Bad Luck, Worm-Eaten Apple, Goldshire's Generosity, Bookworm, Knowledge Corrupts, The Ruins of Northshire, Accursed Sisterhood, Words That Shepherd Madness, Oracular Idol, A Betrayal Within, The Maid I Left Behind, The Saddest Among Us, The Threat Swept Downstream, Stay a While, Defias Disruption.
- **Dun Morogh**: A Small Mistake, We Found Her!, The Scout's Favor, Old Mirsinth, Smoke on the Wind, A Promising Path, A Fitting Disguise, His Radiant Majesty, Deciphering Radiation, Soaking the Masses, Sever the Right Hand, A Growing Business, Thunderbrew's Hop, Stay a While, Live-Fire Demo, Bots on Strike, A Brother's Betrayal, The True Story, Timber for the Coldhewn, Icehide the Unbroken.
- **Teldrassil**: The Carrion Road, The Sister Who Never Returned, Finding the Good Meat, Transsubstantiating the Flesh, Communion Banquet, A Trail of Petals, Restless entrails, A Dark Warning, The Aid of Theren-Dion, No Place for Scavengers, Termites in Teldrassil, Stay a While, Elydna's Heirloom.
@@ -159,17 +209,19 @@
- **Mulgore**: Death and Tribute, Death and Exile, Death and Dishonor, Death and Justice, Death by Laughter, To Whom I Devote, Fighting Over Carrion, Smoke on the Horizon, Amphora of Sacred Water, Stay a While, Exile of Embers, The Smoke that Remembers, The Circles Rite.
### New Ebonhold Quests
- **Outland**: Elemental Balance, Savage Heights, Unstable Fauna, Forest Stalkers, Marsh Predators, Skies of Blade's Edge, Shadowed Beasts, Zangarmarsh Trophy.
- **Northrend**: Tundra Turbulence, Stormbound, Dragonblight Trophy, Heart of the Dragonflights, Peak Predators, Icecrown Advance, Storm Peaks Trophy, Stormforged Scales, Fjord Front, Wild Basin, Grizzly Trophy, Zul'Drak Trophy, Basin Expeditions.
- **Azeroth**: Redridge Trophy, Morogh Missions, Azuremyst Aid, Shadow of Teldrassil, Trials of Durotar, Song of the Woods, Morogh Trophy, Tirisfal Trophy.
### Fixes
- **[Tracker]** **Combat Update Fix**: Tracker now updates objectives immediately during combat without causing Lua errors or taint.
- **[Tracker]** **Bag Update Fix**: Quest progress now updates immediately when looting items (fixes delay with loot bots).
- **[Arrow]** **Refined Visibility Logic**:
- **Auto Nearby**: Arrow correctly defaults to showing the nearest quest when no quests are tracked.
- **Zone Filter**: In "Auto Mode", the arrow hides if the nearest quest is in a different zone.
- **Instance Filter**: Arrow explicitly hides if the target is in a different instance.
- **Auto Nearby**: Arrow correctly defaults to showing the nearest quest when no quests are tracked.
- **Zone Filter**: In "Auto Mode", the arrow hides if the nearest quest is in a different zone.
- **Instance Filter**: Arrow explicitly hides if the target is in a different instance.
- **[Map]** Fixed an issue where completed quest icons would persist on the map (`RequestMapUpdate` logic).
- **[Database]** Updated `wotlkNpcDB.lua` with scraped spawn data for 12 key beast NPCs in Terokkar Forest to ensure accuracy.
- **[Arrow]** Fixed a nil function error for `_CollectObjective` when processing incomplete quests.
+418 -393
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5 -202
View File
@@ -1,206 +1,9 @@
---@type table
local EbonholdDB = QuestieLoader:CreateModule("EbonholdDB")
-- Generated by Ebonhold
EbonholdDB = EbonholdDB or {}
EbonholdDB.npcData = EbonholdDB.npcData or {
-- Quest 11420/11426/11427/11436: The Path to Payback - Update Guard Captain Zorek coordinates
[23728] = {'Guard Captain Zorek',29820,29820,73,73,1,{[495]={{60.1,62.4}}},nil,495,nil,nil,1892,"A","",3},
-- Quest 50030 Corrections: Full Spawn Data for Borean Tundra Elementals
-- Overrides existing sparse data in WotLK DB
[24601] = {'Steam Rager',9291,9291,70,71,0,{[3537] = {{67,25.4},{67.4,22},{67.4,26.4},{67.4,26.8},{68,26},{68,27},{68.2,23},{68.2,23.6},{68.4,22.4},{68.4,25},{68.4,28.2},{68.8,24.8},{68.8,31.2},{69.2,23.8},{69.2,26.8},{69.2,27.8},{69.4,22.2},{69.4,22.8},{69.4,26.4},{69.4,29},{69.4,30},{69.6,22.4},{69.6,24.4},{69.6,28.2},{69.8,22.8},{69.8,32},{70,29.4},{70.2,36.6},{70.4,25.4},{70.4,25.6},{70.4,26.6},{70.4,29.8},{70.4,30.8},{70.4,36},{70.6,29.4},{70.8,36.4},{71,22.2},{71,22.6},{71,24.4},{71,25.8},{71,28.2},{71,36.6},{71.2,38.4},{71.4,25.4},{71.4,27.2},{71.4,30.4},{71.4,31.2},{71.4,32.4},{71.4,33.2},{71.4,34},{71.4,35},{71.4,39.4},{71.4,39.6},{71.6,22},{71.6,29.6},{71.6,31},{71.6,39.4},{71.8,25.8},{71.8,27.2},{71.8,33.6},{71.8,42.2},{72,24.6},{72,28.6},{72,32.6},{72,37.6},{72.2,24.2},{72.2,28.4},{72.2,32.4},{72.2,35.2},{72.2,35.8},{72.2,36.6},{72.4,21},{72.4,23.4},{72.4,40.4},{72.4,41.2},{72.6,24.4},{72.6,27.2},{72.6,30.2},{72.6,31.4},{72.6,32.4},{72.6,33.4},{72.6,33.6},{72.6,35.4},{72.6,36.4},{72.6,36.6},{72.8,25.4},{72.8,41.6},{73,23.4},{73,26.2},{73,28.2},{73.2,38.2},{73.2,39.4},{73.2,40.2},{73.2,40.6},{73.4,28.6},{73.6,24.4},{73.6,26},{73.6,27.2},{73.6,40.4},{73.6,40.6},{73.8,25.2},{73.8,28},{73.8,29.2},{73.8,38.6},{74.2,30.2},{74.2,30.6},{74.2,37.4},{74.2,37.8},{74.4,31.6},{74.4,33.4},{74.4,34.2},{74.4,35},{74.4,35.8},{74.6,29.4},{74.6,30},{74.6,31.8},{74.6,36.8},{74.6,38.4},{74.6,39.4},{74.6,39.8},{74.8,30.8},{74.8,33.6},{74.8,34.6},{74.8,35.6},{75.4,33.4},{75.6,29},{75.6,33.4},{75.6,33.8},{75.6,34.6},{75.6,35.8},{75.8,38.2},{76.4,32.4},{76.4,37.4},{76.6,32},{76.8,36},{77,37.8},{77.4,37},{77.6,37}}},nil,3537,nil,nil,91,nil,"",0},
[25226] = {'Scalder',9291,9291,71,71,0,{[3537] = {{42.4,15.4},{42.4,15.6},{42.6,15.4},{42.6,16.4},{42.6,16.6},{42.8,17.6}}},nil,3537,nil,nil,14,nil,"",0},
[25376] = {'Imperean',9291,9291,72,72,0,{[3537] = {{46.4,9.4},{46.6,9.4}}},nil,3537,nil,nil,190,nil,"",0},
[25415] = {'Enraged Tempest',9291,9291,70,71,0,{[3537] = {{42,8},{42.2,9.4},{42.2,13.4},{42.4,10.2},{42.4,10.6},{42.6,10.4},{42.8,9},{42.8,10.6},{43,7.4},{43.2,8},{43.4,13.8},{43.6,7.8},{43.6,9},{43.6,13.4},{43.8,7.4},{43.8,10.6},{44,13.6},{44.2,11.6},{44.4,10},{44.6,7.4},{44.6,7.6},{44.6,11.6},{44.8,10.8},{45,9.2},{45.4,6.4},{45.4,9.8},{45.6,6.4},{45.6,9.8},{45.8,9.4},{45.8,12.4},{46,7.4},{46,7.6},{46,10.6},{46,13.2},{46.6,7.8},{46.6,9.2},{46.6,10.4},{46.6,11.2},{47.2,11.6},{48.2,7.4}}},nil,3537,nil,nil,834,nil,"",0},
[25416] = {'Simmer',9291,9291,72,72,0,{[3537] = {{50.4,15.2},{50.8,15.2}}},nil,3537,nil,nil,1983,nil,"",0},
[25417] = {'Raging Boiler',9291,9291,69,70,0,{[3537] = {{46,14.6},{46.2,13.2},{46.2,13.8},{46.8,14.4},{47,9.8},{47,12.4},{47,12.8},{47.2,14.6},{47.2,16.8},{47.4,11.2},{47.4,15.6},{47.8,12.4},{47.8,15.4},{48,11.4},{48,15.8},{48,21.2},{48.2,12.8},{48.2,16.6},{48.2,19.8},{48.4,14.2},{48.4,18.2},{48.4,19.4},{48.6,12.4},{48.6,16.2},{48.6,19.4},{48.8,13.4},{48.8,13.6},{49,16.8},{49.2,17.6},{49.4,10.2},{49.4,10.6},{49.4,15},{49.6,10.8},{49.6,13.4},{49.6,14.4},{49.6,17},{50.2,15},{50.4,15.6},{50.4,17.8},{50.6,15.6},{50.8,15.2}}},nil,3537,nil,nil,1983,nil,"",0},
[25418] = {'Churn',9291,9291,71,71,0,{[3537] = {{45.4,12.4},{45.8,12.2},{45.8,12.8},{45.8,13.6}}},nil,3537,nil,nil,1984,nil,"",0},
[25419] = {'Boiling Spirit',9291,9291,69,70,0,{[3537] = {{43.4,13.8},{44.4,9},{44.6,9.8},{44.6,15.6},{45.2,12.4},{45.2,12.6},{45.2,16.8},{45.4,11.4},{45.4,15.4},{45.8,9.8},{45.8,12.2},{45.8,13},{45.8,13.6},{46,14.8},{46,16.8},{46,19},{46.2,11.4},{46.2,15.6},{46.4,9.4},{46.6,9.6},{46.8,9.2},{46.8,15},{46.8,16.8},{47,11.2},{47,12.6},{47.2,12.4},{47.2,16},{47.2,18.4},{47.4,14.2},{47.6,11.4},{47.6,12.4},{47.6,15.4},{47.8,16.8},{47.8,17.8},{48,15.8},{48.2,13.4},{48.2,14},{48.6,16},{48.8,12.2},{48.8,13.4},{49,16.8},{49.2,14.8},{49.4,10.6},{49.4,13.8},{50,10.8},{50.2,15.2},{51,15.2}}},nil,3537,nil,nil,1984,nil,"",0},
[25514] = {'Rocknar',9291,9291,71,71,0,{[3537] = {{81.4,42.4},{81.4,42.6},{81.6,42.6}}},nil,3537,nil,nil,14,nil,"",0},
[25707] = {'Magic-bound Ancient',9291,9291,71,72,0,{[3537] = {{22.4,23.4},{22.4,24},{22.8,24},{23,25.4},{23.4,30.2},{23.4,33.6},{23.6,27.8},{23.8,25.8},{23.8,34.2},{24,29.4},{24,29.8},{24.2,21.4},{24.4,22.4},{24.4,22.6},{24.4,23.6},{24.4,30.8},{24.4,31.6},{24.4,35},{24.6,22.4},{24.6,23},{24.6,23.6},{24.6,28.8},{24.6,30.4},{25,31.4},{25.2,20.4},{25.2,25},{25.2,31.6},{25.4,28.4},{25.4,34.4},{25.4,34.8},{25.4,35.6},{25.4,36.6},{25.6,34.6},{25.6,37},{25.8,21.4},{25.8,34.2},{25.8,35.6},{26,21.8},{26,23.4},{26,29.4},{26,31.4},{26,31.8},{26.4,32.6},{26.6,35},{27,20.4},{27,32.6},{27,33.8},{27.2,31},{27.4,20.6},{27.4,21.8},{27.4,30.4},{27.4,32},{27.6,33.6},{27.8,32.2},{27.8,32.6},{28.2,28.8},{28.2,29.8},{28.2,31.4},{28.2,35.8},{28.4,20.4},{28.4,21.4},{28.4,21.8},{28.4,28},{28.6,32.6},{28.6,34.2},{28.8,21.4},{28.8,21.6},{28.8,28.4},{28.8,32.4},{29,20.4},{29.2,27.4},{29.4,29},{29.4,30},{29.4,30.6},{29.6,20.2},{29.6,20.6},{29.6,22.8},{29.6,30.4},{29.8,22},{29.8,24.4},{29.8,25.8},{29.8,30.8},{29.8,31.8},{30,29.4},{30,33.2},{30.4,24.6},{30.6,29.2},{30.6,29.8},{30.8,31.2},{31,22.4},{31,26.6},{31.2,23.4},{31.2,25.6},{31.4,20.2},{31.4,21.2},{31.4,24.4},{31.4,24.8},{31.4,27.8},{31.6,20.4},{31.6,22.4},{31.6,24.2},{31.6,24.8},{31.6,25.6},{31.6,26.6},{31.6,30.8},{31.8,20.6},{31.8,23.4},{32,30},{32.4,27.6},{32.4,28.6},{32.4,33.8},{32.6,27.8},{32.6,30.6},{32.8,23.4},{33.2,24.6},{33.2,34.4},{33.4,24.2},{33.4,25.6},{33.4,29.4},{33.6,23.4},{33.6,24.4},{33.6,24.6},{33.8,25.6},{33.8,26.8},{34.4,28.4},{34.4,29.2},{34.4,29.8},{34.6,25.6},{34.8,29},{35,27},{35,29.6},{35.2,28.4},{35.4,30.6},{35.6,28},{35.6,29},{35.6,29.6},{35.6,30.6}}},nil,3537,nil,nil,16,nil,"",0},
[25709] = {'Glacial Ancient',9291,9291,71,72,0,{[3537] = {{20.4,27},{20.4,28.2},{20.8,28.2},{21,25.8},{21,29.2},{21.2,29.6},{21.4,27},{21.4,31.4},{21.4,31.6},{21.4,32.6},{21.6,29.2},{21.6,30.4},{21.6,31},{21.6,32},{21.8,27.6},{22.2,33.2},{22.4,26.4},{22.4,27.2},{22.4,33.6},{22.4,34.6},{22.6,33.4},{22.8,27.2},{22.8,28.8},{23,32.4},{23,33.6},{23.2,27.8},{23.2,34.6},{23.4,25.2},{23.4,26},{23.4,35.6},{23.4,36.6},{23.6,25},{23.6,28.6},{23.6,33.4},{23.6,34.8},{23.6,35.8},{23.8,26.4},{23.8,27.8},{23.8,32.4},{23.8,36.6},{24,26.6},{24.2,34.2},{24.6,26},{24.6,27.8},{24.6,36.8},{24.8,27.2},{25,34.2},{25.2,19.4},{25.4,33.4},{25.4,34.6},{25.4,35.6},{25.6,33.4},{25.6,35},{25.6,35.6},{25.6,36.6},{25.8,34.2},{26.4,17.4},{26.4,18.2},{26.4,19.2},{26.4,20},{26.4,20.6},{26.6,18.4},{26.6,18.6},{26.6,19.6},{26.6,20.6},{27.2,33.8},{27.8,18},{29.4,30.2},{29.4,32.2},{29.4,32.8},{30.4,23.4},{30.4,23.8},{30.4,24.8},{30.4,29},{30.4,30.4},{30.4,31.4},{30.4,32.4},{30.4,32.6},{30.6,29.2},{30.6,30.4},{30.6,30.6},{30.6,32.2},{30.6,32.6},{31,28.4},{31.2,25.6},{31.4,22},{31.4,22.8},{31.4,23.6},{31.4,24.8},{31.6,22.4},{31.6,23.6},{31.6,24.6},{31.6,25.8},{31.6,31},{31.8,23.2},{32.6,23},{32.6,23.8},{32.6,26.4},{34.4,25.4},{34.4,26.4},{34.4,26.8},{34.4,28.2},{34.4,28.6},{34.8,26.4},{34.8,26.6},{35,28.6},{35.2,27.8},{35.4,25.4},{35.6,26.4},{35.6,27.2},{35.6,27.6},{35.6,28.6}}},nil,3537,nil,nil,7,nil,"",0},
[25715] = {'Frozen Elemental',9291,9291,70,71,0,{[65]={{11.28,62.69},{11.05,61.25},{9.43,61.9},{10.2,60.22},{10.21,62.04},{9.29,60.28},{8.68,62.04},{9.07,59.02},{7.78,62.99},{8.79,54.22},{9.63,56.48},{11.56,54.92},{11.53,57.32},{11.05,56.14},{8.94,55.86},{10.33,55.94},{10.23,57.3},{7.8,61.25},{13.09,60.42},{8.24,60.06},{12.21,58.61},{11.52,59.08},{11.71,53.34},{7.88,58.82},{8.55,58.35},{6.81,62.17},{11.85,60.86}},[3537] = {{82.8,48.8},{83.2,45.6},{83.4,44.4},{83.4,45},{83.4,47.2},{83.4,47.6},{83.6,44.4},{83.6,47.2},{83.8,44.8},{84.2,46.4},{84.2,47.6},{84.6,47.2},{85,46.4},{85,48.8},{85.2,48},{85.4,43.8},{85.4,44.8},{85.6,43.4},{85.8,44.2},{85.8,47.4},{85.8,47.6},{86,45.8},{86.2,45.4},{86.8,47.6},{87.2,43.4},{87.2,43.6},{87.2,45},{87.4,42.4},{87.4,46.4},{87.4,46.6},{87.6,45.4},{87.6,45.8},{87.8,42.8},{88.4,42.4},{88.4,44.2},{88.4,46.8},{88.6,44.2},{88.6,45.8},{88.6,46.8},{88.8,41.4},{88.8,42.2},{88.8,45.4},{89,42.6},{89.8,42.6},{90.2,40.8},{90.2,41.8},{91,43},{91.2,40.4},{91.2,41.6},{91.4,41.4},{91.6,41.2},{91.6,41.6},{92,42.6},{92.4,37.8},{92.4,39.4},{92.4,39.6},{92.6,39.2},{92.6,39.6},{92.8,43.6},{93,36.4},{93,37.4},{93,38},{93.4,35.4}}},nil,3537,nil,nil,14,nil,"",0},
[26045] = {'Storm Tempest',9291,9291,71,71,0,{[3537] = {{77,38.6}}},nil,3537,nil,nil,834,nil,"",0},
-- Quest 50162 Wetlands Trophy: Rare NPCs in Wetlands (zone 11)
[1037] = {'Dragonmaw Battlemaster',684,734,24,25,4,{[11] = {{42.2,40.8},{42.2,44.6},{42.4,42.8},{42.4,44.4},{42.8,42.6},{42.8,43.8},{43,40.6},{43,42.4},{44.2,43.2},{44.2,44},{44.4,44.8},{44.6,42.8},{44.6,43.8},{45.4,44.6},{45.8,43.8},{46.4,45},{47,43},{47,44.2},{47,46},{47.4,45.4},{47.4,46.6},{47.6,46},{48,44.2},{48.2,45.2},{48.4,46.6},{48.4,47.6},{48.4,48.8},{48.6,47.4},{48.8,45.8},{48.8,48.4},{49,45.2},{49,49.4},{49.2,42},{49.2,49.6},{50,48.2},{50,49.4},{50.2,52.2},{50.4,50},{50.4,51.2},{50.8,49.4},{51,51},{51.2,51.8},{51.4,50},{51.4,52.6},{51.6,48.8},{51.6,52.4},{51.6,52.6},{52.4,53.6},{52.6,49.4},{52.8,53.2},{53,53.6}}},nil,11,nil,nil,62,nil,"",0},
[1140] = {'Razormaw Matriarch',1106,1106,31,31,4,{[11] = {{67.2,29.2},{67.4,28.4},{67.4,30.2},{67.4,30.8},{67.4,31.6},{67.6,30.6},{67.6,32},{67.8,27.8},{69,28.4},{69.2,28.8},{69.6,29.8},{69.8,29},{70,31.8},{70.2,31.4},{70.6,30.8},{70.8,30.4}}},nil,11,nil,nil,48,nil,"",0},
[2108] = {'Garneg Charskull',812,812,29,29,4,{[11] = {{30.6,39.4},{31.4,41.2},{37,44},{37,46.4},{37,47},{37,47.8},{38.2,45.4},{38.4,46},{38.6,46.2},{39.2,46.6},{39.2,47.6},{39.2,49},{39.4,37.2},{39.8,46.6},{40,46.4},{40.6,37.4},{40.8,46.8},{41.2,46},{41.4,45.4},{41.6,45.4},{41.6,45.6},{41.8,42.6},{42.4,44.2},{42.6,40.2},{43.2,43},{43.2,44.2},{44,42.6},{44.4,44.2},{44.8,45.4},{45.4,44.4},{46,43.8},{46.2,44.8},{46.8,43.4},{46.8,44.2},{47,48.6},{47.2,46.2},{47.6,49},{48,45.2},{48,46.4},{48,47.6}}},nil,11,nil,nil,62,nil,"",0},
[14425] = {'Gnawbone',664,664,24,24,4,{[11] = {{30,30.6},{30.2,29},{30.2,30},{30.4,33},{30.6,30},{30.6,31.2},{32,34.2},{32.2,31.4},{32.4,32.8},{32.8,29},{32.8,29.8},{33,32.6},{33.4,31.2},{33.4,34},{33.6,29.8},{33.8,31.4},{34.2,34.2},{34.4,36},{34.8,30.2},{35,35},{35.2,34.4},{35.4,31.6},{35.4,37.2},{35.6,35},{35.8,29.6},{36,37.4},{36.8,29.4},{36.8,34.4},{37,35.2},{37.6,29.2},{37.8,34.6},{38,33.4},{38.4,29.8},{38.4,36.4},{38.8,29.6},{39,29},{39,30.6}}},nil,11,nil,nil,61,nil,"",0},
[14433] = {'Sludginn',974,974,30,30,4,{[11] = {{10.8,69.4},{11.2,71.4},{11.4,69.6},{11.8,69.4},{11.8,69.6},{12,70.6},{13.2,68},{13.6,68.4},{13.8,68.6},{14.2,66.4},{14.2,66.6},{14.6,67.2}}},nil,11,nil,nil,14,nil,"",0},
[2090] = {'Ma\'ruk Wyrmscale',617,617,23,23,4,{[11] = {{47.2,74},{47.4,74.6},{47.4,75.8},{47.8,74.6},{47.8,75.6},{47.8,77},{48,74.2},{48.2,73.4},{48.4,78.6},{48.6,75},{48.8,78.8},{49.4,74.2},{49.4,76},{49.8,73.6},{50,72.4}}},nil,11,nil,nil,62,nil,"Dragonmaw Warlord",0},
[1112] = {'Leech Widow',664,664,24,24,4,{[11] = {{45.6,63.4},{45.6,63.8},{46,62.2},{46.2,64.8},{46.6,64},{46.6,65.4},{46.6,65.6},{46.8,61.4},{46.8,62.8},{47,61.6}}},nil,11,nil,nil,22,nil,"",0},
[14424] = {'Mirelow',734,734,25,25,4,{[11] = {{17.2,28.6},{17.4,26.8},{17.4,28},{18,26.2},{18,26.8},{18.2,27.6},{18.4,29.2},{18.4,30.6},{18.6,27.4},{18.6,29.4},{18.8,31},{19,27.6},{19,30.4},{19.6,31.6},{19.6,32.8},{20,30.6},{20.2,27.8},{20.2,28.8},{20.4,27.4},{20.4,30},{20.6,30.6},{20.8,28.4},{20.8,32},{20.8,32.8},{21,29.2},{21,33.6},{21.2,29.8},{21.2,35.6},{21.4,35.2},{21.6,30.8},{21.8,29.2},{22,35.4},{22.2,21.4},{22.4,21.6},{22.4,22.6},{22.4,23.8},{22.4,29.6},{22.4,35.8},{22.4,36.8},{22.6,28.2},{22.8,22.4},{22.8,22.6},{22.8,24.2},{22.8,24.6},{22.8,31.4},{23,21.4},{23.2,34.4},{23.4,29},{23.4,30.2},{23.4,35.8},{23.4,37},{23.6,25.2},{23.6,34.8},{23.8,30.8},{23.8,36.2},{24,24.4},{24,29.6},{24.2,26.2},{24.2,27.6},{24.2,36.6},{24.4,26.6},{24.6,25.2},{24.6,25.6},{24.6,37},{24.8,35.6},{25,38},{25.2,27.2},{25.2,30.4},{25.2,30.6},{25.2,32.2},{25.4,28},{25.4,38.6},{25.6,27.8},{25.8,32},{26,29.2},{26,33.8},{26.4,31.2},{26.4,33.4},{26.6,29.4},{26.6,30},{26.6,33.6},{27,30.8},{27,33.2},{27.4,31.8},{27.6,32},{27.6,32.8},{28.2,31.4}}},nil,11,nil,nil,14,nil,"",0},
-- Quest 50199 Borean Trophy: Rare NPCs in Borean Tundra (zone 3537)
[32358] = {'Fumblub Gearwind',13936,13936,71,71,2,{[3537] = {{59.2,21.8},{59.6,19.4},{59.8,26.2},{60,17},{60.4,23.6},{60.4,28.4},{60.6,25},{60.8,15.2},{61,21.6},{61.2,22.8},{61.2,24.2},{61.4,30},{61.6,22.6},{61.8,20},{61.8,21.4},{62.4,19.2},{62.8,29},{62.8,34.6},{63,19.4},{63,32.4},{63.2,25.4},{63.2,25.6},{63.4,16.4},{63.4,17},{63.8,17.6},{64.2,16.8},{64.4,16},{64.4,32.4},{64.4,33},{64.6,35},{65,17.2},{65.2,17.8},{65.4,19.2},{65.4,33.6},{65.8,20},{66,17},{66,36.4},{66.2,18},{66.2,18.6},{66.4,20.6},{66.6,36.8},{67,20.8},{67.2,19.8},{67.2,35},{67.8,23.4},{67.8,26.4},{67.8,36.8},{68,25.4},{68.2,28.6},{68.4,19.2},{68.4,27},{69,29.4},{69.2,38},{69.4,37},{69.6,32},{69.6,37.2},{70.2,38},{70.4,34.6},{71,34},{71,36.2},{71.6,28.4},{71.8,34.4},{71.8,36.4},{73,34.2},{73.2,35.2},{74,30.2}}},nil,3537,nil,nil,16,nil,"",0},
[32361] = {'Icehorn',13936,13936,71,71,2,{[3537] = {{80.2,45.8},{80.6,31.8},{80.6,45.8},{81.4,31.4},{81.6,30.8},{84.4,46.8},{84.6,45.8},{84.6,46.6},{85.2,34.2},{85.6,34.4},{85.6,35},{88.2,39.2},{88.2,39.6},{88.6,40.2},{88.6,40.6},{89.8,43.4},{91.2,32.2},{91.2,33.8},{91.4,32.6},{91.6,32.4},{91.8,32.6},{92,31.4}}},nil,3537,nil,nil,14,nil,"",0},
[32357] = {'Old Crystalbark',9291,9291,71,72,0,{[3537] = {{20.6,27.8},{20.8,27},{21.2,28.6},{21.2,29.8},{21.6,30.8},{21.8,27},{21.8,34.6},{22,33.8},{22.2,35.8},{22.4,33.4},{22.6,33},{23,33.8},{23.6,35.8},{24.4,34.4},{24.4,34.6},{25,34.2},{25,34.8},{25.6,34.6},{27,35.6},{29.6,33.2},{30,32},{30.8,30.6},{31.2,30.2},{31.6,30.2},{32,31.4},{33,31.8},{33.2,24.4},{33.2,34},{33.4,31},{33.6,31.2},{34.2,23.6},{34.4,25.4},{34.6,27.4},{35.2,28.4},{35.4,30},{35.6,30.8},{35.6,31.6},{36,28.2},{36.2,29.2},{36.2,30.2},{37.2,29}}},nil,3537,nil,nil,14,nil,"",0},
-- Quest 50154 Teldrassil Trophy: Rare NPCs in Teldrassil (zone 141)
[14432] = {'Threggil',115,115,6,6,4,{[141] = {{51.2,50.4},{51.2,50.6},{51.6,50.4},{52,50.6},{52,52},{52.6,52.4}}},nil,141,nil,nil,90,nil,"",0},
[3535] = {'Blackmoss the Fetid',273,273,13,13,4,{[141] = {{39.8,38.2},{40.8,26.6},{40.8,33},{40.8,39.6},{41,26.4},{41.4,28.4},{41.4,38.6},{42,37.2},{42,38.6},{42.2,25.6},{42.2,28},{42.2,38},{42.2,40},{42.4,25.2},{42.4,29},{42.4,32},{42.6,25.4},{42.6,26.4},{42.6,37.6},{42.8,28.4},{42.8,28.6},{42.8,37.2},{42.8,38.6},{43,27},{43,31},{43,39.6},{43.2,36.4},{43.4,30},{43.4,32.2},{43.4,32.6},{43.6,27.4},{43.6,30.6},{43.6,32},{43.6,36.6},{43.6,39.4},{43.8,28.8},{43.8,29.8},{43.8,33.2},{44,28.2},{44,39.6},{44.2,26.4},{44.6,27.4},{44.6,27.6},{44.6,29.2},{44.8,30.2},{45,31.2},{45.6,29.2},{45.6,29.6},{45.6,32.8}}},nil,141,nil,nil,14,nil,"",0},
[14430] = {'Duskstalker',176,176,9,9,4,{[141] = {{50.2,76.6},{51.2,77},{51.2,77.8},{51.6,76.2},{51.8,77.4},{52.4,77.6},{52.6,77.6},{53.4,76.2},{53.4,77.2},{53.8,76.8},{54,77.6},{54.2,75.4},{54.4,76},{54.6,76},{54.6,77},{57.4,76},{57.4,76.6},{58,75},{58,76.4},{58.4,76.8},{58.6,76.2},{58.8,76.6},{60,75.6},{60.2,73.8},{60.2,75.4},{60.6,75.4},{60.6,75.8},{61,74.4}}},nil,141,nil,nil,66,nil,"",0},
[14428] = {'Uruson',137,137,7,7,4,{[141] = {{62.6,56.2},{62.8,57},{63.4,57.6},{63.8,57.2},{64.4,55.8},{64.6,62.2},{65.2,60.4},{65.4,57.4},{65.4,58},{65.4,61.2},{65.6,53.8},{66.2,58.6},{66.2,60.6},{66.4,56.4},{66.4,57.2},{66.4,58.2},{66.4,59.8},{66.6,54.8},{66.8,56},{66.8,58.4},{66.8,58.6},{67,56.8},{67.2,54},{67.2,61.2},{67.4,59.6},{67.4,61.8},{67.6,56.4},{67.6,58.4},{67.6,59.4},{67.6,59.6},{67.8,57},{67.8,61},{68,62.2},{68.4,53.6},{69.2,61.4},{69.6,62},{70.2,57.8}}},nil,141,nil,nil,82,nil,"",0},
[14431] = {'Fury Shelda',156,156,8,8,4,{[141] = {{34.4,34.4},{34.4,35.2},{34.8,32.4},{34.8,33},{34.8,34.4},{35.2,35},{35.4,36.2},{35.4,36.6},{35.4,37.6},{35.4,38.6},{35.6,35.4},{35.6,35.6},{35.6,37.2},{36,38},{36.4,33.8},{36.4,38.8},{36.4,39.6},{36.4,42.4},{36.6,35.4},{36.6,38.2},{36.6,39.4},{36.8,36.4},{37,40.8},{37.2,40.2},{37.2,41.6},{37.4,36.6},{37.4,42.6},{37.4,43.6},{37.6,40.4},{37.6,40.6},{37.6,42.4},{37.8,37},{37.8,42.6},{38,39},{38,44},{38.2,34.8},{39,41.4},{39.8,44.2}}},nil,141,nil,nil,514,nil,"",0},
[14429] = {'Grimmaw',222,222,11,11,4,{[141] = {{39.2,79.4},{39.4,79.6},{39.4,80.8},{40.2,80.6},{40.4,79.4},{40.4,79.6},{40.6,79.6},{41,78.4},{41,78.8},{41.4,74.6},{41.4,77.4},{41.6,76},{41.6,78.4},{41.6,78.6},{41.8,77},{41.8,79.6},{42.6,75.8},{42.6,77.6},{42.6,79.6},{43,75.4},{43,79.2},{43.2,74.4},{43.6,76.4},{43.8,77.8},{43.8,79.6},{47.8,73.8}}},nil,141,nil,nil,82,nil,"",0},
-- Quest 50156 Westfall Trophy: Rare NPCs in Westfall (zone 40)
[573] = {'Foe Reaper 4000',484,484,20,20,4,{[40] = {{33.8,52},{34.6,51.6},{35.2,50.4},{35.4,49.4},{36.2,50},{36.2,50.6},{36.4,52.4},{36.6,53.6},{37,48.2},{37,49.2},{37.2,47.4},{37.2,52},{37.2,52.6},{37.4,50.2},{37.4,51.4},{37.6,51.4},{37.6,53.2},{37.8,47.8},{38,46},{38,49},{38,51.8},{38.2,54.2},{38.4,49.8},{38.6,45.8},{38.6,50},{38.6,51},{38.8,51.6},{39,49.4},{39.2,47},{39.6,52},{40,50.4},{40.2,46.6},{42,34.2},{42.4,36.4},{42.4,37},{42.4,37.8},{42.4,39.4},{42.6,34.6},{43.2,32.8},{43.2,54.2},{43.4,35.8},{43.4,36.8},{43.4,38.6},{43.6,32.6},{44,32},{44,34.2},{44,35.6},{44,40.4},{44.2,38.2},{44.2,39.4},{44.4,35.2},{44.4,37},{44.6,32.8},{44.6,38.2},{44.6,39},{44.6,40.6},{44.8,31.4},{44.8,34.4},{44.8,35.6},{44.8,37},{45,31.6},{45.2,35.2},{45.8,31.8},{46,32.6},{46,35.2},{46.2,36},{46.8,33.6},{46.8,49.2},{47,19.2},{47.4,22},{48.4,23.2},{48.4,23.8},{48.6,22.8},{48.8,24},{49,64.2},{49.2,25.4},{49.4,22.2},{49.4,25.6},{49.6,23.4},{49.6,64.4},{50,22.2},{50,23.8},{50,25},{50.2,25.8},{50.2,26.8},{50.2,65},{50.4,21.4},{50.4,66.8},{50.6,27.2},{50.8,21.2},{50.8,21.8},{50.8,22.8},{50.8,24},{50.8,26},{50.8,67.2},{50.8,69},{51,27.6},{51,66.2},{51.4,24.6},{51.4,67.6},{51.6,20.4},{51.6,23.2},{51.6,23.8},{51.6,67.4},{51.6,67.8},{51.8,21.2},{51.8,25.4},{51.8,69},{52,21.6},{52,26.2},{52,69.8},{52.2,64.6},{52.6,26.2},{52.6,66.8},{52.6,67.8},{52.8,64.6},{53,64.4},{53.2,21.6},{53.2,68.6},{53.4,23},{54.4,19},{54.6,67.8},{57.8,32.8},{61.4,64.2},{61.8,60.2},{62.2,63.8},{62.2,65.2},{62.4,61.4},{62.4,61.6},{62.4,62.6},{62.6,62},{62.8,60.4},{62.8,61.4},{62.8,62.6},{63,59.4},{63.6,59.4},{63.6,60},{63.6,60.6},{63.6,63},{63.8,62.2}}},nil,40,nil,nil,14,nil,"",0},
[599] = {'Marisa du\'Paige',531,531,18,18,4,{[40] = {{39.8,78.2}}},nil,40,nil,nil,17,nil,"",0},
[462] = {'Vultros',788,788,26,26,4,{[40] = {{34,68.2},{35,67},{35.2,70},{35.4,67.8},{35.4,68.8},{35.6,67.4},{35.6,67.8},{35.6,68.6},{36.2,71},{36.6,66.4},{36.6,67.8},{43.2,59.2},{43.4,60.4},{44,60.2},{44.2,59},{44.2,61.6},{44.6,59},{44.8,59.6},{45.4,58.2},{45.6,57.4},{45.6,58.6},{45.6,60.2},{45.8,58},{46,56.4},{46.4,45},{46.8,44.4},{47,44.6},{47.2,42.6},{47.4,40.8},{47.4,41.8},{47.4,45.6},{47.6,44.4},{47.8,41.4},{47.8,45.6},{48,42.4},{48,43.4},{48.4,44.6},{48.6,45},{48.8,42.4},{49,43.8},{49.2,43.4},{49.6,23.4},{50,25},{50.2,24},{50.2,28.2},{50.4,47.8},{50.6,27.8},{51,20.6},{51,22.2},{51,24},{51.2,22.8},{51.4,24.6},{51.4,25.6},{51.6,23.2},{51.6,23.6},{51.8,26},{52,20.8},{52,22},{52.2,20.4},{52.2,25.2},{52.6,23.2},{52.6,24.8},{52.8,24.4},{52.8,26.4},{52.8,26.6},{54,21.4},{58.2,34.4},{62.6,73},{63,74.4},{63,74.8},{63.2,54.8},{63.8,58},{64,73.8},{64.4,55.2},{64.4,56},{64.4,57},{64.6,57.6},{64.8,56.2},{65,56.6},{65.6,73.8}}},nil,40,nil,nil,73,nil,"",0},
[520] = {'Brack',449,449,19,19,4,{[40] = {{25.4,64.6},{25.6,64.4},{26,53},{26,56},{26,58.8},{26,61},{26,63},{26.2,61.8},{26.4,47.4},{26.4,48},{26.4,48.8},{26.4,51},{26.4,52},{26.4,57.2},{26.4,58.4},{26.4,60},{26.4,65.2},{26.4,65.6},{26.4,66.8},{26.4,69.4},{26.6,41},{26.6,47.8},{26.6,51.6},{26.6,60},{26.6,61.6},{26.6,66},{26.8,49.2},{26.8,50},{26.8,54},{26.8,58.6},{27,41.6},{27,43.2},{27,44.8},{27,46.2},{27,46.6},{27,52.6},{27,60.8},{27,67.4},{27.2,38.8},{27.2,44},{27.2,56},{27.2,58},{27.2,62.8},{27.2,68},{27.2,68.8},{27.2,69.8},{27.4,38},{27.4,50.8},{27.4,55.2},{27.4,57},{27.4,70.6},{27.6,44.8},{27.6,46.2},{27.6,48.4},{27.6,49.4},{27.6,51.2},{27.6,54.2},{27.6,66.6},{27.6,71.6},{27.8,39.6},{27.8,40.8},{27.8,43.4},{27.8,44.4},{27.8,54.8},{28,49.6},{28,69},{28,71.4},{28,73.2},{28.2,33.6},{28.2,37.4},{28.2,42},{28.2,56},{28.2,56.8},{28.2,74.2},{28.4,36},{28.4,37.8},{28.4,38.8},{28.4,70.4},{28.4,74.8},{28.4,76.6},{28.6,34.6},{28.6,37.2},{28.6,38.6},{28.6,40},{28.6,71.6},{28.8,33.4},{28.8,37.6},{28.8,45.6},{28.8,71},{29,32.2},{29,34.4},{29,41.2},{29,72.8},{29,74.2},{29.2,30.4},{29.2,76.4},{29.2,76.6},{29.2,77.8},{29.4,46.6},{29.4,75},{29.4,79.4},{29.6,31.4},{29.6,35.2},{29.6,39.8},{29.6,76.4},{29.8,30.2},{29.8,31.6},{29.8,34},{29.8,71.4},{29.8,78},{29.8,78.6},{30,75.2},{30.2,29},{30.2,77.2},{30.2,80},{30.2,81.6},{30.4,80.6},{30.4,82.8},{30.6,81},{30.8,27.8},{30.8,29.4},{30.8,75.2},{30.8,79.4},{30.8,81.8},{31,25.2},{31,26.2},{31.2,30.4},{31.2,31.2},{31.4,27.4},{31.4,83},{31.4,83.6},{31.6,27.4},{31.6,28.2},{31.6,81.4},{31.6,82.4},{32.2,23.6},{32.2,26.4},{32.2,84},{32.4,22.6},{32.4,25},{32.4,83},{32.6,24.6},{33,23.8},{33.2,82.4},{33.2,83.4},{33.2,83.6},{33.4,19.4},{33.4,21.8},{33.4,22.6},{33.6,22.4},{33.6,23.2},{33.6,23.8},{33.8,81.8},{34,20.2},{34,83.4},{34,84},{34,84.8},{34.2,20.8},{34.6,20.4},{34.6,84},{34.6,84.6},{34.8,20.6},{34.8,85.8},{35.2,83.2},{35.6,20},{35.6,21.6},{36.4,19},{36.8,16.4},{36.8,18},{36.8,19},{36.8,19.8},{37.6,17.6},{38,17.2},{38.4,16},{38.6,15.4},{38.8,17.2},{39,13.4},{39,18},{39.2,14.2},{39.2,15.8},{39.6,12.8},{39.6,13.6},{39.6,15.8},{40.2,17},{40.4,10.4},{40.4,11.4},{40.4,12.4},{40.4,18.4},{40.6,12.6},{41,11.2},{41,11.8},{41.6,11.6},{42,9.6},{42,11.2},{42.8,10.4},{42.8,11.6},{43.4,11.4},{43.6,9.4},{43.8,10.2},{44.2,10.6},{44.2,12.4},{45,8.4},{45.2,10},{45.4,9.4},{46,10.8},{46.2,10.4},{46.4,12.2},{46.6,8.8},{46.6,12.4},{47,10.4},{47,10.6},{47.8,10.6},{48.4,9.8},{48.6,11.2},{49.2,10.2},{49.2,11.6},{49.4,14},{49.6,14.8},{49.8,10.4},{49.8,13.2},{50.2,11},{50.2,11.8},{50.6,10.4},{51.2,11.6},{51.4,8.8},{51.4,10.8},{51.8,11.4},{51.8,11.6},{52,8.8},{52.4,10.4},{52.6,9},{52.6,11},{52.8,10.4},{53,11.8},{53.6,10.6},{53.8,10.4},{54.2,12.6},{54.4,9},{54.4,12},{54.4,13.8},{54.6,13.2},{54.8,10.8},{54.8,16},{55,14.6},{55.2,9.4},{55.2,9.6},{55.2,14.2},{55.4,12},{55.6,9.2},{55.8,10.2},{55.8,10.8},{57.2,10.2},{57.2,11}}},nil,40,nil,nil,18,nil,"",0},
[596] = {'Brainwashed Noble',531,531,18,18,4,{[40] = {{42.4,76.6}}},nil,40,nil,nil,17,nil,"",0},
[572] = {'Leprithus',404,404,19,19,4,{[40] = {{39.4,26.2},{39.4,26.6},{39.6,35.6},{40.2,27.2},{40.4,29.4},{40.8,32.6},{40.8,35.8},{41.2,27.6},{41.2,32.4},{41.4,29},{41.4,29.6},{41.4,31},{41.6,29.2},{41.8,25.6},{41.8,29.6},{42.4,28.4},{42.4,30.8},{42.6,25.8},{42.6,29.6},{42.6,33.8},{42.8,29},{43.2,28.4},{43.2,31.4},{43.2,31.6},{43.6,28.2},{43.6,30},{43.8,28.6},{44,41.8},{45,27.8},{45.6,28.2},{46.2,31.2},{47.8,29.8},{48,29.2},{49,29},{56.4,80.6},{56.6,79.4},{56.6,80.6},{56.8,80.4},{58,79.2},{58,80.2},{59.4,77},{59.8,77.2},{60,77.6},{60.2,72.8},{61,76.4},{61.2,72.2},{61.2,76.6},{61.4,73.2},{61.4,73.8},{61.4,74.8},{61.6,72},{61.8,68.4},{61.8,69.6},{61.8,72.8},{62,67.2},{62,74},{62.2,68.8},{62.8,65.8},{63,65},{63.4,66.6},{63.8,64.6},{64,63.8},{64.2,68.6},{64.4,66.2},{64.4,67.4},{64.4,68.4},{64.6,61.2},{64.6,63.6},{64.6,69},{64.8,64.6},{64.8,66.8},{64.8,67.6},{65.2,62.4},{65.2,63.2},{65.4,65.8},{65.6,66.6},{66.4,62.4},{66.4,63},{66.6,62.8},{66.8,65.6},{67.6,62.8}}},nil,40,nil,nil,21,nil,"",0},
[1424] = {'Master Digger',328,328,15,15,4,{[40] = {{44.6,19.6},{44.8,20.6},{45.2,18.6},{45.4,18.2},{45.8,18.2},{46.2,18.8},{46.6,19.4},{46.6,19.6}}},nil,40,nil,nil,26,nil,"",0},
[506] = {'Sergeant Brashclaw',417,417,18,18,4,{[40] = {{32.2,38.8},{32.6,34.8},{33.4,29.6},{33.4,32.4},{33.6,33.8},{34,32.6},{34.4,32.4},{34.4,36.4},{34.6,30.4},{34.8,31},{35,29.4},{35,33.8},{35,36.8},{35.2,32.4},{35.4,32.6},{35.6,33.8},{36.2,29},{36.2,31},{36.2,31.8},{36.4,27.2},{36.4,28.4},{36.4,30.4},{36.4,32.8},{36.4,35},{36.6,38.4},{36.8,29.4},{36.8,35.2},{37,27.6},{37,31.4},{37,31.8},{37,35.6},{37.2,30.4},{37.2,33},{37.4,33.8},{37.6,31.4},{37.6,32.8},{37.6,36.2},{37.6,37.4},{37.8,30.4},{38,29.4},{38,33.6},{38,37.8},{38.2,34.6},{38.4,32},{38.4,40},{38.6,31.6},{38.6,33.4},{38.8,29.6},{38.8,33.8},{39,27},{39,35},{39.2,27.8},{39.2,30.8},{39.4,29.4},{39.6,29.2},{39.6,29.6},{39.6,31.2},{39.6,34},{39.8,27.2},{40,36.8},{40,43},{40.2,35},{40.2,36},{40.4,33.2},{40.6,26.6},{40.6,30.6},{40.8,31.8},{41.2,38},{41.6,31.8},{43.4,27},{44,41},{46.4,28},{46.6,28.8}}},nil,40,nil,nil,20,nil,"",0},
[519] = {'Slark',328,328,15,15,4,{[40] = {{25.6,63},{25.8,56.4},{26.2,59},{26.2,64.4},{26.2,65.6},{26.4,47},{26.4,48},{26.4,48.6},{26.4,49.8},{26.4,52.4},{26.4,52.6},{26.4,54.4},{26.4,55.2},{26.4,61.4},{26.4,62.2},{26.4,65.2},{26.4,66.8},{26.4,67.8},{26.6,53},{26.6,56.4},{26.6,59.8},{26.6,65.2},{26.8,47.6},{26.8,52.2},{26.8,66.4},{27,39.2},{27,46.4},{27,49},{27,57.6},{27,66.6},{27,69.4},{27.2,35.2},{27.2,35.6},{27.2,44.2},{27.2,45},{27.2,46.6},{27.2,49.8},{27.2,54.8},{27.2,56.6},{27.2,62.8},{27.2,68.2},{27.4,34},{27.4,36.8},{27.4,38},{27.4,39.8},{27.4,41},{27.4,42.2},{27.4,51.4},{27.4,54},{27.4,70},{27.4,71},{27.4,72.6},{27.6,34},{27.6,34.6},{27.6,42},{27.6,43.4},{27.6,49.6},{27.6,51.4},{27.6,52.4},{27.6,53.2},{27.6,54.4},{27.6,56.6},{27.6,69.8},{27.8,45.2},{27.8,72.2},{28,46.2},{28,70.6},{28.2,36.4},{28.2,36.6},{28.2,37.6},{28.2,41.2},{28.2,63.4},{28.2,73},{28.2,74},{28.4,38.8},{28.4,39.6},{28.4,55.6},{28.4,57.6},{28.4,75.6},{28.4,77},{28.6,35.8},{28.6,39.8},{28.6,56},{28.6,71.4},{28.8,32},{28.8,34.2},{28.8,70.2},{29,34.6},{29,36.8},{29,55.2},{29,73},{29,74},{29,74.8},{29.2,38},{29.2,72.4},{29.2,79.6},{29.4,31.2},{29.4,33},{29.4,76.2},{29.4,77.2},{29.4,77.8},{29.4,79.2},{29.6,32},{29.6,33},{29.6,34.4},{29.6,34.6},{29.6,70.8},{29.6,77.4},{29.6,78.8},{29.8,31.2},{29.8,80.4},{30.2,29},{30.2,36.2},{30.4,29.8},{30.4,80.6},{30.4,82},{30.6,31.2},{30.6,80.2},{30.6,81.2},{30.6,81.6},{30.6,85.2},{30.8,27.8},{31,26.4},{31,27.2},{31.2,29.4},{31.2,83},{31.4,25},{31.4,30},{31.6,25.6},{31.6,27.2},{31.6,27.8},{31.8,81.2},{32.2,82.4},{32.2,83.6},{32.2,84.8},{32.4,21.8},{32.4,23.4},{32.4,23.8},{32.4,24.8},{32.4,83},{32.6,25.2},{32.8,82.8},{33,83.6},{33.2,81.8},{33.2,84.6},{33.4,21.8},{33.4,23.2},{33.4,23.8},{33.6,23.6},{33.6,83.2},{33.8,84.8},{34,21.6},{34,84},{34.2,81.8},{34.4,19.8},{34.4,20.6},{34.4,22.6},{34.8,23},{34.8,82.6},{34.8,87.2},{35,20.6},{35,85.6},{35.2,19.4},{35.2,20.2},{35.4,84.2},{35.4,84.6},{35.8,84.4},{35.8,85},{36,19.2},{36,20.2},{36,86},{36.2,18.4},{36.4,86.6},{37.2,15.6},{37.2,16.8},{37.2,18.2},{37.2,87.8},{37.6,15.6},{37.6,18},{38,15.4},{38,16.6},{38.6,14},{38.8,12},{38.8,16.2},{39,15},{39.4,17},{39.6,13.4},{39.6,14.4},{39.6,14.8},{39.6,15.8},{39.8,10.8},{40.4,12.2},{41.2,12.8},{41.4,12.4},{41.6,12.4},{41.8,12.6},{42.2,11.4},{42.4,10.4},{42.8,11.4},{43,10},{43.2,9.4},{43.2,12.8},{43.6,11.2},{43.6,12.6},{44.2,10},{44.4,9.4},{44.8,9.4},{45.2,10.2},{45.6,8.2},{46.2,10.2},{46.4,11},{46.6,8.2},{46.8,11.6},{47.2,11},{47.4,10.2},{47.8,10.8},{48.4,10.4},{49.2,9.6},{49.4,11},{49.4,12.2},{49.4,12.6},{50,10.4},{50.2,10.8},{50.4,12},{50.4,13.6},{50.8,9},{51,11.6},{51.2,11.4},{51.4,9.6},{51.8,8.8},{52.2,10.6},{52.4,10},{53,11.6},{53.2,10.6},{53.4,9.4},{53.4,10.2},{54.2,10.4},{54.4,11},{54.4,12},{54.4,12.8},{54.6,10.4},{54.8,13.6},{55,10.6},{55.2,12.4},{55.2,13.4},{55.6,14.6},{55.8,9.4},{55.8,10.8},{55.8,11.6},{56.2,10},{56.8,10.6}}},nil,40,nil,nil,18,nil,"",0},
-- Quest 50157 Modan Trophy: Rare NPCs in Loch Modan (zone 38)
[1425] = {'Grizlak',301,301,15,15,4,{[38] = {{34.2,26.8},{34.8,27.2},{35.4,27.8},{35.6,27.8},{36.2,26.6},{36.4,26}}},nil,38,nil,nil,26,nil,"",0},
[1399] = {'Magosh',473,473,21,21,4,{[38] = {{64.6,67.4},{65.2,65.6},{66.4,69},{67.2,63.2},{67.4,68.4},{67.4,68.6},{68,68.6},{68.4,68.4},{68.6,63.2},{68.8,68.4},{69.2,66},{69.2,66.8},{69.6,60.2},{69.6,67.6},{70,65.4},{70,66.2},{70.2,62},{70.4,61.4},{70.4,62.8},{70.4,64.2},{70.4,67.2},{70.6,64.2},{70.6,65.2},{70.6,67},{70.8,65.8},{70.8,68.4},{71.6,68.2},{72.4,67},{72.4,68.6},{72.8,61.8},{72.8,63.6}}},nil,38,nil,nil,59,nil,"Stonesplinter Tribal Shaman",0},
[2476] = {'Large Loch Crocolisk',573,573,22,22,4,{[38] = {{53.2,30.8},{55.8,17.6},{56,19.4},{56,32},{56.8,30.2},{57,20.6},{57.4,29.4},{57.4,30.8},{57.8,22},{57.8,31.4},{58,29.2},{58,32.2},{58,32.6},{58,33.6},{58.2,27.4},{58.4,26.4},{58.4,28},{58.4,29.8},{58.4,35},{58.4,36.8},{58.6,25.2},{58.6,26},{58.6,28.4},{58.6,30.4},{58.6,34.2},{58.6,34.8},{58.6,35.8},{58.8,29.4},{58.8,30.8},{58.8,31.6},{58.8,37.4},{59.2,22.2},{59.2,32.6},{59.2,39},{59.2,39.8},{59.4,38},{59.4,40.8},{59.6,29.2},{59.6,30.2},{59.6,31.2},{59.6,34.8},{59.6,38},{59.8,26.2},{59.8,31.8},{59.8,35.6},{59.8,36.6},{59.8,39.6},{60,33.2},{60,34.4},{60,39.4},{60.2,41},{60.4,27},{60.4,41.8},{60.6,29},{60.6,34.2},{60.8,26.2},{60.8,36.4},{60.8,37.2},{60.8,38.2},{60.8,39},{60.8,39.8},{61,43},{61.2,42},{61.4,40.8},{61.6,41.2},{61.6,41.6},{61.8,34.2},{61.8,36.4},{61.8,42.6},{61.8,44},{62,39},{62.4,45.6},{62.6,35.2},{62.8,42},{63.4,47.6},{63.4,48.6},{64,42.2},{64.4,51.6}}},nil,38,nil,nil,14,nil,"",0},
[14266] = {'Shanda the Spinner',449,449,19,19,4,{[38] = {{75.8,53.4},{76.2,47.2},{76.4,54.2},{76.6,49.8},{76.6,55},{77,51.2},{77.2,53},{77.4,52.2},{77.4,53.8},{77.8,52.2},{77.8,52.6},{77.8,53.6},{78.6,52.6},{79,51.4},{79.2,51.6},{79.8,51.8}}},nil,38,nil,nil,22,nil,"",0},
[14268] = {'Lord Condar',328,356,15,16,4,{[38] = {{57,77},{60.4,77.8},{61.4,77.6},{62.2,78},{62.4,77.4},{62.4,78.6},{62.4,79.6},{62.8,78.2},{63.4,77.4},{63.6,77.8},{64,77},{64.4,76.4},{74.2,67.8},{74.4,64},{74.4,65},{74.4,65.8},{74.4,66.6},{74.4,68.6},{74.6,65.4},{74.6,66},{74.8,67.2},{75.2,67.8},{75.4,69},{75.6,66.4},{75.6,68},{76,67.2},{76.2,73.4},{76.4,73.8},{76.4,75},{76.8,66.2},{77,72.4},{77.4,73.4},{77.4,74},{77.4,74.8},{77.6,72.4},{77.8,74.2},{77.8,74.6},{78,73},{78.4,76.4},{78.6,71.4},{79,75.2},{79.6,72}}},nil,38,nil,nil,73,nil,"",0},
[14267] = {'Emogg the Crusher',1347,1347,19,19,2,{[38] = {{61.6,20},{61.8,24},{62.2,17},{62.4,19.4},{62.8,22.8},{63,23.8},{63.4,19},{63.8,19.4},{64.4,20},{64.4,20.6},{65,21},{65.2,19.4},{65.4,19.8},{66.2,21},{66.2,22.4},{66.2,22.8},{66.4,20.4},{66.6,21},{67.2,21.6},{67.2,30},{67.2,31.4},{67.4,23},{67.6,22.6},{67.8,20.4},{67.8,20.6},{67.8,26.6},{68.4,21.6},{68.4,24},{68.4,26.4},{68.4,27.8},{68.4,28.8},{68.6,24.4},{68.6,26.2},{68.6,29.2},{68.8,21.2},{68.8,24.8},{69.2,23.4},{69.2,27.6},{69.4,22.2},{69.4,27.2},{69.4,29.6},{69.6,21.8},{69.6,23.2},{69.6,25.6},{69.6,28.8},{69.8,21.4},{69.8,24.2},{69.8,24.6},{69.8,26.8},{69.8,27.6},{70,20.4},{70.6,20.4},{70.8,21},{70.8,23.6},{70.8,26},{71.4,22.4},{71.4,23.2},{71.4,24.6},{71.6,19.6},{71.6,21},{72,23.8},{72.2,23.2},{72.2,24.8},{72.2,25.8},{72.4,21.8},{72.4,27},{72.6,20.2},{72.6,27.6},{72.8,19.2},{72.8,21.2},{72.8,22},{72.8,24.4},{72.8,25},{73,27},{73,28.6},{73.2,23.2},{73.4,26},{73.6,23},{73.6,26},{73.6,30},{74,28.6},{74.2,28.4},{74.2,31},{74.8,32},{75.2,24.8},{75.4,34.4}}},nil,38,nil,nil,45,nil,"",0},
[1398] = {'Boss Galgosh',573,573,22,22,4,{[38] = {{59.6,65.2},{62.6,61.8},{62.8,62.8},{64,62.2},{64,66.8},{64.4,65.8},{64.6,64.2},{65,65.4},{65.2,67.4},{65.4,68.4},{65.8,68},{66,66},{66.2,65.2},{66.4,68.6},{66.8,68.2},{67,63.2},{67,66},{67,68.8},{67.4,64.8},{67.8,68.8},{68.4,66},{68.4,68.2},{68.8,66},{68.8,68.6},{69,68.2},{69.2,66.6},{69.6,67.4},{69.8,65.8},{70,65.2},{70.4,63.8},{70.4,67.6},{70.6,66.8},{70.6,68.6},{70.8,65},{70.8,66.4},{70.8,67.8},{71.6,68.6},{71.8,64.2},{72.4,67.6},{72.6,69}}},nil,38,nil,nil,59,nil,"Stonesplinter Chieftain",0},
-- Quest 50031 Stormbound: Storm Peaks (zone 67) elementals
-- All 11 elementals scraped from Storm Peaks filter page
[30120] = {'Seething Revenant',12600,12600,80,80,0,{[67] = {{76.4,63},{76.8,63},{77,62.2},{77.6,62.4},{77.6,62.6}}},nil,67,nil,nil,834,nil,"",0},
[30053] = {'Icebound Revenant',12600,12600,78,80,0,{[67] = {{41.8,65.8},{42,63.4},{42,65},{42.4,63.8},{42.4,66.8},{42.6,63.8},{42.6,66.4},{42.8,65},{43,67.4},{43.2,67.6},{43.6,67.6},{43.8,67},{44,66.4}}},nil,67,nil,nil,16,nil,"",0},
-- Additional Storm Peaks elementals for Quest 50031 (scraped from filter page)
[29504] = {'Seething Revenant',12600,12600,80,80,0,{[67] = {{68,63.6},{68.2,63.2},{69,57.4},{69,57.6},{69.2,62.4},{69.4,59.4},{69.4,60.4},{69.4,60.6},{69.4,62.6},{69.6,60.6},{69.6,62.6},{69.8,59.4},{70,57},{70,59.6},{70.2,56.4},{70.4,57.6},{70.4,61.8},{70.6,57.8},{70.6,60.4},{70.6,61.8},{70.6,62.6},{71,56},{71,56.6},{71.4,59},{71.4,60.6},{71.6,59},{71.6,60.4},{71.6,60.8},{72.4,62},{72.6,62.2},{73.4,63},{73.8,63},{74.2,62.4},{75,63.2},{76.4,63},{76.4,63.8},{76.6,62.8},{76.6,63.6},{77,61.4},{77,62},{77.6,62.4},{77.6,62.8}}},nil,67,nil,nil,834,nil,"",0},
[30450] = {'Wailing Winds',12600,12600,79,80,0,{[67] = {{61.4,38.4},{61.4,39},{61.4,39.6},{61.6,38.2},{61.6,39.2},{61.8,40},{62.2,40.8},{62.4,41.6},{62.6,40.4}}},nil,67,nil,nil,16,nil,"",0},
[30184] = {'Scion of Storm',12600,12600,80,80,0,{[67] = {{64,42.8},{64.2,39.4},{64.2,42.2},{64.4,40.4},{64.4,41.2},{64.4,44.4},{64.4,44.6},{64.6,40.6},{64.6,44.2},{64.8,39},{65,44.6},{65.2,42.4},{65.2,42.6},{65.4,39.6},{65.6,38.4},{65.6,40.4},{65.6,42.8},{65.6,43.8},{65.8,39.4},{65.8,41.2},{66.2,41.8},{66.4,44.8},{66.6,39.4},{66.6,40.6},{66.6,42.2},{66.6,42.6},{67,44.6},{67,45.6},{67.2,40},{67.4,43.8},{67.6,43.8},{67.6,44.6},{67.8,39.6},{67.8,41},{68.2,43.4},{68.4,39.4},{68.4,42.4},{68.6,40.2},{68.6,40.8},{68.6,43.8},{69,42.6},{69.4,41.6},{69.6,40.4},{69.6,41},{69.6,41.6}}},nil,67,nil,nil,16,nil,"",0},
[29844] = {'Icebound Revenant',12600,12600,78,80,0,{[67] = {{40.4,62.4},{40.4,63},{40.8,62.8},{40.8,63.6},{41.2,62.4},{41.4,61},{41.4,65},{41.4,66.2},{41.6,62.4},{41.8,60.4},{41.8,62.8},{41.8,65.8},{42,61.4},{42,65.2},{42.4,63.8},{42.6,66.2},{42.8,67},{43,64.4},{43,64.8},{43.2,67.6},{43.6,67.6},{43.8,67},{44,66},{44.4,64.4},{44.4,64.6},{44.6,64.4},{44.6,65.4},{44.6,65.6}}},nil,67,nil,nil,16,nil,"",0},
[30387] = {'Seething Revenant',12600,12600,80,80,0,{[67] = {{73.4,62.8},{73.8,62.4},{74,62.8},{74.6,62.4},{75,62.6},{75.2,63.6},{75.6,63},{75.8,63.6},{75.8,64.6},{76.6,64},{76.8,63},{77.2,62.2},{77.6,62.4},{77.6,62.6}}},nil,67,nil,nil,834,nil,"",0},
[30160] = {'Brittle Revenant',12600,12600,80,80,0,{[67] = {{69,59.4},{69.2,57.4},{69.2,59.6},{69.4,58.2},{69.4,61},{69.4,61.8},{69.6,59.8},{70,57},{70,58.2},{70,59.4},{70.2,61},{70.2,62},{70.2,62.6},{70.6,56.8},{70.6,60.4},{70.6,61.4},{70.8,57.8},{70.8,58.8},{70.8,61.6},{71.2,56},{71.4,62.6},{71.6,62.4},{71.8,62.6}}},nil,67,nil,nil,834,nil,"",0},
[29624] = {'Stormrider',12600,12600,80,80,0,{[67] = {{23.2,36.2},{23.4,37.4},{23.4,37.6},{23.8,37.4},{23.8,37.6},{24.4,39.4},{24.4,40.2},{24.4,40.6},{24.6,39.8},{25,43},{25.2,38.6},{25.4,37.4},{25.4,37.8},{25.4,40.8},{25.6,37.4},{25.6,37.8},{26,39.2},{26.2,39.6},{26.2,40.8},{26.2,44},{26.4,41.8},{26.4,42.8},{26.4,44.6},{26.4,50.6},{26.6,41.8},{27,43.6},{27.2,38.4},{27.2,40.4},{27.2,40.6},{27.2,48.8},{27.4,39.2},{27.4,42.8},{27.4,45.4},{27.4,45.8},{27.4,47},{27.4,48.4},{27.4,49.6},{27.6,39.2},{27.6,39.8},{27.6,41.4},{27.6,42},{27.6,44.4},{27.6,44.6},{27.6,45.8},{27.6,47.6},{27.8,47.4},{28,42.8},{28.2,37.4},{28.4,49.4},{28.4,49.8},{28.4,51.2},{28.4,51.6},{28.6,43},{28.6,51},{28.6,51.6},{28.8,47.4},{28.8,48.4},{29,46.2},{29,49.6},{29.2,49.4},{29.6,47.4},{29.6,50.6},{30,49.4},{30,49.6},{30.2,48.4},{30.6,48.2},{31.2,49.2},{31.2,49.6},{31.6,49.6},{32,47.2},{32.4,48.2},{32.4,48.6},{32.6,48.6},{32.8,47.8},{33.4,47.2},{33.6,47.2},{33.6,47.6}}},nil,67,nil,nil,16,nil,"",0},
[30001] = {'Blackmaw',12600,12600,78,80,0,{[67] = {{30.4,36.8},{30.6,37}}},nil,67,nil,nil,16,nil,"",0},
[30474] = {'The North Wind',12600,12600,80,80,0,{[67] = {{64.2,47.6},{64.4,47.2},{64.6,47.2},{64.6,47.6}}},nil,67,nil,nil,16,nil,"",0},
-- Quest 50095 Peak Predators: Storm Peaks (zone 67) beasts
-- All hostile beasts scraped from Storm Peaks filter page
[29411] = {'Crystalweb Weaver',12600,12600,77,78,0,{[67] = {{40.2,74.4},{40.2,74.6},{40.2,76.4},{40.2,76.6},{41.2,76},{41.4,74},{41.4,74.8},{41.6,75},{41.6,75.6},{42,74.4},{42.6,74.4},{42.6,74.6},{43.4,76.2},{43.6,74.2},{43.8,75.4},{44,76},{44.6,73.4},{45,73.8},{45.6,73.8},{47.2,72.4},{47.2,72.6}}},nil,67,nil,nil,16,nil,"",0},
[29590] = {'Blighted Proto-Drake',12600,12600,80,80,0,{[67] = {{21.4,57.4},{21.4,58},{21.4,58.6},{21.6,57.2},{22,59.8},{22.2,58.6},{22.4,58.4},{22.6,58.6},{23,57.4},{23,59.6},{23.2,58.2},{24,57.8},{24,58.6},{24.4,57.4},{24.6,57.2},{24.6,57.6}}},nil,67,nil,nil,16,nil,"",0},
[30148] = {'Infesting Jormungar',12600,12600,80,80,0,{[67] = {{46.4,55.4},{46.4,55.6},{46.6,57.4},{46.8,55.6},{47.2,54.4},{47.2,54.6},{47.4,48.4},{47.4,48.6},{47.4,49.6},{47.4,50.6},{47.6,48.4},{47.6,48.6},{47.6,50},{47.8,46.6},{47.8,50.8},{47.8,54.6},{48.4,46.4},{48.4,51.6},{48.4,53},{48.4,54.2},{48.6,46.4},{48.6,47.4},{48.6,48.6},{48.6,51.8},{48.6,54.2},{48.8,47.8},{49,52.8}}},nil,67,nil,nil,16,nil,"",0},
[32475] = {'Terror Spinner',12600,12600,76,76,0,{[67] = {{72,75}}},nil,67,nil,nil,16,nil,"",0},
[29958] = {'Tundra Ram',12600,12600,79,80,0,{[67] = {{67.2,64.2},{67.6,64.6},{68.2,63.2},{68.4,63.6},{68.6,63.4},{68.6,63.6},{69.2,62},{69.4,55.2},{69.4,55.8},{69.6,55.2},{69.6,55.6},{70.2,54},{70.2,58.8},{70.2,59.8},{70.4,56.8},{70.6,54.4},{71.2,55.2},{71.2,55.6},{71.4,56.8},{71.4,58.2},{71.4,59.2},{71.4,60.4},{71.4,60.6},{71.6,56.4},{71.6,56.6},{71.6,57.8},{71.6,60.6},{71.8,59},{71.8,60.4},{72,61.6}}},nil,67,nil,nil,16,nil,"",0},
[29358] = {'Frostworg',12600,12600,79,80,0,{[67] = {{59.4,40.6},{59.6,41.4},{59.6,42.6},{60,40.8},{60.2,41},{60.4,40.4},{60.4,40.6},{60.6,42.2},{61,41},{61.2,40.2},{61.4,42.6},{61.6,41.2},{62,41.8},{62.2,41.2},{62.4,41},{62.4,42.6},{62.6,41.4},{62.6,42.6},{62.8,41.6},{63,41},{63.2,42.4},{63.4,41},{63.6,42.2},{64,42.6},{64.2,41.6},{64.4,42.2},{64.6,42.6}}},nil,67,nil,nil,16,nil,"",0},
[29469] = {'Ice Steppe Rhino',12600,12600,78,79,0,{[67] = {{59.2,52.6},{59.4,51.8},{59.6,52.6},{60,53},{60.2,52},{60.2,53.4},{60.4,53},{60.6,51.4},{60.6,52},{60.8,53.6},{61,52},{61.2,52.6},{61.4,53.4},{61.6,52.8},{61.8,53},{62,52.8},{62.2,52.2},{62.4,51.8},{62.4,53.6},{62.6,52.2},{62.8,52.6},{63,52},{63.2,53},{63.4,52},{63.6,53.2},{63.8,52.6},{64,52.8}}},nil,67,nil,nil,16,nil,"",0},
[30422] = {'Roaming Jormungar',12600,12600,79,80,0,{[67] = {{57.2,57.6},{57.4,58},{57.6,57.6},{57.6,58.4},{57.8,57},{58,57.4},{58,58.2},{58.2,58},{58.4,57.4},{58.4,58.2},{58.6,57.8},{58.8,57.4},{58.8,58.2},{59,57.6},{59,58.4},{59.2,58},{59.4,57.4},{59.4,58.6},{59.6,58.4},{59.8,57.8},{60,58.2},{60.2,58.6},{60.4,58},{60.6,57.4}}},nil,67,nil,nil,16,nil,"",0},
[30445] = {'Ice Steppe Bull',12600,12600,78,79,0,{[67] = {{60.4,59.4},{60.6,59},{60.8,58.4},{61,58.6},{61.2,59.4},{61.4,58.8},{61.6,59.2},{61.8,58.6},{62,59},{62.2,58.4},{62.4,59},{62.6,58.6},{62.8,59.2},{63,58.8},{63.2,59.4},{63.4,58.6},{63.6,59},{63.8,58.4},{64,59.2},{64.2,58.6},{64.4,59},{64.6,58.8},{64.8,59.4},{65,58.6},{65.2,59}}},nil,67,nil,nil,16,nil,"",0},
[30291] = {'Ravenous Jormungar',12600,12600,79,80,0,{[67] = {{53.4,62},{53.6,62.2},{54,61.4},{54.4,60.4},{54.4,63.2},{54.4,63.6},{54.6,63.2},{54.6,63.6},{55,60.4},{55.2,60.8},{55.4,62},{55.6,61.4},{55.6,61.6},{55.6,63.4},{55.8,63.8}}},nil,67,nil,nil,16,nil,"",0},
[29412] = {'Crystalweb Spitter',12600,12600,77,78,0,{[67] = {{40.2,75.4},{40.2,76},{40.4,77.2},{40.4,77.6},{40.6,77.2},{41.2,74},{41.2,75.4},{41.4,76},{41.6,76},{42.2,74.4},{42.2,74.6},{42.6,74.4},{43.4,75},{43.6,74.8},{44,74.4},{44,75.6},{44.8,73.8},{45.8,73.8},{46.4,73.4},{46.6,73.4},{46.6,73.6},{47.2,72.4},{47.6,72.8}}},nil,67,nil,nil,16,nil,"",0},
[30448] = {'Plains Mammoth',12600,12600,77,78,0,{[67] = {{61.2,42},{61.6,42},{62.2,43.4},{62.2,43.6},{62.4,44.8},{62.6,44.4},{62.6,44.8},{62.6,45.6},{63,47.2},{63,47.8},{63.4,42.2},{63.4,42.6},{63.4,48.6},{63.6,42.8},{63.6,48.4},{63.8,47.2},{63.8,48.6},{64,43.8},{64.4,41.4},{64.4,42},{64.4,44.8},{64.4,45.8},{64.6,41.2},{64.6,42.2},{64.8,46.2},{65,45},{65.2,43.2},{65.4,43.8},{65.4,48.6},{65.6,41.8},{65.6,43.4},{66,45.6},{66,48.2},{66,48.6},{66.2,44.4},{66.4,45.4},{66.4,47.2},{66.6,45.2},{66.6,47.6},{66.8,47.2},{67.4,46.4},{67.6,46.4},{67.6,46.6},{67.6,47.6}}},nil,67,nil,nil,16,nil,"",0},
[29461] = {'Icetip Crawler',12600,12600,78,78,0,{[67] = {{47,72},{47.2,71.4},{47.2,72.6},{47.4,70},{47.6,70.4},{48,71.8},{48.2,71.2},{48.6,71.4},{48.6,71.6}}},nil,67,nil,nil,16,nil,"",0},
[29605] = {'Ravenous Jormungar',12600,12600,79,80,0,{[67] = {{53.4,62},{53.6,62.2},{53.8,62.8},{54.2,61},{54.6,63.6},{55.2,60.4},{55.2,60.6},{55.4,62},{55.4,63.2},{55.6,61.4},{55.6,62},{55.6,63.4},{55.8,64.2}}},nil,67,nil,nil,16,nil,"",0},
[30447] = {'Romping Rhino',12600,12600,78,79,0,{[67] = {{61.2,41.8},{61.6,43},{62.2,44.4},{62.4,42},{62.4,44.6},{62.6,42.8},{63,42.4},{63,45},{63.2,44.4},{63.6,43.6},{64,42.6},{64.2,42},{64.6,41.8},{64.6,43.2},{64.8,43.6},{65,44.6},{65.4,48.8},{65.6,42.4},{65.6,42.6},{65.6,43.6},{65.8,45},{66.4,46},{66.4,47.4},{66.4,47.6},{66.8,45.8},{66.8,47.6},{67.4,46.6},{67.6,47.6},{67.8,46.8}}},nil,67,nil,nil,16,nil,"",0},
[29319] = {'Icepaw Bear',12600,12600,76,77,0,{[67] = {{68.4,73.6},{68.6,73.4},{68.8,73.8},{69.4,74.8},{69.6,72.8},{69.8,74.4},{69.8,74.6},{70.2,71.4},{70.2,71.6},{70.6,73.2},{71.4,72.4},{71.4,74.4},{71.4,74.8},{71.6,72.4},{71.6,74.4},{72.2,73.4},{72.4,75},{72.6,72.4},{72.6,73.8},{72.8,75},{73,73.4},{74.2,74.2},{74.8,74.2},{75.4,74.6},{75.6,73.6},{75.6,74.8}}},nil,67,nil,nil,16,nil,"",0},
[29390] = {'Snowdrift Jormungar',12600,12600,79,80,0,{[67] = {{23.4,36.2},{23.4,36.8},{23.4,38.4},{23.8,37.8},{23.8,39.2},{24,36.4},{24,40},{24.4,40.8},{24.8,36.4},{24.8,36.8},{24.8,43},{25.8,44.8},{26.2,42.4},{26.2,43},{26.4,43.6},{26.4,50},{26.6,42.4},{26.8,41},{27,42.6},{27,44.8},{27.2,40.4},{27.2,46.2},{27.4,44.2},{27.4,46.6},{27.4,47.6},{27.4,49.6},{27.4,54.4},{27.4,54.8},{27.6,46},{27.6,46.6},{27.6,47.6},{27.6,49.4},{27.6,54},{27.8,40.2},{27.8,41.2},{27.8,43.2},{27.8,52.8},{28,41.6},{28,44.4},{28,44.6},{28,52.4},{28.2,50.4},{28.2,50.6},{28.8,42.2},{29,47.2},{29.4,48.2},{29.4,49.4},{29.4,50.4},{29.4,50.6},{29.6,48.2},{29.8,48.8},{29.8,50.6},{30,49.8},{30.8,50},{31.6,50},{32,47},{32.4,47.6},{32.6,47.2},{32.6,47.6},{32.6,48.6}}},nil,67,nil,nil,16,nil,"",0},
[29562] = {'Icemaw Bear',12600,12600,79,80,0,{[67] = {{54.2,63},{54.6,63.4},{54.6,63.6},{55.6,61.4},{55.6,62},{55.6,63.4},{55.8,64}}},nil,67,nil,nil,16,nil,"",0},
[30340] = {'Jorcuttar',12600,12600,80,80,0,{[67] = {{54.6,60.8}}},nil,67,nil,nil,16,nil,"",0},
[30292] = {'Dead Icemaw Bear',12600,12600,79,80,0,{[67] = {{53.6,61.8},{54.2,61.4},{54.4,60.4},{54.4,63.2},{54.8,60.4},{55,61.8},{55.4,60.8},{55.4,63.2},{55.6,61.4},{55.6,62.2},{55.6,63.2},{55.8,64}}},nil,67,nil,nil,16,nil,"",0},
[29808] = {'Stormcrest Hatchling',12600,12600,77,77,0,{[67] = {{26,72},{26,72.6},{26.6,72.4},{27.4,71.4},{27.8,71},{28.2,68},{28.2,68.8},{28.2,70},{28.6,67.4},{28.6,68},{28.6,68.6},{29,65.2},{29.4,63.8},{29.8,66.8},{30,66.4},{30.4,64.6},{30.8,65.4},{31,66},{31,66.6},{31.6,66.2},{33,66.6},{33.2,66},{34.4,65.8},{34.8,65.8},{35.8,66.4},{35.8,69.4},{35.8,75},{36.2,67.2},{36.2,69.8},{36.4,67.8},{36.4,71},{36.4,74.4},{36.6,68.2},{36.6,68.6},{36.6,69.6},{36.6,71.4},{36.6,72},{36.8,72.8},{37.2,73.6}}},nil,67,nil,nil,16,nil,"",0},
[30174] = {'Hyldsmeet Warbear',12600,12600,80,80,0,{[67] = {{49.4,67.4},{49.4,67.8},{49.4,68.8},{49.6,68.2},{49.6,68.6}}},nil,67,nil,nil,16,nil,"",0},
[34920] = {'Deep Jormungar',12600,12600,80,80,0,{[67] = {{37.4,56},{37.6,56},{37.6,59.8},{38,57},{38,58.8},{38.2,54.4},{38.2,62},{38.4,55},{38.4,58.4},{38.4,60.6},{38.6,58.4},{38.8,54.6},{38.8,59.4},{38.8,59.6},{39.2,61.4},{39.2,61.6},{39.4,53.8},{39.8,61.2},{40,52.4},{40,52.6},{40,60.2},{40,61.8},{40.4,62.8},{40.6,53.2},{40.6,60.6},{40.6,62.8},{40.8,52},{41.4,53.6},{41.4,60},{41.6,52.2},{42,53.2},{42.2,60},{42.4,53.8},{42.4,55},{42.6,58.4},{42.8,54},{42.8,56.8},{42.8,58.6},{43.2,54.8},{43.2,56},{43.2,59.6},{43.4,60.6},{43.6,58.6},{43.8,56.4},{43.8,56.6},{43.8,61.8},{44.2,58.4},{44.2,61},{44.6,59.4},{44.6,61.8},{45,55.6},{45,56.8},{45,60.8},{45.4,60},{45.6,55.6},{45.6,57},{45.8,57.6},{46,61.2},{46,63.4},{46.4,59.2},{46.4,62.4},{46.4,64},{46.6,60.6},{46.6,64},{46.8,63},{47,60},{47.2,62.4},{47.6,61.6}}},nil,67,nil,nil,16,nil,"",0},
-- Quest 50096 Peak Predators: Icecrown (zone 210) beasts
-- All hostile beasts scraped from Icecrown filter page
[29392] = {'Ravenous Jaws',12600,12600,79,80,0,{[210] = {{2.4,42.2},{2.6,42.2},{2.8,42.6},{3.2,40.4},{3.2,44.2},{3.2,44.6},{3.4,37.4},{3.4,37.8},{3.4,40.6},{3.4,47.2},{3.6,38},{3.6,43.8},{4,42.4},{4,42.6},{4,44.6},{4.2,36.2},{4.2,36.6},{4.2,39.2},{4.2,46.4},{4.2,46.6},{4.4,34.4},{4.4,34.8},{4.4,40.4},{4.4,41.2},{4.6,34.4},{4.6,36.6},{4.6,39.2},{4.6,40.4},{4.6,40.8},{4.6,42.6},{4.8,38.2},{5,41.6},{5,46.2},{5.2,47.6},{5.4,35.4},{5.4,35.6},{5.4,47.4},{5.6,35.4},{5.8,46},{6.2,42.4},{6.2,42.6},{6.2,43.8},{6.4,34.2},{6.4,36.2},{6.4,36.6},{6.4,45},{6.4,46.8},{6.4,47.8},{6.6,36.4},{6.6,36.6},{6.6,42.4},{6.6,46.4},{6.6,47.6},{6.8,33.8},{6.8,44.4},{6.8,44.6},{6.8,47.4},{7.4,35.4},{7.4,43},{7.6,35.4},{7.6,35.6},{7.6,43},{8.2,34.4},{8.4,39.4},{8.4,39.6},{8.4,41.4},{8.4,41.6},{8.6,32.2},{8.6,41.4},{8.8,33},{8.8,39.4},{8.8,39.6},{8.8,42},{9.4,34.4},{9.4,34.6},{9.4,35.6},{9.6,34.4},{10.2,33.4},{10.2,35.6},{10.2,41.8},{10.4,35.2},{10.4,39.4},{10.4,40.4},{10.4,40.6},{10.6,33.4},{10.6,35.4},{10.6,40},{11.2,34.2},{11.4,36.2},{11.4,36.6},{11.4,41.4},{11.4,41.6},{11.6,34.8},{11.6,41.6},{11.8,36.6},{11.8,41.4},{12,34.4},{12,36},{12.2,42.6},{12.2,46.4},{12.4,37.8},{12.4,38.6},{12.4,47},{12.4,47.8},{12.6,35.4},{12.6,35.6},{12.6,37.8},{12.6,47.2},{12.6,47.6},{12.8,46.4},{13.2,41.4},{13.2,42},{13.2,43.4},{13.2,44.6},{13.4,36.8},{13.4,39.4},{13.4,39.8},{13.4,43.8},{13.4,48.6},{13.6,36.8},{13.6,39.4},{13.6,39.8},{13.6,42},{13.6,42.6},{13.6,43.8},{13.6,48.4},{13.6,48.6},{14.2,38.2},{14.4,40.8},{14.4,45.4},{14.4,46.2},{14.4,46.6},{14.6,38.2},{14.6,40.8},{14.6,44},{14.6,46},{14.6,46.8},{15.4,39.2},{15.4,39.6},{15.4,42.2},{15.4,42.8},{15.4,45.4},{15.6,39.2},{15.6,39.8},{15.6,42.6},{15.6,45.4},{15.6,45.6},{15.8,42.2}}},nil,210,nil,nil,16,nil,"",0},
[30206] = {'Carrion Fleshstripper',12600,12600,76,77,0,{[210] = {{83,72.4},{83.2,72.6},{83.2,77.4},{83.2,77.6},{83.4,74.2},{83.4,75.4},{83.4,76},{84,73.6},{84,80},{84.2,76.2},{84.4,72.4},{84.4,72.8},{84.4,74.8},{84.4,77.4},{84.4,77.6},{84.4,78.6},{84.6,72.4},{84.6,72.8},{84.6,74.6},{84.6,77.4},{84.6,78.6},{84.8,73.8},{84.8,78.4},{84.8,80.4},{85,75.6},{85,80.8},{85.8,73.4},{85.8,73.6},{85.8,78.8}}},nil,210,nil,nil,16,nil,"",0},
[30330] = {'Jotunheim Proto-Drake',12600,12600,80,80,0,{[210] = {{28,38.4},{29.2,24.8},{29.2,35.2},{29.6,32.8},{30,33.6},{30.2,25},{30.2,34.6},{31.2,25.6},{32.2,26.4},{32.8,24},{33.2,25},{33.6,27.8}}},nil,210,nil,nil,16,nil,"",0},
[30430] = {'Sentry Worg',12600,12600,77,78,0,{[210] = {{15.2,58.2},{15.2,58.8},{19.6,56.2},{19.8,55.4},{20,57},{20.6,54.2},{20.8,53.2},{22,54},{22.4,59.2},{23,54.2},{23.4,54.6},{23.4,57.8},{23.6,54.8},{23.6,58.4},{23.6,58.6},{23.8,54.2},{23.8,56.4},{23.8,57},{27.2,40.2},{27.2,42.8},{27.4,39.4},{27.6,37.2},{28,39.4},{28,40},{28.2,40.6},{28.2,44.2},{28.2,45.4},{28.4,38.2},{28.4,41.8},{28.4,42.6},{28.4,46.4},{28.4,46.8},{28.6,38.2},{28.6,40.2},{28.8,40.6},{28.8,41.8},{28.8,46.4},{29,26.4},{29,29.2},{29,31.4},{29.2,27.2},{29.2,28.2},{29.2,35},{29.2,36.2},{29.2,37.4},{29.2,39.2},{29.4,30.2},{29.4,43.2},{29.4,44.4},{29.4,44.6},{29.4,47.4},{29.4,48.2},{29.6,27.8},{29.6,36.2},{29.8,34},{29.8,34.8},{29.8,47},{30,36.6},{30.2,26.6},{30.2,32.6},{30.2,38.4},{30.4,26.4},{30.4,31.4},{30.4,31.6},{30.4,38.6},{30.4,43.4},{30.4,43.8},{30.4,47.6},{30.6,31.6},{30.6,33.8},{30.6,34.6},{30.8,31.4},{30.8,43.6},{30.8,47.6},{31,33.2},{31,37.2},{31,38.4},{31,43.2},{31.2,26.4},{31.2,26.8},{31.2,39.4},{31.2,47.4},{31.4,39.6},{31.4,40.6},{31.6,26.4},{31.6,39.4},{31.6,40.8},{31.6,47.6},{31.8,32.2},{31.8,40.4},{32,32.6},{32,42.2},{32,47.4},{32.2,27.4},{32.2,27.8},{32.4,28.6},{32.4,31},{32.4,42.8},{32.6,31},{32.6,43},{32.8,28.2},{33.2,30},{33.4,27},{33.4,28.8},{33.6,28.8},{34.4,27.2},{34.4,27.6},{34.6,27.4},{34.6,27.6},{35.8,28.8},{36.4,24.6},{36.4,26.4},{36.4,26.6},{36.6,25.8},{36.6,26.6},{36.8,24.2},{36.8,25.4}}},nil,210,nil,nil,16,nil,"",0},
[31265] = {'Savage Proto-Drake',12600,12600,80,80,0,{[210] = {{52.8,52},{53,52.6},{53.2,53.6},{53.8,52.6},{54,52.4},{54.8,51.4},{55,51.8},{55.4,52.8},{55.8,51},{56,57.4},{56,57.8},{56.4,52.4},{56.4,52.6},{56.6,52.2},{56.6,52.8},{56.8,55},{56.8,57.4},{57.4,55.8},{57.6,56.8},{57.8,56},{58,55},{58.2,54.4},{59,56.2},{59,56.6},{59,62.4},{59.4,62.8},{59.6,62.8},{59.8,55.2},{59.8,62.4},{60.2,64}}},nil,210,nil,nil,16,nil,"",0},
[31747] = {'Necrotic Webspinner',12600,12600,79,80,0,{[210] = {{44.4,66.8},{45,67.2},{45.2,67.8},{46,67.4},{46,68},{48.2,65.2},{48.4,64},{48.6,66},{49,64.8},{49.2,64},{51,75.6},{51.2,75.2},{51.6,75.4},{51.6,76},{52.8,76.2},{53.2,77.4},{53.4,74},{53.4,75.4},{53.4,77.6},{53.6,75},{53.6,75.6},{53.6,77.8},{54,77},{54.4,80},{54.6,79.4},{54.6,79.8},{54.8,81.4},{55,81.6},{55.2,82.6}}},nil,210,nil,nil,16,nil,"",0},
[32484] = {'Bone Spider',12600,12600,80,80,0,{[210] = {{30,28.4},{30.4,29.4},{30.4,29.8},{30.6,28.4},{31,30.8},{31.2,29},{31.4,29.6},{31.6,28.4},{31.6,29.4},{31.8,29.8}}},nil,210,nil,nil,16,nil,"",0},
[35060] = {'North Sea Thresher',12600,12600,79,80,0,{[210] = {{65.8,8.6},{66.4,7.4},{66.4,8},{66.4,9.8},{66.8,7.4},{66.8,7.6},{66.8,9},{67.2,6.4},{67.6,8.4},{72,6.2},{73,9.4},{73.4,9.8},{74,7.8},{74,10.4},{74.2,9},{74.2,10.6},{74.6,8.4},{74.6,9.4},{74.6,9.6}}},nil,210,nil,nil,16,nil,"",0},
[35061] = {'North Sea Blue Shark',12600,12600,79,80,0,{[210] = {{66,9},{66.4,7.4},{66.4,8},{66.4,10},{66.8,7.4},{66.8,7.6},{66.8,9},{67.6,8.4},{68.8,5.6},{72,6.2},{73,9.6},{73.2,10.6},{73.4,9.2},{74.2,9},{74.2,10.4},{74.2,10.6},{74.6,9.6},{74.8,9.2}}},nil,210,nil,nil,16,nil,"",0},
[35071] = {'North Sea Mako',12600,12600,79,80,0,{[210] = {{65.8,8.6},{66.4,8},{66.8,7.4},{66.8,7.6},{66.8,9},{67,9.6},{67.6,8.4},{68.8,5.6},{72,5.6},{73.4,9.8},{73.8,7.8},{74,10.6},{74.2,9},{74.2,10.4},{74.6,9.6},{74.8,9.4}}},nil,210,nil,nil,16,nil,"",0},
[35470] = {'Icehowl',12600,12600,80,80,0,{[210] = {{72.4,25},{72.6,25}}},nil,210,nil,nil,16,nil,"",0},
[35482] = {'Hungry Jormungar',12600,12600,83,83,0,{[210] = {{70.4,23.8},{23.8}}},nil,210,nil,nil,16,nil,"",0},
-- Quest 50205 Storm Peaks Trophy: Rare NPCs in The Storm Peaks (zone 67)
[35189] = {'Skoll',12600,12600,80,80,0,{[67] = {{27.8,47.6},{27.8,50.8},{28,50.4},{30,64.6},{30.2,64.4},{46.2,64.4},{46.2,65.6},{46.4,64.8},{46.6,64.2},{48,63}}},nil,67,nil,nil,16,nil,"",0},
[32491] = {'Time-Lost Proto-Drake',12600,12600,80,80,0,{[67] = {{31.4,69.8},{32,65.6},{35,76.6},{36,76},{36.8,74.4},{37,71.4},{38.2,60.8},{41.2,61.4},{43.8,30.6},{45,59},{49.6,30.2},{50.8,71.2},{52.2,33.4},{52.2,34.8}}},nil,67,nil,nil,16,nil,"",0},
[32630] = {'Vyragosa',12600,12600,80,80,0,{[67] = {{26.2,42.2},{26.2,43.4},{26.2,74.2},{26.4,58.4},{26.8,44.2},{27,42},{27.2,55},{27.2,63.8},{27.2,70.6},{27.4,44.8},{27.4,67.6},{27.8,38.6},{27.8,62.2},{28,64.2},{28.4,51.2},{28.4,69},{29,50.6},{29,66},{29,67.2},{29.6,65.8},{29.8,38},{30.2,68},{31,69.4},{31.4,65.8},{31.6,64.4},{31.6,66},{31.6,69.4},{33.2,36.6},{33.4,35.4},{33.8,64.8},{33.8,66},{34.6,66.4},{35,65.2},{35.2,66.6},{35.6,74},{35.8,77.8},{36,64.2},{36,65},{36.4,62.6},{36.4,68.4},{36.4,76},{36.6,73},{36.8,62.8},{36.8,69},{36.8,74.8},{37,69.6},{37,72},{37,74.2},{38.2,61.2},{38.2,74},{38.4,75.2},{38.8,61.4},{39,59.4},{39.2,73},{40,65.2},{40.2,29.6},{40.4,63.4},{40.4,71.8},{40.8,55.2},{40.8,70.8},{40.8,84},{41.2,30.6},{41.2,60},{41.8,60.4},{42.8,30.6},{43.6,30.4},{43.8,30.6},{44.6,58.6},{45.6,82.4},{46,62},{46.2,30},{46.4,31.4},{46.6,62.6},{46.8,31.4},{47.4,63.8},{47.4,65.2},{47.6,64.6},{47.8,30.6},{48.4,67.8},{48.6,66.4},{48.8,67},{49,29.6},{49,67.6},{49,74.8},{49.2,31},{49.2,46.8},{49.6,30.4},{50,69},{50.2,69.8},{51,71},{51.8,34},{52,32.2},{52,33.2},{52.2,34.8}}},nil,67,nil,nil,16,nil,"",0},
[32500] = {'Dirkee',12600,12600,80,80,0,{[67] = {{37.4,58.4},{37.4,58.6},{37.4,60.4},{37.6,57},{37.6,58.2},{37.6,59.2},{37.8,59.8},{40.4,39.6},{41.2,39.8},{41.2,51.4},{41.2,52},{41.4,41},{41.6,40.6},{41.8,40.2},{42.6,38.2},{67.2,47.6},{68,47.4},{68.2,47.8}}},nil,67,nil,nil,16,nil,"",0},
-- Quest 50066 Stormforged Scales: Dragonkin NPCs in The Storm Peaks (zone 67)
[29753] = {'Stormpeak Wyrm',12600,12600,80,80,0,{[67] = {{43.4,64.2},{43.8,64.6},{44,61.4},{44,64.4},{44.6,61},{45,58},{45.8,62.4},{45.8,62.6},{46.2,67.4},{46.2,67.6},{47,55},{47.4,57.4},{48.8,55.2},{49,60},{49.2,62.2},{50.8,71.2},{51.8,74.4},{52,74.6},{53,73.4}}},nil,67,nil,nil,16,nil,"",0},
[29755] = {'Stormpeak Hatchling',12600,12600,80,80,0,{[67] = {{45.2,66.4},{45.2,66.8},{45.6,67.2},{52,73.6},{52.2,72.4},{52.4,73.2},{52.4,75.2},{52.6,73.4},{52.6,73.6},{52.6,75.2}}},nil,67,nil,nil,16,nil,"",0},
[29460] = {'Frigid Proto-Drake',12600,12600,80,80,0,{[67] = {{53.6,66},{54.4,65.4},{54.8,65.2},{55.2,67},{55.2,67.6},{55.6,65.8},{56,64.2},{56,64.6},{56.8,64.4},{57,64.6},{57,65.6},{57.2,63.2},{57.8,63.8},{58.2,61.6},{58.4,63},{58.6,62.6},{58.8,62.2},{59.6,57.6},{59.6,58.6},{59.8,61.6},{60.2,60.8},{60.6,60.4},{60.8,60.8},{63.4,62.4},{64,61.8},{64.6,60.6}}},nil,67,nil,nil,16,nil,"",0},
[30275] = {'Wild Wyrm',12600,12600,80,80,0,{[67] = {{51.2,64.4},{53.2,66.6},{54,64.4},{54.2,66.6},{54.6,66.6},{55,65.8},{55.4,65.2},{56,64.6},{56,66.6},{56.2,66.4},{56.4,64.2},{56.6,63},{56.6,65.2},{56.6,65.6},{56.6,66.6},{57,64.4},{57.4,59.8},{58,63},{58.2,64},{58.4,57.8},{58.4,61.6},{59,57.6},{59,59.6},{59,61.8},{59.6,61.8},{59.8,61.4},{60,58},{60,60.4},{60.4,59.4},{60.6,58.4},{61,61},{61.4,58.6}}},nil,67,nil,nil,16,nil,"",0},
[30461] = {'Veranus',12600,12600,80,80,0,{[67] = {{38.8,65.4},{38.8,65.6}}},nil,67,nil,nil,16,nil,"",0},
-- Quest 50094 Wild Basin: Sholazar Basin (zone 3711) beasts
-- All hostile beasts scraped from Sholazar Basin filter page
[32485] = {'King Krush',12600,12600,75,75,0,{[3711] = {{26.0,73.0},{28.0,45.0},{30.0,38.0},{32.0,36.0},{37.0,28.0},{47.0,42.0},{50.0,43.0},{51.0,82.0},{52.0,85.0},{55.0,83.0},{57.0,80.0},{59.0,83.0},{64.0,79.0},{65.0,82.0},{67.0,78.0}}},nil,3711,nil,nil,39,nil,"",0},
[28009] = {'Shardhorn Rhino',12600,12600,75,76,0,{[3711] = {{22.0,51.0},{24.0,47.0},{26.0,46.0},{28.0,41.0},{30.0,39.0},{32.0,37.0},{35.0,43.0},{37.0,43.0},{40.0,43.0},{42.0,47.0},{44.0,50.0},{47.0,54.0},{49.0,59.0},{52.0,45.0}}},nil,3711,nil,nil,43,nil,"",0},
[32481] = {'Aotona',12600,12600,75,75,0,{[3711] = {{40.0,59.0},{42.0,67.0},{43.0,52.0},{45.0,68.0},{47.0,55.0},{51.0,72.0},{54.0,51.0},{57.0,65.0}}},nil,3711,nil,nil,2,nil,"",0},
[28097] = {'Pitch',12600,12600,76,76,0,{[3711] = {{50.0,76.0},{50.0,77.0},{51.0,76.0},{51.0,77.0}}},nil,3711,nil,nil,1,nil,"",0},
[28083] = {'Serfex the Reaver',12600,12600,78,78,0,{[3711] = {{50.0,85.0},{51.0,86.0},{52.0,86.0},{58.0,83.0},{59.0,85.0}}},nil,3711,nil,nil,6,nil,"",0},
[28001] = {'Dreadsaber',12600,12600,75,76,0,{[3711] = {{22.0,50.0},{24.0,47.0},{26.0,43.0},{28.0,41.0},{30.0,37.0},{32.0,37.0},{35.0,41.0},{37.0,41.0},{40.0,42.0},{42.0,50.0},{45.0,53.0},{47.0,48.0},{50.0,50.0}}},nil,3711,nil,nil,1,nil,"",0},
[28213] = {'Hardknuckle Matriarch',12600,12600,77,77,0,{[3711] = {{65.0,75.0},{66.0,73.0},{66.0,74.0},{67.0,72.0},{67.0,73.0},{68.0,72.0}}},nil,3711,nil,nil,2,nil,"",0},
[28297] = {'Shango',12600,12600,76,76,0,{[3711] = {{31.0,37.0},{32.0,35.0},{33.0,32.0},{34.0,31.0},{35.0,31.0}}},nil,3711,nil,nil,1,nil,"",0},
[28358] = {'Venomtip',12600,12600,77,77,0,{[3711] = {{53.0,50.0},{56.0,50.0},{57.0,51.0},{58.0,52.0},{59.0,53.0},{60.0,55.0}}},nil,3711,nil,nil,20,nil,"",0},
[28317] = {'Bushwhacker',12600,12600,76,76,0,{[3711] = {{46.0,63.0},{46.0,64.0},{47.0,63.0},{47.0,64.0}}},nil,3711,nil,nil,8,nil,"",0},
[28096] = {'Hardknuckle Charger',12600,12600,76,77,0,{[3711] = {{55.0,69.0},{57.0,66.0},{58.0,64.0},{60.0,67.0},{62.0,67.0},{64.0,70.0},{66.0,70.0},{68.0,70.0},{70.0,69.0},{72.0,70.0},{74.0,69.0}}},nil,3711,nil,nil,2,nil,"",0},
[28325] = {'Ravenous Mangal Crocolisk',12600,12600,76,80,0,{[3711] = {{54.0,64.0},{54.0,65.0},{55.0,63.0},{56.0,65.0},{57.0,62.0}}},nil,3711,nil,nil,6,nil,"",0},
[28288] = {'Farunn',12600,12600,76,76,0,{[3711] = {{42.0,38.0},{46.0,41.0},{47.0,41.0},{48.0,43.0},{49.0,44.0}}},nil,3711,nil,nil,6,nil,"",0},
[28399] = {'Zeptek the Destroyer',12600,12600,77,77,0,{[3711] = {{41.0,19.0},{41.0,20.0},{42.0,20.0}}},nil,3711,nil,nil,42,nil,"",0},
[29044] = {'Goretalon Matriarch',12600,12600,78,78,0,{[3711] = {{57.0,23.0},{59.0,23.0},{60.0,22.0},{60.0,23.0}}},nil,3711,nil,nil,2,nil,"",0},
[28086] = {'Sapphire Hive Wasp',12600,12600,76,77,0,{[3711] = {{43.0,78.0},{50.0,80.0},{51.0,83.0},{54.0,84.0},{56.0,80.0},{58.0,79.0},{60.0,75.0},{62.0,76.0}}},nil,3711,nil,nil,44,nil,"",0},
[28011] = {'Emperor Cobra',12600,12600,75,76,0,{[3711] = {{36.0,58.0},{40.0,52.0},{42.0,51.0},{45.0,51.0},{47.0,58.0},{50.0,48.0},{52.0,48.0},{55.0,48.0},{57.0,52.0},{59.0,56.0}}},nil,3711,nil,nil,35,nil,"",0},
[28085] = {'Sapphire Hive Drone',12600,12600,76,77,0,{[3711] = {{43.0,77.0},{48.0,79.0},{51.0,78.0},{54.0,75.0},{57.0,74.0},{60.0,70.0},{63.0,73.0},{65.0,76.0}}},nil,3711,nil,nil,44,nil,"",0},
[28380] = {'Shattertusk Bull',12600,12600,76,77,0,{[3711] = {{35.0,31.0},{37.0,27.0},{40.0,29.0},{43.0,29.0},{49.0,43.0},{52.0,41.0},{54.0,30.0},{57.0,35.0},{60.0,33.0},{62.0,33.0}}},nil,3711,nil,nil,21,nil,"",0},
[28847] = {'Siltslither Eel',12600,12600,1,77,0,{[3711] = {{47.0,62.0},{48.0,62.0},{48.0,64.0},{49.0,63.0}}},nil,3711,nil,nil,35,nil,"",0},
[29033] = {'Spirit of Atha',12600,12600,79,80,0,{[3711] = {{35.0,41.0},{37.0,35.0},{38.0,36.0},{40.0,42.0},{42.0,39.0},{44.0,42.0}}},nil,3711,nil,nil,46,nil,"",0},
[28010] = {'Stranded Thresher',12600,12600,75,76,0,{[3711] = {{46.0,62.0},{48.0,60.0},{50.0,62.0},{72.0,65.0},{73.0,64.0}}},nil,3711,nil,nil,35,nil,"",0},
[28002] = {'Mangal Crocolisk',12600,12600,75,76,0,{[3711] = {{20.0,59.0},{22.0,58.0},{25.0,58.0},{30.0,59.0},{35.0,58.0},{40.0,53.0},{45.0,55.0},{50.0,50.0},{53.0,68.0}}},nil,3711,nil,nil,6,nil,"",0},
[29034] = {'Spirit of Koosu',12600,12600,78,80,0,{[3711] = {{44.0,74.0},{48.0,68.0},{50.0,69.0},{56.0,85.0},{58.0,84.0},{60.0,85.0}}},nil,3711,nil,nil,46,nil,"",0},
[28129] = {'Longneck Grazer',12600,12600,75,76,0,{[3711] = {{21.0,61.0},{24.0,62.0},{27.0,63.0},{30.0,64.0},{33.0,64.0},{36.0,80.0},{40.0,86.0},{44.0,86.0},{48.0,86.0}}},nil,3711,nil,nil,21,nil,"",0},
[28004] = {'Goretalon Roc',12600,12600,75,76,0,{[3711] = {{54.0,23.0},{55.0,24.0},{56.0,23.0},{57.0,25.0},{58.0,26.0},{59.0,26.0},{60.0,27.0},{61.0,29.0}}},nil,3711,nil,nil,2,nil,"",0},
[28087] = {'Sapphire Hive Queen',12600,12600,78,78,0,{[3711] = {{57.0,79.0},{57.0,80.0},{58.0,80.0}}},nil,3711,nil,nil,44,nil,"",0},
[29018] = {'Spirit of Ha-Khalan',12600,12600,78,80,0,{[3711] = {{46.0,61.0},{48.0,60.0},{50.0,61.0},{51.0,63.0}}},nil,3711,nil,nil,46,nil,"",0},
[28003] = {'Bittertide Hydra',12600,12600,75,76,0,{[3711] = {{32.0,38.0},{35.0,36.0},{38.0,37.0},{41.0,37.0},{43.0,40.0},{45.0,43.0},{48.0,43.0}}},nil,3711,nil,nil,38,nil,"",0},
-- Requested WotLK Rare Spawns
[32422] = {'Grocklar',12600,12600,73,74,4,{[394] = {{9.6,39.0},{10.4,39.8},{10.6,40.6},{11.2,42.2},{11.2,70.6},{11.4,42.6},{11.4,54.0},{11.6,42.8},{12.0,57.0},{12.0,70.8},{12.2,44.4},{12.2,54.8},{12.4,49.8},{12.4,53.0},{12.4,54.4},{12.6,53.6},{12.8,45.6},{12.8,50.0},{12.8,70.8},{13.0,54.6},{13.0,70.4},{13.2,48.4},{13.2,49.0},{13.2,51.4},{13.6,48.2},{14.0,52.4},{14.0,54.8},{14.2,53.0},{14.2,53.6},{14.2,69.4},{14.4,70.2},{14.6,50.0},{14.6,50.6},{14.6,51.6},{15.4,69.8},{15.8,69.8},{16.6,70.2},{17.4,70.6},{17.8,69.2},{18.4,72.2},{18.6,68.8},{18.6,72.4},{19.8,72.2},{20.2,56.0},{21.0,60.8},{21.2,71.8},{21.4,57.2},{21.4,71.4},{21.6,57.6},{21.6,67.8},{21.8,56.8},{21.8,71.2},{22.0,72.0},{22.8,72.4},{23.0,57.4},{23.2,58.2},{23.8,56.2},{24.2,57.2},{24.4,54.8},{24.8,58.6},{24.8,61.6},{25.0,55.0},{25.0,56.4},{25.0,59.6},{25.2,56.8},{25.8,55.2}}},nil,394,nil,nil,35,nil,"",0},
[32429] = {'Seething Hate',12600,12600,73,73,4,{[394] = {{26.6,44.2},{28.0,45.4},{28.2,45.6},{28.8,46.4},{34.2,49.2},{34.2,49.6},{34.4,48.4},{34.6,48.8},{34.8,48.2},{38.4,49.0},{38.6,49.6},{38.8,49.2},{39.6,51.0},{40.2,49.2},{40.2,49.6}}},nil,394,nil,nil,35,nil,"",0},
[32438] = {'Syreian the Bonecarver',12600,12600,73,73,4,{[394] = {{61.2,35.0},{61.2,35.8},{61.6,35.6},{63.0,36.8},{64.2,35.8},{64.8,29.6},{65.2,31.2},{65.4,35.8},{65.6,29.6},{65.6,33.2},{65.6,35.8},{65.6,37.8},{65.8,34.2},{66.0,29.2},{66.2,40.8},{66.4,40.0},{66.6,41.4},{66.6,42.6},{66.8,36.8},{66.8,38.4},{66.8,41.6},{67.0,28.2},{67.6,27.0},{68.6,31.6},{68.8,30.4},{68.8,31.4},{69.0,32.8},{69.2,27.2},{69.4,28.8},{70.4,33.4},{70.4,33.8},{70.6,33.6},{71.4,35.0},{71.6,34.8},{72.4,36.6},{73.0,36.0},{73.4,37.6},{74.2,43.0},{74.4,35.8},{74.4,38.2},{74.6,39.0},{75.0,38.0},{75.0,41.6},{75.6,38.2},{75.8,41.0}}},nil,394,nil,nil,35,nil,"",0},
[38453] = {'Arcturis',12600,12600,74,74,4,{[394] = {{30.4,55.0},{31.0,55.4},{31.0,55.8},{32.8,55.2}}},nil,394,nil,nil,35,nil,"",0},
-- Quest 50203 Zul'Drak Trophy: Rare NPCs in Zul'Drak (zone 66)
[33776] = {'Gondria',12600,12600,77,77,4,{[66] = {{61.4,61.4},{61.4,62.4},{61.4,63},{62,63.2},{63,43.2},{63,43.6},{63.2,42.4},{63.6,42.8},{63.6,43.6},{66.8,77.8},{67.2,77.2},{67.6,79.2},{67.8,78.2},{68,77.4},{69.2,48},{69.6,48},{69.8,48.8},{77,70.4},{77.2,70.6},{77.6,70},{78.2,71.2}}},nil,66,nil,nil,35,nil,"",0},
[32471] = {'Griegen',12600,12600,75,75,4,{[66] = {{13.2,56.2},{13.4,54.8},{14.4,56},{14.4,56.8},{14.6,55.4},{14.6,56.2},{17.2,71},{17.2,71.8},{18,70.8},{20.6,71.2},{21,78.8},{21.2,79.6},{21.6,70.2},{22.4,60.8},{22.8,61.2},{22.8,62},{24.4,62},{25,75.4},{25.2,77.2},{26.2,71},{26.4,53.8},{26.4,55.2},{26.4,70.2},{26.6,55.6},{26.6,56.6},{26.6,70.2},{26.6,71.2},{26.8,54.4},{27,54.8}}},nil,66,nil,nil,16,nil,"",0},
[32475] = {'Terror Spinner',12600,12600,76,76,4,{[66] = {{52.4,31.6},{52.6,32.6},{53,32},{53.4,31.2},{60.6,37},{61.4,35.4},{61.4,36},{61.6,33.8},{61.6,35.8},{62.2,33.2},{70.8,28.8},{71.2,23},{71.2,24.2},{71.4,27.4},{71.6,23},{71.8,24},{71.8,25},{71.8,29.8},{72.4,27.6},{73.4,68.4},{73.6,66},{74.4,67.4},{74.8,66.4},{75.2,67},{76.4,42.8},{76.4,43.8},{76.6,68.8},{77.2,42.6},{81.4,34.2},{81.4,35},{81.6,34.6}}, [67] = {{72,75}}},nil,66,nil,nil,16,nil,"",0},
[32447] = {'Zul\'drak Sentinel',12600,12600,76,76,4,{[66]={{21.2,87.2},{22.2,82.4},{22.2,83},{22.4,83.6},{22.8,83},{23,82.2},{23.6,82.4},{24.2,87},{24.4,83},{25.2,82.8},{25.6,83.4},{26.4,82.2},{27,82.6},{28,82.6},{28.4,82.2},{28.6,73.8},{28.8,72.4},{28.8,82.8},{29,75},{29,77.6},{29,81.2},{29,81.8},{29.2,75.8},{29.4,79.2},{29.4,79.8},{29.6,82.4},{39.6,67.4},{40,51.8},{40,59.8},{40.4,53},{40.4,54.4},{40.4,55},{40.4,56.2},{40.4,56.6},{40.4,58},{40.4,59.4},{40.4,61},{40.4,61.6},{40.4,62.6},{40.8,52.6},{41.4,55.4},{41.4,69.8},{41.8,56},{42.2,70},{42.8,71},{43.2,54.6},{43.2,71.8},{43.6,72.6},{44.2,54.4},{44.2,55.2},{44.4,73.8},{44.6,67.6},{45,67},{45,74.4},{45.2,59.4},{45.2,74.8},{45.6,59.2},{45.6,59.6},{45.8,75.8},{46,60.6},{46,61.6},{46,64.4},{46,66},{46,66.6},{46.4,65.2},{46.4,77.2},{46.6,77.2},{46.8,61},{46.8,64.4},{46.8,65.8},{46.8,78},{47.2,79},{47.4,63.2},{48,79.6},{48.4,79.4},{49,81.2},{50.6,83},{50.8,84}}},nil,66,nil,nil,16,nil,"",0},
-- Quest 50153 Morogh Trophy: Rare NPCs in Dun Morogh (zone 1)
[1132] = {'Timber',12600,12600,10,10,4,{[1] = {{30.6,44.2},{31.8,38.0},{34.6,41.2},{37.2,40.2}}},nil,1,nil,nil,1,nil,"",0},
[1130] = {'Bjarn',12600,12600,12,12,4,{[1] = {{50.8,59.4},{55.4,55.4},{59.6,60.6},{65.0,59.6}}},nil,1,nil,nil,1,nil,"",0},
[8503] = {'Gibblewilt',12600,12600,10,10,4,{[1] = {{24.0,43.4},{25.0,44.6},{26.4,36.6},{28.6,35.6}}},nil,1,nil,nil,1,nil,"",0},
[1260] = {'Great Father Arctikus',12600,12600,10,10,4,{[1] = {{21.0,51.0},{23.0,52.4},{25.4,49.4},{29.6,48.4}}},nil,1,nil,nil,1,nil,"",0},
[1137] = {'Edan the Howler',12600,12600,10,10,4,{[1] = {{38.4,48.2},{40.8,45.4},{42.2,49.4},{43.6,49.6}}},nil,1,nil,nil,1,nil,"",0},
[1119] = {'Hammerspine',12600,12600,12,12,4,{[1] = {{71.4,51.4},{71.6,50.2},{72.2,53.6},{73.0,51.8}}},nil,1,nil,nil,1,nil,"",0},
-- Quest 50164 Tirisfal Trophy: Rare NPCs in Tirisfal Glades (zone 85)
[10357] = {'Ressan the Needler',12600,12600,11,11,4,{[85] = {{42,68.2},{42.2,69},{42.4,67.2},{42.6,68.8},{43,66.4},{43,67.8},{43.2,64.4},{43.4,67},{43.6,66.2},{43.6,67},{44,65},{44.2,68.2},{45,66.8},{45.4,64.4},{45.4,65.6},{45.8,62.6},{45.8,65.4},{45.8,66.8},{46.2,64.4},{46.4,65.8},{46.6,65.4},{46.6,66},{46.8,64.2},{48.4,65.6},{49.2,63.2},{49.2,65.2},{49.4,62.2},{49.4,64.4},{49.4,65.8},{49.4,66.6},{49.6,63.4},{49.6,64.6},{49.8,65.6},{49.8,66.6},{50.2,64.4},{51,66.2},{51.2,63.8},{51.4,62.2},{51.4,63.4},{51.4,65.2},{51.8,62.2},{51.8,66.2},{52.2,60},{52.2,63},{52.2,65},{52.4,61.2},{52.4,63.8},{52.6,62.2},{52.6,63.2},{52.6,63.8},{52.8,64.6},{53.4,67},{54.4,75.2},{54.8,67.4},{55.2,59.4},{55.2,59.8},{55.4,61},{55.4,61.8},{55.4,62.6},{55.6,59.4},{55.6,60},{55.6,61.8},{55.8,62.6},{56,61},{56.2,63.6},{56.6,60.2},{56.6,61.6},{56.8,61},{57,56.4},{57,59},{57.4,63},{58.2,62}}},nil,85,nil,nil,16,nil,"",0},
[10359] = {'Sr\'iskulk',12600,12600,10,10,4,{[85] = {{79.6,47},{81.8,51.6},{82.2,54.2},{83,54.4},{83,55.8},{83.4,53.2},{83.4,54.8},{83.6,54.6},{83.8,53},{83.8,54.4},{83.8,56},{84,48.2},{84.2,50},{84.4,49.2},{84.4,50.6},{84.4,52.2},{84.6,50.2},{85,55.4},{85.2,48.2},{85.2,49.4},{85.2,50.8},{85.6,50.4},{88,50},{88.2,50.6},{88.4,51.8},{88.4,52.6},{88.4,53.8},{88.6,50.4},{88.8,48.6},{88.8,51},{88.8,53.6},{89,45.4},{89,52.6},{89.2,47.6},{89.2,51.6},{89.4,40.2},{89.4,41.4},{89.4,42},{89.4,46},{89.4,47.4},{89.6,41.2},{89.6,41.6},{89.6,43.4},{89.6,46},{89.6,47},{89.8,49.6},{90,53.6},{90.2,47.6},{90.6,44}}},nil,85,nil,nil,16,nil,"",0},
[10356] = {'Bayne',12600,12600,10,10,4,{[85] = {{38,44.6},{39.2,41.2},{39.4,42},{39.4,42.6},{39.4,44},{40,40.4},{40,41.4},{40.2,43},{40.2,49.4},{40.2,53.4},{40.2,54.8},{40.4,42.4},{40.4,43.6},{40.6,42},{40.6,43},{40.8,41.4},{41,51},{41,54.6},{41.4,43.8},{41.4,52},{41.4,52.8},{41.4,53.8},{41.6,41.2},{41.6,53.6},{41.6,55},{41.8,42.4},{42,51.4},{42,52.6},{42.2,52.4},{42.6,53.8},{42.8,51.6},{42.8,55},{43,51.4},{43,52.6},{43.8,54.6},{44.2,52.8},{44.2,54},{44.8,50},{45,54},{45.2,51},{45.4,52},{45.4,52.8},{45.8,52.8},{46,51.4},{46.2,50},{46.2,51.6},{46.6,51.4},{46.6,52.4},{46.6,55},{46.6,56.2},{46.8,53.2},{46.8,54.4},{47,50.2},{47.6,51.8},{47.6,53.2},{48.4,49.2},{48.4,51.4},{49,50.8},{49,52.4},{49.4,50.2},{49.6,47.2},{49.8,49},{50,50.2},{50,50.8},{50.2,52.8},{50.4,52.4},{50.6,51},{51.2,50.4},{51.4,48.2},{51.4,48.8},{51.4,52.2},{52.6,53.4},{55.4,40.8},{55.6,39.4},{56.2,41.6},{56.4,40.4},{56.4,40.8},{56.6,41.6},{57,38.4},{57,39.8},{57.2,39.4},{57.2,41.2},{57.2,42.8},{57.6,41.2},{57.6,42},{58,39},{58.2,39.6},{58.6,39.8},{59,38.8},{59.2,47}}},nil,85,nil,nil,16,nil,"",0},
[1936] = {'Farmer Solliden',12600,12600,10,10,4,{[85] = {{34.2,50.2},{34.4,51.4},{34.4,52.2},{34.6,51.4},{34.6,52.2},{34.8,52.6},{35,50.4},{35.6,52},{36.4,49.4},{36.4,50},{36.8,49.6},{36.8,50.8},{37,53},{37.2,48.4},{37.2,52},{37.4,49},{37.6,49},{37.8,52.8},{38,51.4},{38,51.8},{38,53.6},{38.2,50},{38.6,50.8},{38.6,52},{38.8,49.4},{39,52.6},{39.8,54.4},{40,51.6}}},nil,85,nil,nil,16,nil,"",0},
[1531] = {'Lost Soul',12600,12600,10,10,4,{[85] = {{44.4,37.4},{44.4,37.6},{44.6,37.6},{44.8,37.2},{45,40.4},{45.8,43},{46,36.8},{46.4,40.4},{47,39.4},{47,40},{47.2,40.6},{47.4,35.4},{48,35},{48.4,36},{48.6,35.4},{48.6,36.2},{49,37.6},{49.4,37.4},{52.4,49},{53,45.2},{53.2,46.2},{53.2,46.6},{53.2,48.4},{53.2,48.6},{53.2,49.6},{53.6,45.4},{53.6,46.4},{53.6,46.6},{53.6,48.2},{53.6,49.2}}},nil,85,nil,nil,16,nil,"",0},
[1911] = {'Deeb',12600,12600,13,13,4,{[85] = {{57.2,28.2},{57.4,29.2},{57.6,28.2},{57.6,29},{58,25},{58,26.4},{58.2,26.6},{58.6,27.6},{58.6,28.6},{58.8,27.2},{59.6,27},{59.6,28.6},{60,30},{61.4,29.8},{62,28.4},{62.2,29.6},{62.4,26.6},{62.4,29},{62.6,28.6},{62.8,29.6},{63.2,27.4},{63.2,27.6},{63.2,31},{63.6,28},{63.8,26.8},{63.8,29.4}}},nil,85,nil,nil,16,nil,"",0},
[1910] = {'Muad',12600,12600,12,12,4,{[85] = {{33.4,42.8},{33.4,43.8},{33.6,43},{34,43.8},{34.2,41.6},{34.2,44.6},{34.4,41.2},{34.6,41},{34.6,42},{34.8,44.2},{34.8,44.8},{34.8,45.8},{35,39.8},{35.2,43.4},{35.8,46.4},{36.2,38},{36.2,42.4},{36.2,44.2},{36.2,44.6},{36.4,39.4},{36.4,40.2},{36.4,41.2},{36.4,42.8},{36.6,38},{36.6,42},{36.6,43.4},{36.6,43.6},{36.8,39},{36.8,40},{36.8,40.8},{36.8,44.6},{37.6,39.2},{37.6,40.4},{37.6,41.2},{37.6,41.6},{37.8,43.4}}},nil,85,nil,nil,16,nil,"",0},
[10358] = {'Fellicent\'s Shade',12600,12600,11,11,4,{[85] = {{71.4,61.6},{73.4,60},{74.2,60.4},{74.2,63.2},{74.4,59.4},{74.4,60.8},{74.4,61.6},{74.6,63.8},{74.8,60.6},{75,59.4},{75,59.8},{75.2,58.4},{75.2,61.8},{75.4,63},{75.6,59.4},{75.8,58.4},{75.8,63.6},{76.2,59.6},{76.4,57.4},{76.4,61.4},{76.4,61.8},{76.4,62.6},{76.6,59.4},{76.6,61.8},{76.6,62.6},{76.8,60.2},{76.8,61.2},{77,64.2},{77.6,60.8},{77.6,61.6},{77.8,63.2},{78.2,65.4}}},nil,85,nil,nil,16,nil,"",0},
-- Quest 50176 Arathi Trophy: Rare NPCs in Arathi Highlands (zone 45)
[2598] = {'Darbel Montrose',12600,12600,29,30,4,{[45] = {{25.2,66.2},{25.8,59.2},{25.8,66.2},{26.4,62.4},{27,65.2},{27.2,66},{27.4,62.8},{27.4,63.8},{27.6,61},{27.6,63.6},{27.6,66.2},{27.8,60.2},{27.8,62.4},{28,62.8},{28.4,64.8},{28.6,61.8},{28.6,64},{28.6,64.8},{29.2,60.2},{29.4,60.8},{29.6,60.6},{29.8,60.2},{29.8,62.4},{29.8,62.8},{29.8,64}}},nil,45,nil,nil,16,nil,"",0},
[2600] = {'Singer',12600,12600,28,29,4,{[45] = {{30.2,26.2},{30.2,28.8},{31,27.8},{31.2,26.2},{31.2,26.6},{31.8,31.8},{32,28},{32.4,26.4},{32.4,26.6},{32.4,29.6},{32.4,30.8},{32.6,30.2},{32.6,31.2},{33.4,26.2},{33.4,27.4},{33.4,27.6},{33.4,29.4},{33.4,31.6},{33.6,27.4},{33.6,27.8},{33.6,30.2},{34.6,32},{34.8,31.4},{35,30},{35.4,35.2},{36.2,28.4}}},nil,45,nil,nil,16,nil,"",0},
[2601] = {'Foulbelly',12600,12600,28,29,4,{[45] = {{18,66.2},{18.8,66},{18.8,67.6},{19.4,64.4},{19.4,65.2},{19.8,65.2},{19.8,65.6},{20,64.4},{20,66.6},{20.2,67.6},{20.6,67.4},{21.4,66.2},{21.6,65.4},{21.6,66.8}}},nil,45,nil,nil,16,nil,"",0},
[2602] = {'Ruul Onestone',12600,12600,31,31,4,{[45] = {{17.4,68.4},{17.4,68.6},{18.2,68.4},{18.2,68.6}}},nil,45,nil,nil,16,nil,"",0},
[2603] = {'Kovork',12600,12600,31,31,4,{[45] = {{31.4,45.8},{31.6,45.8},{32.6,46}}},nil,45,nil,nil,16,nil,"",0},
[2604] = {'Molok the Crusher',12600,12600,31,31,4,{[45] = {{53.4,79.4},{53.4,79.8},{53.6,79.8},{53.8,79.4},{54,77.6},{54.4,77.4},{54.6,78.2},{54.6,79},{54.8,80.2}}},nil,45,nil,nil,16,nil,"",0},
[2605] = {'Zalas Witherbark',12600,12600,31,32,4,{[45] = {{66,82},{67.4,80.4},{67.4,81.2},{67.4,81.6},{67.6,80.2},{67.6,80.6},{68.2,82},{68.6,80.8},{69,82.6},{69.2,79.4},{69.2,79.6},{69.2,81.8},{69.6,79.6},{69.6,81.2},{69.6,82.2},{69.8,79}}},nil,45,nil,nil,16,nil,"",0},
[2606] = {'Nimar the Slayer',12600,12600,28,29,4,{[45] = {{60.2,71.8},{60.2,73.2},{60.4,73.8},{60.6,75.2},{61,73.6},{61.2,71.8},{61.2,72.6},{61.4,71.2},{61.6,74},{61.8,72.6},{62.4,71.4},{62.4,71.6},{62.6,67.6},{62.6,71.8},{63,68.6},{63,74},{63.2,66.2},{63.4,67.2},{63.6,67.4},{63.8,63.6},{63.8,64.8},{63.8,67.8},{63.8,69},{64.2,73},{64.2,74.4},{64.4,63.4},{64.4,66.2},{64.4,74.6},{64.6,66.2},{64.6,69},{64.6,74.6},{64.8,63},{65,73.4},{65,73.8},{65.2,61.2},{65.4,56.6},{65.6,57.6},{65.8,62.2},{65.8,74.8},{66,60.2},{66.2,64},{66.4,62.6},{66.4,64.8},{66.6,62.4},{66.6,62.6},{67,63.8},{67.2,61.2},{67.6,62.4},{68.4,59.8},{68.6,61.2},{69.4,54.6},{69.8,52.8},{70.2,64},{70.2,66.2},{71,59},{71.2,62.6},{71.2,63.6},{71.2,64.8},{71.2,66.6},{71.4,65.8},{71.8,63},{72,65.8},{72,66.6},{72.2,61.2},{72.4,64.2},{72.4,64.8},{72.6,65.8},{72.8,64.2},{72.8,65},{73.2,67},{73.6,63.8}}},nil,45,nil,nil,16,nil,"",0},
[2609] = {'Geomancer Flintdagger',12600,12600,28,29,4,{[45] = {{82.8,32.2},{82.8,32.6},{83.6,31.4},{83.6,34.6},{84.2,32.8},{84.4,28.4},{84.4,28.6},{84.4,32.2},{84.6,28.4},{84.6,28.6},{84.8,29.6},{85.2,31.4},{85.4,31.6},{85.6,31.4},{85.6,31.6}}},nil,45,nil,nil,16,nil,"",0},
[2779] = {'Prince Nazjak',12600,12600,32,32,4,{[45] = {{19.8,88.8},{20.2,82.4},{20.6,83.4},{20.8,84},{21.2,84.6},{21.2,89.2},{21.2,89.6},{21.6,82.8},{21.6,86},{21.6,89},{21.8,83.6},{21.8,84.8},{22.4,89.6},{22.8,84.4},{23,84.8},{23,86.2},{23,88},{23.2,82.2},{23.2,83.2},{23.4,89},{23.6,83.8},{23.6,89.4},{23.6,89.8},{24,88.4}}},nil,45,nil,nil,16,nil,"",0},
-- Quest 50195 Blade's Edge Trophy: Rare NPCs in Blade's Edge Mountains (zone 3522)
[18690] = {'Morcrush',12600,12600,69,69,4,{[3522] = {{59.2,23},{59.2,24.4},{59.4,20.2},{59.4,25.2},{59.6,20.4},{59.6,24.4},{59.6,25.4},{60,20.6},{60.2,23.2},{60.8,24.8},{61,20.8},{61,23},{61.2,23.8},{61.4,18.8},{61.4,21.8},{61.4,54.6},{61.8,21},{61.8,22.6},{61.8,54.4},{62,19.4},{62,19.6},{62.6,51},{62.6,52.4},{62.6,53.2},{63.8,51},{65,49.2},{65,53.4},{65.2,47.2},{65.2,50.4},{66.6,65},{66.8,48.4},{67,49},{67.2,68.2},{67.4,46.8},{67.4,66.2},{67.4,70.2},{67.6,47.2},{67.6,48.2},{67.6,65.6},{67.8,64.4},{67.8,65.4},{67.8,70.2},{67.8,73.4},{68,33.6},{68,67.4},{68,74.4},{68.2,67.6},{68.2,68.6},{68.2,75.6},{68.4,32.4},{68.4,33.2},{68.4,71.6},{68.6,64.4},{68.6,65.8},{68.6,68},{68.6,71.8},{68.6,74.8},{68.8,31.8},{68.8,64.6},{68.8,73},{69,29.8},{69.2,45},{69.4,33.8},{69.4,73.8},{69.6,32.8},{69.6,45},{70,28.6},{70,44.2},{70.2,34.2},{70.8,41.8},{71,41.4},{71.2,29.4},{71.6,30.2},{71.6,33.4},{73.2,29.8},{73.4,29.2},{73.6,24.6},{74,23.8},{74,29},{74.2,27.4},{74.4,23.4},{74.8,23.4},{76.8,23.8},{77.8,24.4},{77.8,28.8},{77.8,30},{78.4,26.8},{78.4,28}}},nil,3522,nil,nil,16,nil,"",0},
[18692] = {'Hemathion',12600,12600,68,69,4,{[3522] = {{27.4,54.4},{27.6,72},{27.8,67.6},{28,65.6},{28.2,65},{28.4,63.2},{28.4,67.2},{28.4,69.8},{28.6,64.4},{29,43.8},{29.2,44.6},{29.2,49.6},{29.2,50.8},{29.4,47.6},{29.4,49.4},{29.4,68},{29.4,68.6},{29.4,70.2},{29.4,70.8},{29.6,50.4},{29.6,63.6},{29.6,66.6},{29.6,68.2},{29.6,70.2},{29.6,71},{29.8,43.6},{29.8,51.2},{29.8,68.6},{30,43.2},{30,44.8},{30,48.6},{30,71.6},{30.2,45.8},{30.2,48.2},{30.2,53.6},{30.2,63.4},{30.2,64.8},{30.4,46.6},{30.4,51.6},{30.4,66.2},{30.6,44.6},{30.6,46.8},{30.6,51.4},{30.6,67},{30.6,68.8},{30.6,72.4},{30.6,72.6},{30.8,44},{30.8,68.2},{30.8,70.8},{31,45.6},{31.2,50.4},{31.2,52},{31.2,70},{31.4,52.6},{31.4,55.6},{31.6,55},{31.6,55.8},{32,53},{32.2,51.4},{32.2,52},{32.2,53.8},{32.6,52.6},{32.8,51.6},{32.8,54.6}}},nil,3522,nil,nil,16,nil,"",0},
[18693] = {'Speaker Mar\'grom',12600,12600,68,68,4,{[3522] = {{39.8,56},{40.2,56.6},{40.8,55.8},{41,49.2},{41,55},{41.2,49.6},{41.2,50.6},{41.4,48},{41.6,54},{41.8,51},{41.8,53.2},{42,55.6},{42.2,82},{42.4,50},{42.6,49.6},{42.8,49.2},{42.8,81.2},{43.4,80.4},{43.6,80.8},{43.8,79},{44.4,78},{44.6,75.4},{45,76.8},{45.8,76.8},{45.8,79.6},{46.2,78.8},{46.4,78.2},{46.6,78.2},{46.6,79.6},{46.8,76.2},{47,75.4},{47,76.8},{55.4,36.2},{55.8,25.4},{55.8,36},{56,26.4},{56.2,24.4},{56.2,26.8},{56.2,34.8},{56.6,27.4},{56.6,33.8},{57.2,28.2},{57.4,29.4},{57.4,33.4},{57.6,30},{57.6,30.6},{57.6,33.4},{57.8,32},{64.2,19.2},{64.4,19.6},{65,20.2},{65.2,21.2},{65.2,21.6},{65.6,22.4},{66,23.4},{66.2,23.6},{66.2,24.8},{66.2,26.2},{66.4,26.6},{67,26.8},{67.6,24.2}}},nil,3522,nil,nil,16,nil,"",0},
-- Quest 50196 Netherstorm Trophy: Rare NPCs in Netherstorm (zone 3523)
[20932] = {'Nuramoc',13972,13972,70,70,4,{[3523] = {{22.6,80},{22.8,78.8},{24,79.2},{24,81.8},{24.6,79.6},{25.8,81.2},{26.6,82.4},{26.6,83.6},{28.6,80.4},{29.4,82.6},{29.8,82.4},{30,80.8},{30.4,79.6},{31.2,78.4},{31.8,79.2},{32,81.8},{32.8,28.8},{33.4,28.2},{33.4,31.4},{33.4,32.4},{33.6,25.4},{33.6,25.6},{34,27.6},{34.2,20.8},{34.4,22.2},{34.4,27.2},{34.4,29},{34.4,78},{34.6,27.8},{34.6,28.8},{34.6,33.4},{34.6,78.6},{34.8,30.8},{34.8,78.4},{35,26.2},{35,29.6},{35.2,34.2},{35.4,23.4},{35.4,23.8},{35.4,25.2},{35.4,31.8},{35.6,32.2},{35.8,20.4},{35.8,24.6},{36,21.2},{36,21.6},{36,23.8},{36.2,34.8},{36.4,22.8},{36.6,78.6},{37.8,78.6},{40.2,76.4},{42.2,76},{42.6,75.6},{43.8,69.4},{43.8,69.6},{43.8,71.8},{43.8,74.4},{43.8,78},{44,70.6},{44.8,72.6},{45.4,70.8},{45.4,72.2},{45.8,73.2},{46,71.2},{52.6,59.6},{53,59},{53.2,55.6},{54.2,57.6},{54.6,60},{55,61.2},{55.6,61},{56,58.6},{56,59.8},{56.6,59.2},{57.4,57.4},{58.4,60},{58.4,60.6},{58.6,59.6},{59.8,58.2},{60,60.6},{61,58.6},{61.4,60},{61.6,58.6},{61.6,59.6},{62.2,61},{62.6,58.4},{63.2,60.6},{63.4,59.6},{63.6,59.6},{64,61.6},{64.2,58.8},{64.8,59},{65.2,58.2},{65.4,62.4},{65.6,58.2},{66,62.2},{66.4,58.6},{66.6,62.4},{66.8,59.2},{67,60},{67.6,60.8},{67.6,61.6}}},nil,3523,nil,nil,16,nil,"",0},
[18698] = {'Ever-Core the Punisher',12600,12600,68,69,4,{[3523] = {{19.8,69.4},{19.8,70},{20,67.4},{20.4,68},{20.4,73.4},{20.6,70},{21,75.2},{21.2,66},{21.2,76},{21.6,65.4},{21.6,77},{23.2,64.8},{23.2,75},{23.2,76.2},{23.4,39.2},{23.6,38.4},{24,37.2},{24,40.2},{24,41.4},{24.4,41.6},{24.4,64.6},{24.4,75.8},{24.8,65.4},{25.2,42.2},{25.2,42.6},{25.8,42.2},{26,65.4},{26.2,72.4},{26.2,73.2},{26.4,42.8},{26.6,72.8},{27,42.6},{27,69.4},{27.2,42},{27.2,70},{27.4,66.8},{27.6,67.4},{27.8,41.8},{28,72.4},{28.2,65.2},{28.4,41},{28.4,71.2},{28.8,44.2},{28.8,44.8},{29.2,43},{29.4,40.2},{29.4,40.8},{29.6,39.4},{29.6,39.8},{29.6,40.8},{29.6,43.8},{30,42.8},{30.4,42},{30.6,42},{30.8,42.6},{31,41},{57.4,41.6},{57.6,38.8},{57.6,43.2},{57.6,44},{58,38.4},{58.8,45.2},{59.4,34},{59.6,34.4},{59.6,45.6},{59.8,34.8},{60,33},{60.6,32.6},{61.4,46.8},{61.6,32.2},{61.8,47.6},{62,47},{63.4,32},{64.2,32},{64.2,48.2},{64.6,32},{65,33},{65.6,33.8},{65.8,46.6},{66,46.2},{66.4,45.2},{66.6,33.8},{66.8,34.8},{66.8,35.8},{67,38},{67.4,38.6},{67.8,44.8},{68,40.4},{68,40.6},{68,41.6},{68.2,43.8},{68.4,43}}},nil,3523,nil,nil,16,nil,"",0},
[18697] = {'Chief Engineer Lorthander',12600,12600,68,69,4,{[3523] = {{26,40.8},{26.4,40},{26.4,42.4},{26.4,42.6},{26.6,40.8},{26.8,36.2},{26.8,38.8},{26.8,41.8},{27,37},{27.4,38.4},{27.8,39.4},{46.2,80.6},{46.4,79.8},{46.4,82},{46.4,83.2},{46.6,82.2},{46.6,83.2},{46.8,80.2},{47.4,81},{47.4,84.6},{47.6,84.4},{48.2,81.6},{48.4,81.4},{48.4,82.6},{48.8,81.2},{49.2,82},{58.2,63},{58.2,64.6},{58.4,63.6},{58.6,63.8},{59,62.6},{59.2,62.4},{59.2,67},{59.6,67.4},{59.8,65.8},{60.4,63.2},{60.6,65.8},{61.2,63.4},{61.2,63.6},{61.4,65.2},{61.6,64.6}}},nil,3523,nil,nil,16,nil,"",0},
[32398] = { "King Ping", nil, nil, 71, 71, 2, { [495] = { { 25.6, 69.2 }, { 26.0, 63.8 }, { 26.6, 63.2 }, { 30.4, 71.4 }, { 30.8, 56.4 }, { 30.8, 70.8 }, { 31.0, 71.6 }, { 31.2, 56.6 }, { 31.2, 57.6 }, { 31.6, 77.4 }, { 31.8, 75.4 }, { 32.2, 75.8 }, { 32.6, 78.8 }, { 33.0, 80.2 }, { 33.4, 80.6 }, { 33.6, 80.2 }, { 33.6, 81.0 } } }, nil, 495, nil, nil, 12 },
[32377] = { "Perobas the Bloodthirster", nil, nil, 71, 71, 2, { [495] = { { 47.8, 5.8 }, { 50.0, 4.6 }, { 50.4, 4.4 }, { 50.6, 4.2 }, { 52.4, 10.8 }, { 53.0, 11.8 }, { 53.2, 12.6 }, { 53.8, 11.8 }, { 60.0, 19.8 }, { 60.6, 21.0 }, { 60.8, 22.0 }, { 61.0, 19.8 }, { 68.2, 17.8 }, { 68.4, 17.2 }, { 68.6, 16.8 }, { 69.4, 14.2 }, { 71.2, 13.2 }, { 71.2, 14.6 }, { 71.4, 14.0 }, { 71.6, 14.4 }, { 72.2, 13.4 } } }, nil, 495, nil, nil, 12 },
[32386] = { "Vigdis the War Maiden", nil, nil, 71, 71, 2, { [495] = { { 68.4, 46.0 }, { 68.4, 58.4 }, { 68.6, 48.6 }, { 68.8, 47.8 }, { 68.8, 59.0 }, { 70.0, 50.4 }, { 70.0, 56.8 }, { 70.0, 57.6 }, { 70.4, 49.2 }, { 70.6, 50.2 }, { 71.0, 52.2 }, { 71.0, 56.8 }, { 71.2, 55.4 }, { 71.2, 55.6 }, { 71.6, 44.4 }, { 71.6, 45.6 }, { 71.8, 47.0 }, { 72.2, 42.8 }, { 72.2, 48.6 }, { 72.2, 52.6 }, { 72.4, 44.8 }, { 72.4, 50.6 }, { 72.6, 46.0 }, { 72.6, 52.4 }, { 72.6, 61.2 }, { 72.8, 39.2 }, { 72.8, 41.0 }, { 72.8, 44.4 }, { 72.8, 49.0 }, { 72.8, 51.4 }, { 72.8, 61.8 }, { 73.0, 40.2 }, { 73.0, 48.0 }, { 73.2, 44.6 }, { 73.2, 53.2 }, { 73.6, 45.4 }, { 73.8, 49.6 }, { 73.8, 61.4 }, { 74.0, 52.6 }, { 74.2, 45.8 }, { 74.2, 47.8 }, { 74.2, 59.2 }, { 74.4, 54.6 }, { 74.4, 60.2 }, { 74.6, 50.0 }, { 74.6, 51.4 }, { 74.6, 54.8 }, { 74.8, 45.2 }, { 74.8, 56.6 }, { 74.8, 58.4 }, { 75.0, 48.2 }, { 75.0, 52.6 }, { 75.6, 45.2 }, { 76.2, 42.8 } } }, nil, 495, nil, nil, 12 },
}
-- Backward compatibility
+375 -45
View File
@@ -302,7 +302,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50031: Stormbound
-- Kill 30 Elementals in Storm Peaks
[50031] = {
[1] = "Stormbound", -- Quest name
[1] = "Stormbound", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Required level (Min level)
[5] = 73, -- Quest level
@@ -392,7 +392,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50095: Peak Predators
-- Kill 75 beasts in The Storm Peaks
[50095] = {
[1] = "Peak Predators", -- Quest name
[1] = "Peak Predators", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Required level (Min level)
[5] = 73, -- Quest level
@@ -422,7 +422,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50096: Peak Predators
-- Kill 75 beasts in Icecrown
[50096] = {
[1] = "Peak Predators", -- Quest name
[1] = "Peak Predators", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 73, -- Required level (Min level)
[5] = 77, -- Quest level
@@ -469,10 +469,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Kill 1 rare in The Storm Peaks
[50205] = {
[1] = "Storm Peaks Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 80, -- Quest level
[17] = 67, -- Zone (The Storm Peaks)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 80, -- Quest level
[17] = 67, -- Zone (The Storm Peaks)
[8] = {
"Kill a rare NPC in The Storm Peaks to complete this quest.",
"Kill a rare in The Storm Peaks"
@@ -499,10 +499,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Kill 30 dragonkin in The Storm Peaks
[50066] = {
[1] = "Stormforged Scales", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 80, -- Quest level
[17] = 67, -- Zone (The Storm Peaks)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 80, -- Quest level
[17] = 67, -- Zone (The Storm Peaks)
[8] = {
"The dragons of the Storm Peaks must be dealt with before we can proceed with our plans. Destroy 30 dragonkin in the Storm Peaks to show the Storm Peaks dragons that they are not welcome here.",
"Slain: 0/30"
@@ -545,7 +545,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Requires completing any 6 quests in Dun Morogh
-- Auto-complete, no turn-in location
[50098] = {
[1] = "Morogh Missions", -- Quest name
[1] = "Morogh Missions", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 1, -- Min level
[5] = 60, -- Quest level
@@ -561,7 +561,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Requires completing any 6 quests in Azuremyst Isle
-- Auto-complete, no turn-in location
[50100] = {
[1] = "Azuremyst Aid", -- Quest name
[1] = "Azuremyst Aid", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 1, -- Min level
[5] = 60, -- Quest level
@@ -576,7 +576,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50094: Wild Basin
-- Kill 75 beasts in Sholazar Basin
[50094] = {
[1] = "Wild Basin", -- Quest name
[1] = "Wild Basin", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Required level (Min level)
[5] = 73, -- Quest level
@@ -606,7 +606,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50202: Grizzly Trophy
-- Kill 1 rare in Grizzly Hills
[50202] = {
[1] = "Grizzly Trophy", -- Quest name
[1] = "Grizzly Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 74, -- Quest level
[5] = 71, -- Min level
@@ -624,7 +624,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
{
-- 4 rare NPCs in Grizzly Hills
{ 38453, 32429, 32422, 32438 }, -- Arcturis, Seething Hate, Grocklar, Syreian the Bonecarver
38453, -- RootId: Arcturis (representative rare)
38453, -- RootId: Arcturis (representative rare)
"Rare slain"
}
}
@@ -636,7 +636,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50203: Zul'Drak Trophy
-- Kill 1 rare in Zul'Drak
[50203] = {
[1] = "Zul'Drak Trophy", -- Quest name
[1] = "Zul'Drak Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 77, -- Quest level
[5] = 74, -- Min level
@@ -654,7 +654,7 @@ EbonholdDB.questData = EbonholdDB.questData or {
{
-- 4 rare NPCs in Zul'Drak
{ 33776, 32471, 32475, 32447 }, -- Gondria, Griegen, Terror Spinner, Zul'drak Sentinel
33776, -- RootId: Gondria (representative rare)
33776, -- RootId: Gondria (representative rare)
"Rare slain"
}
}
@@ -711,10 +711,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Complete 6 quests in Teldrassil (Any Quest)
[50099] = {
[1] = "Shadow of Teldrassil", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 1, -- Min level
[5] = 60, -- Quest level
[17] = 141, -- Zone (Teldrassil)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 1, -- Min level
[5] = 60, -- Quest level
[17] = 141, -- Zone (Teldrassil)
[8] = {
"This quest requires completing any 6 quests in Teldrassil. The quest will automatically complete once the objective is met.",
"Complete 6 quests in Teldrassil"
@@ -741,10 +741,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Complete 6 quests in Bloodmyst Isle (Any Quest)
[50104] = {
[1] = "Bloodmyst Recovery", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 10, -- Min level
[5] = 1, -- Quest level
[17] = 3525, -- Zone (Bloodmyst Isle)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 10, -- Min level
[5] = 1, -- Quest level
[17] = 3525, -- Zone (Bloodmyst Isle)
[8] = {
"This quest requires completing any 6 quests in Bloodmyst Isle. The quest will automatically complete once the objective is met.",
"Complete 6 quests in Bloodmyst Isle"
@@ -981,8 +981,8 @@ EbonholdDB.questData = EbonholdDB.questData or {
{ -- [5] killCreditObjective
{
-- 10 rare NPCs in Arathi Highlands
{ 2598, 2603, 2600, 2604, 2606, 2779, 2605, 2609, 2602, 2601 },
2606, -- RootId: Nimar the Slayer (representative rare)
{ 2598, 2603, 2600, 2604, 2606, 2779, 2605, 2609, 2602, 2601 },
2606, -- RootId: Nimar the Slayer (representative rare)
"Rare slain"
}
}
@@ -1023,8 +1023,8 @@ EbonholdDB.questData = EbonholdDB.questData or {
{ -- [5] killCreditObjective
{
-- 3 rare NPCs in Blade's Edge Mountains
{ 18690, 18692, 18693 },
18692, -- RootId: Hemathion (representative rare)
{ 18690, 18692, 18693 },
18692, -- RootId: Hemathion (representative rare)
"Rare slain"
}
}
@@ -1037,10 +1037,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Kill 40 demons in Nagrand
[50044] = {
[1] = "Fel Scars of Nagrand", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 64, -- Min level
[5] = 67, -- Quest level
[17] = 3518, -- Zone (Nagrand)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 64, -- Min level
[5] = 67, -- Quest level
[17] = 3518, -- Zone (Nagrand)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 40 Demons in Nagrand"
@@ -1053,8 +1053,8 @@ EbonholdDB.questData = EbonholdDB.questData or {
{ -- [5] killCreditObjective
{
-- 14 demon NPCs in Nagrand
{ 18683, 17151, 17981, 22357, 18567, 17152, 18535, 18661, 22362, 22394, 18536, 18401, 18660, 16945 },
18683, -- RootId: Voidwalker Minion (representative demon)
{ 18683, 17151, 17981, 22357, 18567, 17152, 18535, 18661, 22362, 22394, 18536, 18401, 18660, 16945 },
18683, -- RootId: Voidwalker Minion (representative demon)
"Demon slain"
}
}
@@ -1067,10 +1067,10 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Kill 1 rare in Netherstorm
[50196] = {
[1] = "Netherstorm Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 67, -- Min level
[5] = 70, -- Quest level
[17] = 3523, -- Zone (Netherstorm)
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 67, -- Min level
[5] = 70, -- Quest level
[17] = 3523, -- Zone (Netherstorm)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Netherstorm"
@@ -1096,11 +1096,11 @@ EbonholdDB.questData = EbonholdDB.questData or {
-- Quest 50199: Borean Trophy
-- Kill 1 rare in Borean Tundra
[50199] = {
[1] = "Borean Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 71, -- Quest level
[17] = 3537, -- Zone (Borean Tundra)
[1] = "Borean Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 71, -- Quest level
[17] = 3537, -- Zone (Borean Tundra)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Borean Tundra"
@@ -1123,6 +1123,336 @@ EbonholdDB.questData = EbonholdDB.questData or {
[30] = 1, -- Kill count
},
-- Quest 50034: Ashen Corruption
-- Kill 40 demons in Ashenvale
[50034] = {
[1] = "Ashen Corruption", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 20, -- Min level
[5] = 25, -- Quest level
[17] = 331, -- Zone (Ashenvale)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 40 Demons in Ashenvale"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 23 demon NPCs in Ashenvale
{ 6072, 3772, 3758, 17300, 6073, 3762, 3774, 11697, 3765, 3771, 3767, 3763, 4619, 6071, 3757, 3770, 3755, 3752, 6115, 3759, 10928, 3754, 10647 },
6072, -- RootId: Diathorus the Seeker (representative demon)
"Demon slain"
}
}
},
[30] = 40, -- Kill count
},
-- Quest 50197: Shadowmoon Trophy
-- Kill 1 rare in Shadowmoon Valley
[50197] = {
[1] = "Shadowmoon Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 67, -- Min level
[5] = 70, -- Quest level
[17] = 3520, -- Zone (Shadowmoon Valley)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Shadowmoon Valley"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 3 rare NPCs in Shadowmoon Valley
{ 18694, 18695, 18696 }, -- Collidus the Warp-Watcher, Ambassador Jerrikar, Kraator
18694, -- RootId: Collidus the Warp-Watcher (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
-- Quest 50020: Spires of Chaos
-- Kill 30 elementals in Thousand Needles
[50020] = {
[1] = "Spires of Chaos", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 25, -- Min level
[5] = 30, -- Quest level
[17] = 400, -- Zone (Thousand Needles)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 30 Elementals in Thousand Needles"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 5 elemental NPCs in Thousand Needles
{ 4499, 4120, 10756, 9377, 10757 },
4120, -- RootId: Thundering Boulderkin (representative elemental)
"Elemental slain"
}
}
},
[30] = 30, -- Kill count
},
-- Quest 50173: Northern Jungle Trophy
-- Kill 1 rare in Stranglethorn Vale (Northern)
[50173] = {
[1] = "Northern Jungle Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 25, -- Min level
[5] = 45, -- Quest level
[17] = 33, -- Zone (Stranglethorn Vale)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Stranglethorn Vale"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 9 rare NPCs in Stranglethorn Vale
{ 1552, 14488, 9046, 14492, 14487, 14491, 14490, 11383, 2541 },
1552, -- RootId: Scale Belly (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
-- Quest 50175: Alterac Trophy
-- Kill 1 rare in Alterac Mountains
[50175] = {
[1] = "Alterac Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 25, -- Min level
[5] = 40, -- Quest level
[17] = 36, -- Zone (Alterac Mountains)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Alterac Mountains"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 8 rare NPCs in Alterac Mountains
{ 2447, 14223, 14221, 14222, 2453, 2258, 14281, 2452 },
2447, -- RootId: Narillasanz (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
-- Quest 50089: Tundra Hunters
-- Kill 75 beasts in Borean Tundra
[50089] = {
[1] = "Tundra Hunters", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 73, -- Quest level
[17] = 68, -- Zone (Borean Tundra)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 75 Beasts in Borean Tundra"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 43 Beasts in Borean Tundra
{ 25454, 32361, 25488, 25968, 25675, 25487, 25203, 25829, 25680, 25489, 25481, 25791, 25482, 24614, 25295, 25774, 25743, 18375, 26174, 25498, 26160, 24637, 24613, 18379, 25862, 25850, 25204, 25596, 25750, 18378, 25464, 27074, 25881, 25817, 24765, 27365, 26047, 25606, 25988, 18376, 18380, 25362, 25990 },
25454, -- RootId: Tundra Crawler (representative)
"Beast slain"
}
}
},
[30] = 75, -- Kill count
},
-- Quest 50183: Southern Jungle Trophy
-- Kill 1 rare in Stranglethorn Vale (Southern)
[50183] = {
[1] = "Southern Jungle Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 25, -- Min level
[5] = 45, -- Quest level
[17] = 33, -- Zone (Stranglethorn Vale)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Stranglethorn Vale"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 9 rare NPCs in Stranglethorn Vale
{ 1552, 14488, 9046, 14487, 14492, 14490, 14491, 2541, 11383 },
1552, -- RootId: Scale Belly (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
-- Quest 50200: Fjord Trophy
-- Kill 1 rare in Howling Fjord
[50200] = {
[1] = "Fjord Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 68, -- Min level
[5] = 72, -- Quest level
[17] = 495, -- Zone (Howling Fjord)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Howling Fjord"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 3 rare NPCs in Howling Fjord
{ 32398, 32377, 32386 },
32398, -- RootId: King Ping (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
-- Quest 50045: Demon's at the Edge
-- Kill 40 demons in Blade's Edge Mountains
[50045] = {
[1] = "Demon's at the Edge",
[2] = { nil, { 600600 } },
[4] = 65, -- Min level
[5] = 68, -- Quest level
[17] = 3522, -- Zone (Blade's Edge Mountains)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 40 Demons in Blade's Edge Mountains"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 37 Demons in Blade's Edge Mountains
{ 20557, 22180, 19973, 22195, 23355, 19960, 19979, 19963, 22291, 22304, 16952, 23353, 23174, 23173, 21021, 19747, 19978, 23078, 23055, 23212, 21837, 22500, 19980, 23199, 12922, 21519, 19961, 22204, 22499, 23220, 23113, 21516, 22281, 23230, 23354, 22474, 23275 },
20557, -- RootId: Wrath Hound (representative demon)
"Demon slain"
}
}
},
[30] = 40, -- Kill count
},
-- Quest 50063: Frostbound Brood
-- Kill 30 dragonkin in Borean Tundra
[50063] = {
[1] = "Frostbound Brood",
[2] = { nil, { 600600 } },
[4] = 68,
[5] = 71,
[17] = 3537, -- Zone (Borean Tundra)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 30 Dragonkin in Borean Tundra"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 16 Dragonkin in Borean Tundra
{ 25728, 26127, 25711, 25721, 25448, 32534, 25585, 26232, 25712, 25717, 25722, 24770, 25716, 26231, 25713, 26310 },
25728, -- RootId: Coldarra Wyrmkin (representative dragonkin)
"Dragonkin slain"
}
}
},
[30] = 30, -- Kill count
},
-- Quest 50178: Badlands Trophy
-- Kill 1 rare in Badlands
[50178] = {
[1] = "Badlands Trophy", -- Quest name
[2] = { nil, { 600600 } }, -- startedBy: {{npcIds}, {objectIds}, {itemIds}}
[4] = 35, -- Min level
[5] = 40, -- Quest level
[17] = 3, -- Zone (Badlands)
[8] = {
"This is a custom objective. Upon completion, you will automatically receive the assigned reward. If you die, the quest will be removed from your quest log. This quest is automatically rewarded upon completion.",
"Kill 1 Rare in Badlands"
},
[10] = {
nil, -- [1] creatureObjective (not used)
nil, -- [2] objectObjective (not used)
nil, -- [3] itemObjective (not used)
nil, -- [4] reputationObjective (not used)
{ -- [5] killCreditObjective
{
-- 10 rare NPCs in the Badlands
{ 2850, 2931, 14224, 2754, 2753, 2752, 2749, 7057, 2744, 2751 },
2850, -- RootId: Broken Tooth (representative rare)
"Rare slain"
}
}
},
[30] = 1, -- Kill count
},
}
-- Backward compatibility
+5 -2
View File
@@ -122,7 +122,10 @@ function ZoneDB:GetAreaIdByUiMapId(uiMapId)
end
end
if Questie.db.profile.debugEnabled then
Questie:Debug(Questie.DEBUG_CRITICAL,
-- NOTE: On Ascension, hub cities (Stormwind, Shattrath, etc.) may return a
-- continent-level UiMapId from GetBestMapForUnit. This is harmless — the nil
-- return is handled gracefully by GetCurrentZoneId's callers.
Questie:Debug(Questie.DEBUG_DEVELOP,
"No AreaId found for UiMapId: " ..
uiMapId .. ":" .. (C_Map.GetMapInfo(uiMapId) and C_Map.GetMapInfo(uiMapId).name or "nil"))
end
@@ -373,7 +376,7 @@ function _ZoneDB:RunTests()
local success, result = pcall(ZoneDB.GetAreaIdByUiMapId, ZoneDB, map.mapID)
if not success and not buggedMaps[map.mapID] then
Questie:Error("[ZoneDBTests] ZoneDB.GetAreaIdByUiMapId fails for " ..
map.name .. " (" .. map.mapID .. "). Result: " .. result)
map.name .. " (" .. map.mapID .. "). Result: " .. result)
end
end
end
+45 -41
View File
@@ -89,13 +89,10 @@ function QuestEventHandler:RegisterEvents()
end
for questLogIndex = 1, 75 do
local title, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(questLogIndex)
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(
questLogIndex)
if (not title) then
break
end
if (not isHeader) then
if title and questId and (not isHeader) then
quest = QuestieDB.GetQuest(questId)
if quest then
@@ -107,7 +104,8 @@ function QuestEventHandler:RegisterEvents()
sourceItemId = quest.sourceItemId
if sourceItemId then
soureItemName, _, _, _, _, sourceItemType, _, _, _, _, _, soureClassID = GetItemInfo(sourceItemId)
soureItemName, _, _, _, _, sourceItemType, _, _, _, _, _, soureClassID = GetItemInfo(
sourceItemId)
end
end
@@ -115,7 +113,8 @@ function QuestEventHandler:RegisterEvents()
reqSourceItemId = quest.requiredSourceItems[1]
if reqSourceItemId then
reqSoureItemName, _, _, _, _, reqSourceItemType, _, _, _, _, _, reqSoureClassID = GetItemInfo(reqSourceItemId)
reqSoureItemName, _, _, _, _, reqSourceItemType, _, _, _, _, _, reqSoureClassID =
GetItemInfo(reqSourceItemId)
end
end
@@ -154,14 +153,16 @@ function QuestEventHandler:RegisterEvents()
end
if frame ~= nil and text ~= nil then
local updateText = l10n("Quest Item %%s might be needed for the quest %%s. \n\nAre you sure you want to delete this?")
local updateText = l10n(
"Quest Item %%s might be needed for the quest %%s. \n\nAre you sure you want to delete this?")
text:SetFormattedText(updateText, text_arg1, questName)
text.text_arg1 = updateText
StaticPopup_Resize(frame, which)
deletedQuestItem = true
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] StaticPopup_Show: Quest Item Detected. Updating Static Popup.")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieQuest] StaticPopup_Show: Quest Item Detected. Updating Static Popup.")
end
end
end
@@ -170,12 +171,13 @@ function QuestEventHandler:RegisterEvents()
hooksecurefunc("DeleteCursorItem", function()
-- Hook DeleteCursorItem so we know when the player clicks the Accept button
if deletedQuestItem then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] DeleteCursorItem: Quest Item deleted. Update all quests.")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieQuest] DeleteCursorItem: Quest Item deleted. Update all quests.")
C_Timer.After(0.25, function()
_QuestEventHandler:UpdateAllQuests()
deletedQuestItem = false
end)
_QuestEventHandler:UpdateAllQuests()
deletedQuestItem = false
end)
end
end)
@@ -187,9 +189,9 @@ function _QuestEventHandler:InitQuestLog()
-- Fill the QuestLogCache for first time
local cacheMiss, changes = QuestLogCache.CheckForChanges(nil)
-- if cacheMiss then
-- TODO actually can happen in rare edge case if player accepts new quest during questie init. *cough*
-- or if someone managed to overflow game cache already at this point.
--Questie:Error("Did you accept a quest during InitQuestLog? Please report on Github or Discord. Game's quest log cache is not ok. This shouldn't happen. Questie may malfunction.")
-- TODO actually can happen in rare edge case if player accepts new quest during questie init. *cough*
-- or if someone managed to overflow game cache already at this point.
--Questie:Error("Did you accept a quest during InitQuestLog? Please report on Github or Discord. Game's quest log cache is not ok. This shouldn't happen. Questie may malfunction.")
-- end
for questId, _ in pairs(changes) do
@@ -231,7 +233,7 @@ function _QuestEventHandler:QuestAccepted(questLogIndex, questId)
QuestieCombatQueue:Queue(function()
QuestieLib:CacheItemNames(questId)
_QuestEventHandler:HandleQuestAccepted(questId)
QuestieTracker:Update()
QuestieTracker:Update()
end)
Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_ACCEPTED - skipNextUQLCEvent - ", skipNextUQLCEvent)
@@ -240,13 +242,13 @@ end
---@param questId number
---@return boolean true @if the function was successful, false otherwise
function _QuestEventHandler:HandleQuestAccepted(questId)
local idx = QuestieCompat.GetQuestLogIndexByID(questId)
if not idx then
_QuestLogUpdateQueue:Insert(function()
return _QuestEventHandler:HandleQuestAccepted(questId)
end)
return false
end
local idx = QuestieCompat.GetQuestLogIndexByID(questId)
if not idx then
_QuestLogUpdateQueue:Insert(function()
return _QuestEventHandler:HandleQuestAccepted(questId)
end)
return false
end
-- We first check the quest objectives and retry in the next QLU event if they are not correct yet
local cacheMiss, changes = QuestLogCache.CheckForChanges({ [questId] = true })
if cacheMiss then
@@ -272,9 +274,9 @@ function _QuestEventHandler:HandleQuestAccepted(questId)
else
QuestieQuest:AcceptQuest(questId)
end
QuestieCompat.C_Timer.After(0.2, function()
QuestieTracker:Update()
end)
QuestieCompat.C_Timer.After(0.2, function()
QuestieTracker:Update()
end)
return true
end
@@ -301,7 +303,7 @@ function _QuestEventHandler:QuestTurnedIn(questId, xpReward, moneyReward)
-- is empty
questLog[questId].state = QUEST_LOG_STATES.QUEST_TURNED_IN
elseif QuestieCompat.Is335 then
questLog[questId] = {state = QUEST_LOG_STATES.QUEST_TURNED_IN}
questLog[questId] = { state = QUEST_LOG_STATES.QUEST_TURNED_IN }
end
local parentQuest = QuestieDB.QueryQuestSingle(questId, "parentQuest")
@@ -316,7 +318,8 @@ function _QuestEventHandler:QuestTurnedIn(questId, xpReward, moneyReward)
local itemName, _, _, quality, _, itemID = GetQuestLogRewardInfo(GetNumQuestLogRewards(questId), questId)
if (itemID ~= nil or itemName ~= nil) and quality == 1 then
Questie:Debug(Questie.DEBUG_DEVELOP, "Quest:", questId, "Recieved a possible Quest Item - do a full Quest Log check")
Questie:Debug(Questie.DEBUG_DEVELOP, "Quest:", questId,
"Recieved a possible Quest Item - do a full Quest Log check")
doFullQuestLogScan = true
skipNextUQLCEvent = false
else
@@ -331,11 +334,11 @@ function _QuestEventHandler:QuestTurnedIn(questId, xpReward, moneyReward)
-- QuestieQuest:CompleteQuest(questId)
-- QuestieJourney:CompleteQuest(questId)
-- QuestieAnnounce:CompletedQuest(questId)
-- questLog[questId] = nil
-- questLog[questId] = nil
QuestieCombatQueue:Queue(function()
QuestieTracker:Update()
end)
QuestieCombatQueue:Queue(function()
QuestieTracker:Update()
end)
end
--- Fires when a quest is removed from the quest log. This includes turning it in and abandoning it.
@@ -386,7 +389,8 @@ function _QuestEventHandler:MarkQuestAsAbandoned(questId)
-- so we don't attempt to index a nil value.
if (not questEntry) then
Questie:Debug(Questie.DEBUG_DEVELOP, "QuestEventHandler:MarkQuestAsAbandoned - questLog entry missing for", questId)
Questie:Debug(Questie.DEBUG_DEVELOP, "QuestEventHandler:MarkQuestAsAbandoned - questLog entry missing for",
questId)
return
end
@@ -406,8 +410,8 @@ function _QuestEventHandler:MarkQuestAsAbandoned(questId)
if (not objectivesWereComplete) and desc and type(desc) == "string" then
if string.find(desc, "automatically rewarded") then
Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "detected as auto-complete by description")
objectivesWereComplete = true
Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "detected as auto-complete by description")
objectivesWereComplete = true
end
end
end
@@ -415,7 +419,8 @@ function _QuestEventHandler:MarkQuestAsAbandoned(questId)
if objectivesWereComplete then
-- Objectives were complete, so this was likely an auto-complete quest.
-- Mark as complete instead of abandoned so objective pins get hidden.
Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "objectives were complete - treating as completed (auto-complete quest)")
Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId,
"objectives were complete - treating as completed (auto-complete quest)")
questEntry.state = QUEST_LOG_STATES.QUEST_TURNED_IN
QuestLogCache.RemoveQuest(questId)
@@ -502,14 +507,13 @@ function _QuestEventHandler:UnitQuestLogChanged(unitTarget)
skipNextUQLCEvent = false
end
-- Fallback cleanup: some servers remove quests without firing QUEST_REMOVED reliably.
-- This compares Questie's currentQuestlog vs the game's quest log and removes stale quests.
function _QuestEventHandler:CleanupRemovedQuestsFallback()
local gameQuestIds = {}
local numEntries = select(1, GetNumQuestLogEntries()) or 0
for questLogIndex = 1, numEntries do
local title, _, _, isHeader, _, _, _, qid = GetQuestLogTitle(questLogIndex)
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, qid = GetQuestLogTitle(questLogIndex)
if title and qid and qid > 0 and (not isHeader) then
gameQuestIds[qid] = true
end
@@ -651,7 +655,7 @@ function _QuestEventHandler:ZoneChangedNewArea()
end
end)
-- We only want this to fire outside of an instance if the player isn't dead and we need to reset the Tracker
-- We only want this to fire outside of an instance if the player isn't dead and we need to reset the Tracker
elseif (not Questie.db.char.isTrackerExpanded and not UnitIsGhost("player")) and trackerMinimizedByDungeon == true then
C_Timer.After(8, function()
Questie:Debug(Questie.DEBUG_DEVELOP, "[EVENT] ZONE_CHANGED_NEW_AREA: Exiting Instance")
+37 -35
View File
@@ -10,7 +10,7 @@ local Sounds = QuestieLoader:ImportModule("Sounds")
--- COMPATIBILITY ---
local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle
local C_QuestLog_GetQuestObjectives = QuestieCompat.C_QuestLog.GetQuestObjectives
local C_QuestLog_GetQuestObjectives = QuestieCompat.C_QuestLog.GetQuestObjectives
local HaveQuestData = QuestieCompat.HaveQuestData
local stringByte = string.byte
@@ -53,7 +53,7 @@ local cache = {
},
[questId2] = ....,
}
]]--
]] --
---@class QuestLogCacheObjectiveData
@@ -85,10 +85,10 @@ QuestLogCache.questLog_DO_NOT_MODIFY = cache
---@return table? newObjectives, ObjectiveIndex[] changedObjIds @nil == cache miss in both addon and game caches. table {} == no objectives.
local function GetNewObjectives(questId, oldObjectives, questLogIndex)
local newObjectives = {} -- creating a fresh one to be able revert to old easily in case of missing data
local changedObjIds -- not assigning {} for easier nil when nothing changed
local changedObjIds -- not assigning {} for easier nil when nothing changed
local objectives = C_QuestLog_GetQuestObjectives(questId, questLogIndex)
for objIndex=1, #objectives do -- iterate manually to be sure getting those in order
for objIndex = 1, #objectives do -- iterate manually to be sure getting those in order
local oldObj = oldObjectives[objIndex]
local newObj = objectives[objIndex]
-- Check if objective.text is in game's cache
@@ -102,7 +102,7 @@ local function GetNewObjectives(questId, oldObjectives, questLogIndex)
if (not changedObjIds) then
changedObjIds = { objIndex }
else
changedObjIds[#changedObjIds+1] = objIndex
changedObjIds[#changedObjIds + 1] = objIndex
end
if oldObj and newObj and oldObj.numRequired ~= oldObj.numFulfilled and newObj.numRequired == newObj.numFulfilled then
@@ -120,18 +120,22 @@ local function GetNewObjectives(questId, oldObjectives, questLogIndex)
type = newObj.type,
numRequired = newObj.numRequired,
text = QuestieLib.TrimObjectiveText(newObj.text, newObj.type),
finished = newObj.finished, -- gets overwritten with correct value later if quest isComplete
finished = newObj.finished, -- gets overwritten with correct value later if quest isComplete
numFulfilled = newObj.numFulfilled, -- gets overwritten with correct value later if quest isComplete
}
end
else -- objective text not in game's cache
if oldObj then
Questie:Debug(Questie.DEBUG_INFO, "[GetNewObjectives] objective not in game's cache. Using addon's cache. questID, objIndex:", questId, objIndex)
Questie:Debug(Questie.DEBUG_INFO,
"[GetNewObjectives] objective not in game's cache. Using addon's cache. questID, objIndex:", questId,
objIndex)
-- Extremely unlikely that the objective has changed from cached version as a change SHOULD trigger fetching data into game cache.
-- Possible bug point if there comes desync issues.
newObjectives[objIndex] = oldObj
else
Questie:Debug(Questie.DEBUG_INFO, "[GetNewObjectives] \"WARNING\" objective not in game's cache nor addon's cache. questID, objIndex:", questId, objIndex)
Questie:Debug(Questie.DEBUG_INFO,
"[GetNewObjectives] \"WARNING\" objective not in game's cache nor addon's cache. questID, objIndex:",
questId, objIndex)
-- Objective has been never cached
-- Tell to function caller that we couldn't get all required data from game's cache
-- Don't loop rest of objectives as we won't anyway save those into cache[] and C_QuestLog.GetQuestObjectives() call already triggered game to initiate caching those into game's cache.
@@ -158,7 +162,8 @@ function QuestLogCache.CheckForChanges(questIdsToCheck)
local numEntries = select(1, GetNumQuestLogEntries()) or 0
for questLogIndex = 1, numEntries do
local title, _, questTag, isHeader, _, isComplete, _, questId = GetQuestLogTitle(questLogIndex)
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(
questLogIndex)
-- Skip weird/header entries / questId=0 (these happen a lot on your server)
if title and questId and questId > 0 and (not isHeader) then
@@ -173,8 +178,7 @@ function QuestLogCache.CheckForChanges(questIdsToCheck)
if newObjectives then
if (not cachedQuest) or (#cachedObjectives == #newObjectives and #cachedObjectives > 0 and
(cachedQuest.title ~= title or cachedQuest.questTag ~= questTag or cachedQuest.isComplete ~= isComplete)) then
(cachedQuest.title ~= title or cachedQuest.questTag ~= questTag or cachedQuest.isComplete ~= isComplete)) then
changedObjIds = {}
for i = 1, #newObjectives do
changedObjIds[i] = i
@@ -206,7 +210,9 @@ function QuestLogCache.CheckForChanges(questIdsToCheck)
cacheMiss = true
end
else
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestLogCache.CheckForChanges] HaveQuestData() == false. questId, index:", questId, questLogIndex)
Questie:Debug(Questie.DEBUG_CRITICAL,
"[QuestLogCache.CheckForChanges] HaveQuestData() == false. questId, index:", questId,
questLogIndex)
C_QuestLog_GetQuestObjectives(questId, questLogIndex)
cacheMiss = true
end
@@ -226,29 +232,24 @@ function QuestLogCache.CheckForChanges(questIdsToCheck)
return cacheMiss, changes
end
function QuestLogCache.RemoveQuest(questId)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestLogCache.RemoveQuest] remove questId:", questId)
cache[questId] = nil
end
--- Tests if game client's cache has all quest log quests and objectives cached.
--- Avoid using this function if possible.
---@return boolean gameCacheOK
function QuestLogCache.TestGameCache()
local gameCacheOK = true
for questLogIndex = 1, MAX_QUEST_LOG_INDEX do
local title, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(questLogIndex)
if (not title) then
break -- We exceeded the valid quest log entries
end
if (not isHeader) then
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(
questLogIndex)
if title and questId and (not isHeader) then
if HaveQuestData(questId) then
local objectives = C_QuestLog_GetQuestObjectives(questId, questLogIndex)
for objIndex=1, #objectives do
for objIndex = 1, #objectives do
local text = objectives[objIndex].text
-- Check if objective.text is not in game's cache
if (not text) or (stringByte(text, 1) == 32) then
@@ -261,11 +262,11 @@ function QuestLogCache.TestGameCache()
end
end
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestLogCache.TestGameCache]", (gameCacheOK and "Cache ok." or "Cache missing data."))
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestLogCache.TestGameCache]",
(gameCacheOK and "Cache ok." or "Cache missing data."))
return gameCacheOK
end
--- A wrapper function to add error check instead using exposed table directly.
---@param questId QuestId
---@return QuestLogCacheData? @NEVER EVER MODIFY THE RETURNED TABLE
@@ -292,23 +293,21 @@ function QuestLogCache.GetQuestObjectives(questId)
return cache[questId].objectives
end
---@param q table @quest
---@param i number @index of the objective
---@param o table @objective
local function DebugPrintObjective(q, i, o)
if (o.raw_numFulfilled == o.numFulfilled) and (o.raw_finished == o.finished) then
print(" ", i.."/"..#q.objectives..":",
o.numFulfilled.."/"..o.numRequired.."="..tostring(o.finished),
print(" ", i .. "/" .. #q.objectives .. ":",
o.numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.finished),
o.type,
"\""..o.raw_text.."\" \""..o.text.."\"")
"\"" .. o.raw_text .. "\" \"" .. o.text .. "\"")
else
print(" ", i.."/"..#q.objectives..":",
o.raw_numFulfilled.."/"..o.numRequired.."="..tostring(o.raw_finished),
"FIX:", o.numFulfilled.."/"..o.numRequired.."="..tostring(o.finished),
print(" ", i .. "/" .. #q.objectives .. ":",
o.raw_numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.raw_finished),
"FIX:", o.numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.finished),
o.type,
"\""..o.raw_text.."\" \""..o.text.."\"")
"\"" .. o.raw_text .. "\" \"" .. o.text .. "\"")
end
end
@@ -318,7 +317,8 @@ function QuestLogCache.DebugPrintCache()
local count = 0
for questId, q in pairs(cache) do
count = count + 1
print("Quest: ("..questId..") \""..q.title.."\" questTag="..tostring(q.questTag) ,"isComplete="..tostring(q.isComplete))
print("Quest: (" .. questId .. ") \"" .. q.title .. "\" questTag=" .. tostring(q.questTag),
"isComplete=" .. tostring(q.isComplete))
if not next(q.objectives) then
print(" no objectives")
else
@@ -332,12 +332,14 @@ end
--- Debug function, prints changes
function QuestLogCache.DebugPrintCacheChanges(cacheMiss, changes)
local highlight = ((not cacheMiss) and (not next(changes))) or (cacheMiss and next(changes)) -- highlight untypical cases. they are okey, but sometimes interesting.
local highlight = ((not cacheMiss) and (not next(changes))) or
(cacheMiss and next(changes)) -- highlight untypical cases. they are okey, but sometimes interesting.
print("DebugPrintCacheChanges", GetTime(), (highlight and "\124cffFF4444CacheMiss:\124r" or "CacheMiss"), cacheMiss)
for questId, objIndexes in pairs(changes) do
local q = cache[questId]
print("Quest: ("..questId..") \""..q.title.."\" questTag="..tostring(q.questTag) ,"isComplete="..tostring(q.isComplete))
print("Quest: (" .. questId .. ") \"" .. q.title .. "\" questTag=" .. tostring(q.questTag),
"isComplete=" .. tostring(q.isComplete))
if not next(objIndexes) then
print(" no objectives changed (or quest doesn't have objectives)")
else
+132 -73
View File
@@ -17,7 +17,7 @@ local QuestieCorrections = QuestieLoader:ImportModule("QuestieCorrections")
local l10n = QuestieLoader:ImportModule("l10n")
local DebugInformation = {} -- stores text of debug data dump per session
local debugIndex = 0 -- current debug index, used so we can still retrieve info from previous offers
local debugIndex = 0 -- current debug index, used so we can still retrieve info from previous offers
local openDebugWindows = {} -- determines if existing debug window is already open, prevents duplicates
local GetBestMapForUnit = C_Map.GetBestMapForUnit
@@ -249,15 +249,15 @@ local itemWhitelist = {
}
local itemTripCodes = {
['ItemWhitelisted'] = 0, -- itemID is whitelisted; we want all its data anyway
['ItemMissingFromDB'] = 1, -- error when itemID is missing from DB entirely
['StartQuestIDMismatch'] = 2, -- error when item quest start ID does not match Questie ItemDB
['MissingDropFromNPC'] = 3, -- error when item dropped from npc (corpses) not matching ItemDB
['MissingDropFromObject'] = 4, -- error when item dropped from object (chests in world) not matching ItemDB
['MissingDropFromItem'] = 5, -- error when item dropped from item (lockboxes in inventory) not matching ItemDB
['ContainerMissingFromNPCDB'] = 6, -- error when item's container is missing from NpcDB
['ItemWhitelisted'] = 0, -- itemID is whitelisted; we want all its data anyway
['ItemMissingFromDB'] = 1, -- error when itemID is missing from DB entirely
['StartQuestIDMismatch'] = 2, -- error when item quest start ID does not match Questie ItemDB
['MissingDropFromNPC'] = 3, -- error when item dropped from npc (corpses) not matching ItemDB
['MissingDropFromObject'] = 4, -- error when item dropped from object (chests in world) not matching ItemDB
['MissingDropFromItem'] = 5, -- error when item dropped from item (lockboxes in inventory) not matching ItemDB
['ContainerMissingFromNPCDB'] = 6, -- error when item's container is missing from NpcDB
['ContainerMissingFromObjectDB'] = 7, -- error when item's container is missing from ObjectDB
['ContainerMissingFromItemDB'] = 8, -- error when item's container is missing from ItemDB
['ContainerMissingFromItemDB'] = 8, -- error when item's container is missing from ItemDB
}
-- determines whether to offer a debug offer for a looted item
@@ -274,16 +274,20 @@ local function filterItem(itemID, itemInfo, containerGUID)
-- OG chronoboon displacer was 184937 so safe to say any SoD items are higher than 190000
return nil
else
if tContains(itemWhitelist, itemID) then -- if item is in our whitelist, we want it no matter what
if tContains(itemWhitelist, itemID) then -- if item is in our whitelist, we want it no matter what
return itemTripCodes.ItemWhitelisted
elseif tContains(itemBlacklist, itemID) then -- if item is in our blacklist, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - Item " .. itemID .. " is in debug offer item blacklist, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - Item " .. itemID .. " is in debug offer item blacklist, ignoring")
return nil
elseif UnitLevel(player) < minLevelForDebugOffers then -- if player level is below our threshold, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - Player does not meet level threshold for debug offers, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - Player does not meet level threshold for debug offers, ignoring")
return nil
elseif QuestieCorrections.questItemBlacklist[itemID] then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - Item " .. itemID .. " is in QuestieCorrections item blacklist, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - Item " ..
itemID .. " is in QuestieCorrections item blacklist, ignoring")
return nil
end
@@ -312,7 +316,8 @@ local function filterItem(itemID, itemInfo, containerGUID)
if questID ~= QuestieDB.QueryItemSingle(itemID, "startQuest") then
return itemTripCodes.StartQuestIDMismatch
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - QuestID for " .. itemID .. " present but matches DB, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - QuestID for " .. itemID .. " present but matches DB, ignoring")
end
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - QuestID value not present, ignoring")
@@ -330,7 +335,8 @@ local function filterItem(itemID, itemInfo, containerGUID)
if not lootTable or tContains(lootTable, containerID) == false then
return itemTripCodes.MissingDropFromNPC
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - NPC drop data for item " .. itemID .. " OK, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - NPC drop data for item " .. itemID .. " OK, ignoring")
end
elseif containerType == "GameObject" then -- if container is an object
-- first check if object is even in our DB
@@ -344,7 +350,8 @@ local function filterItem(itemID, itemInfo, containerGUID)
if tContains(lootTable, containerID) == false then
return itemTripCodes.MissingDropFromObject
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - Object drop data for item " .. itemID .. " OK, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - Object drop data for item " .. itemID .. " OK, ignoring")
end
end
elseif containerType == "Item" and containerID > 0 then -- if container is an item and there is an containerID for it
@@ -360,12 +367,14 @@ local function filterItem(itemID, itemInfo, containerGUID)
if tContains(lootTable, containerID) == false then
return itemTripCodes.MissingDropFromItem
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - ItemSource drop data for item " .. itemID .. " OK, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - ItemSource drop data for item " .. itemID .. " OK, ignoring")
end
end
end
end
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemFilter - No data mismatches found for " .. itemID .. ", ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemFilter - No data mismatches found for " .. itemID .. ", ignoring")
return nil -- if no exceptions raised, we don't need to create a debug offer
end
@@ -378,13 +387,14 @@ local npcBlacklist = {
local function _AppendUniversalText(input)
local text = tostring(input)
text = text .. "\n|cFFAAAAAACharacter:|r Lvl " .. UnitLevel(player) .. " " .. string.upper(playerRace) .. " " .. playerClass
text = text ..
"\n|cFFAAAAAACharacter:|r Lvl " .. UnitLevel(player) .. " " .. string.upper(playerRace) .. " " .. playerClass
local mapID = GetBestMapForUnit(player)
local pos = GetPlayerMapPosition(mapID, player);
PosX = pos.x * 100
PosY = pos.y * 100
text = text .. "\n|cFFAAAAAAPlayer Coords:|r [" .. mapID .. "] " .. format("(%.3f, %.3f)", PosX, PosY)
PosX = pos and (pos.x * 100) or 0
PosY = pos and (pos.y * 100) or 0
text = text .. "\n|cFFAAAAAAPlayer Coords:|r [" .. tostring(mapID) .. "] " .. format("(%.3f, %.3f)", PosX, PosY)
local questLog = ""
for k in pairs(QuestLogCache.questLog_DO_NOT_MODIFY) do questLog = k .. ", " .. questLog end
@@ -403,11 +413,12 @@ function QuestieDebugOffer.LootWindow()
local debugContainer, _ = GetLootSourceInfo(1) -- happens early in case the rest of the code is so slow that the container closes before we're ready
local inInstance, _ = IsInInstance()
if inInstance == true then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemLoot - Player looting item is in instance, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemLoot - Player looting item is in instance, ignoring")
return -- temporary for SoD to reduce new raid loot triggering debug spam
end
for i=1, #lootInfo do
for i = 1, #lootInfo do
local itemInfo = lootInfo[i]
local itemLink = GetLootSlotLink(i)
local itemID = 0
@@ -420,7 +431,8 @@ function QuestieDebugOffer.LootWindow()
local tripCode = filterItem(itemID, itemInfo, debugContainer)
if tripCode then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - ItemLoot - Creating debug offer for item " .. itemID .. " reason " .. tripCode)
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - ItemLoot - Creating debug offer for item " .. itemID .. " reason " .. tripCode)
debugIndex = debugIndex + 1
if tripCode == itemTripCodes.ItemWhitelisted then
DebugInformation[debugIndex] = "ItemDB is missing some data about this item!"
@@ -443,13 +455,20 @@ function QuestieDebugOffer.LootWindow()
else
DebugInformation[debugIndex] = "General data mismatch for item!"
end
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n\n|cFFAAAAAAItem ID:|r " .. tostring(itemID)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n\n|cFFAAAAAAItem ID:|r " .. tostring(itemID)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAItem Name:|r " .. itemLink
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest Item:|r " .. tostring(itemInfo.isQuestItem)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest ID:|r " .. tostring(itemInfo.questId)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAContainer:|r " .. tostring(debugContainer)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest Item:|r " .. tostring(itemInfo.isQuestItem)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest ID:|r " .. tostring(itemInfo.questId)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAContainer:|r " .. tostring(debugContainer)
DebugInformation[debugIndex] = _AppendUniversalText(DebugInformation[debugIndex])
Questie:Print(l10n("An item you just encountered has data missing from the Questie database.") .. " " .. l10n("Would you like to help us fix it?") .. " |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
Questie:Print(l10n("An item you just encountered has data missing from the Questie database.") ..
" " ..
l10n("Would you like to help us fix it?") ..
" |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
return -- we only want to show one debug offer per interaction, so break on the first one
end
end
@@ -460,10 +479,11 @@ function QuestieDebugOffer.QuestDialog()
local questID = GetQuestID() -- obtain quest ID from dialog
if questID <= 0 or questID == nil then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - QuestDialog - Invalid quest ID from API, ignoring")
return -- invalid data from API, abandon offer attempt
return -- invalid data from API, abandon offer attempt
end
if UnitLevel(player) < minLevelForDebugOffers then -- if player level is below our threshold, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - QuestDialog - Player does not meet level threshold for debug offers, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - QuestDialog - Player does not meet level threshold for debug offers, ignoring")
return
end
if QuestieDB.QueryQuestSingle(questID, "name") == nil then -- if ID not in our DB
@@ -474,77 +494,100 @@ function QuestieDebugOffer.QuestDialog()
local rewardText = GetRewardText()
local rewardXP = GetRewardXP()
if questText then questText = questText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from quest text
if questText then questText = questText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from quest text
if objectiveText then objectiveText = objectiveText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from objective text
if rewardText then rewardText = rewardText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from reward text
if rewardText then rewardText = rewardText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from reward text
DebugInformation[debugIndex] = "Quest in dialog not present in QuestDB!"
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n\n|cFFAAAAAAQuest ID:|r " .. tostring(questID)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest Name:|r " .. tostring(questTitle)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest Text:|r " .. tostring(questText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAObjective Text:|r " .. tostring(objectiveText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAReward Text:|r " .. tostring(rewardText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest Name:|r " .. tostring(questTitle)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest Text:|r " .. tostring(questText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAObjective Text:|r " .. tostring(objectiveText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAReward Text:|r " .. tostring(rewardText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAReward XP:|r " .. tostring(rewardXP)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuestgiver:|r " .. tostring(UnitGUID(questnpc))
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuestgiver:|r " .. tostring(UnitGUID(questnpc))
DebugInformation[debugIndex] = _AppendUniversalText(DebugInformation[debugIndex])
Questie:Print(l10n("A quest you just encountered is missing from the Questie database.") .. " " .. l10n("Would you like to help us fix it?") .. " |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
Questie:Print(l10n("A quest you just encountered is missing from the Questie database.") ..
" " ..
l10n("Would you like to help us fix it?") ..
" |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
end
end
-- Missing questID when tracking
---@param questID number
function QuestieDebugOffer.QuestTracking(questID) -- ID supplied by tracker during update
function QuestieDebugOffer.QuestTracking(questID) -- ID supplied by tracker during update
if UnitLevel(player) < minLevelForDebugOffers then -- if player level is below our threshold, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - QuestTracking - Player does not meet level threshold for debug offers, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - QuestTracking - Player does not meet level threshold for debug offers, ignoring")
return
end
if QuestieDB.QueryQuestSingle(questID, "name") == nil then -- if ID not in our DB
for i=1, GetNumQuestLogEntries() do
local questTitle, questLevel, suggestedGroup, _, _, _, frequency, questLogId = GetQuestLogTitle(i)
for i = 1, GetNumQuestLogEntries() do
local questTitle, questLevel, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questLogId =
GetQuestLogTitle(i)
local questText, objectiveText = GetQuestLogQuestText(i)
if questText then questText = questText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from quest text
if questText then questText = questText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from quest text
if objectiveText then objectiveText = objectiveText:gsub(GetUnitName(player), "<playername>") end -- strip out player name from objective text
if questID == questLogId then
debugIndex = debugIndex + 1
DebugInformation[debugIndex] = "Quest in tracker not present in QuestDB!"
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n\n|cFFAAAAAAQuest ID:|r " .. tostring(questLogId)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest Name:|r " .. tostring(questTitle)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAQuest Text:|r " .. tostring(questText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAAObjective Text:|r " .. tostring(objectiveText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n\n|cFFAAAAAAQuest ID:|r " .. tostring(questLogId)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest Name:|r " .. tostring(questTitle)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAQuest Text:|r " .. tostring(questText)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAAObjective Text:|r " .. tostring(objectiveText)
DebugInformation[debugIndex] = _AppendUniversalText(DebugInformation[debugIndex])
Questie:Print(l10n("A quest in your quest log is missing from the Questie database and can't be tracked.") .. " " .. l10n("Would you like to help us fix it?") .. " |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
Questie:Print(l10n(
"A quest in your quest log is missing from the Questie database and can't be tracked.") ..
" " ..
l10n("Would you like to help us fix it?") ..
" |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
--print(tostring(QuestieDB.IsSoDRuneQuest(questlogid)))
end
end
end
end
local targetTimeout = {} -- store timeouts per-ID so we don't cause lag or spam chat if a player clicks on an unknown NPC often
local targetTimeout = {} -- store timeouts per-ID so we don't cause lag or spam chat if a player clicks on an unknown NPC often
local timeoutDurationOverworld = 120 -- how many seconds to ignore re-passes outside of instances
local timeoutDurationInstance = 600 -- how many seconds to ignore re-passes outside of instances
local timeoutDurationInstance = 600 -- how many seconds to ignore re-passes outside of instances
-- Missing NPC ID when targeting
function QuestieDebugOffer.NPCTarget()
if UnitLevel(player) < minLevelForDebugOffers then -- if player level is below our threshold, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - NPCTarget - Player does not meet level threshold for debug offers, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - NPCTarget - Player does not meet level threshold for debug offers, ignoring")
return
end
local inInstance, _ = IsInInstance()
if inInstance == true then -- temporary override for SoD launch to not prompt NPC debug offers inside instances at all, to prevent BFD spam
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - NPCTarget - Player targeting NPC is in instance, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - NPCTarget - Player targeting NPC is in instance, ignoring")
return
end
local targetGUID = UnitGUID(target)
local unit_type = strsplit("-", tostring(targetGUID)) -- determine target type
if unit_type == "Creature" then -- if target is an NPC
local unit_type = strsplit("-", tostring(targetGUID)) -- determine target type
if unit_type == "Creature" then -- if target is an NPC
local npcID = tonumber(targetGUID:match("-(%d+)-%x+$"), 10) -- obtain NPC ID
if targetTimeout[npcID] == true then -- if target was already targeted recently
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - NPCTarget - Targeted NPC was targeted recently, ignoring")
if not npcID then return end
if targetTimeout[npcID] == true then -- if target was already targeted recently
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - NPCTarget - Targeted NPC was targeted recently, ignoring")
return
else -- if target was NOT targeted recently
else -- if target was NOT targeted recently
if tContains(npcBlacklist, npcID) then -- if NPC is in our blacklist, ignore it
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - NPCTarget - Targeted NPC is in NPC blacklist, ignoring")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieDebugOffer] - NPCTarget - Targeted NPC is in NPC blacklist, ignoring")
return
end
targetTimeout[npcID] = true
@@ -565,18 +608,26 @@ function QuestieDebugOffer.NPCTarget()
elseif npcFriendly == false then
npcStatus = "Neutral"
end
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n\n|cFFAAAAAANPC ID:|r " .. tostring(npcID)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAANPC Name:|r " .. tostring(npcName)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAANPC Level:|r " .. tostring(npcLevel)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAANPC Health, Max:|r " .. tostring(npcHealth) .. ", " .. tostring(npcHealthMax)
DebugInformation[debugIndex] = DebugInformation[debugIndex] .. "\n|cFFAAAAAANPC Allegiance:|r " .. tostring(npcStatus)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n\n|cFFAAAAAANPC ID:|r " .. tostring(npcID)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAANPC Name:|r " .. tostring(npcName)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAANPC Level:|r " .. tostring(npcLevel)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAANPC Health, Max:|r " .. tostring(npcHealth) .. ", " .. tostring(npcHealthMax)
DebugInformation[debugIndex] = DebugInformation[debugIndex] ..
"\n|cFFAAAAAANPC Allegiance:|r " .. tostring(npcStatus)
DebugInformation[debugIndex] = _AppendUniversalText(DebugInformation[debugIndex])
Questie:Print(l10n("The NPC you just targeted is missing from the Questie database.") .. " " .. l10n("Would you like to help us fix it?") .. " |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
Questie:Print(l10n("The NPC you just targeted is missing from the Questie database.") ..
" " ..
l10n("Would you like to help us fix it?") ..
" |cff71d5ff|Haddon:questie:offer:" .. debugIndex .. "|h[" .. l10n("More Info") .. "]|h|r")
end
if inInstance == false then
C_Timer.NewTimer (timeoutDurationOverworld, function() targetTimeout[npcID] = false end)
C_Timer.NewTimer(timeoutDurationOverworld, function() targetTimeout[npcID] = false end)
else
C_Timer.NewTimer (timeoutDurationInstance, function() targetTimeout[npcID] = false end)
C_Timer.NewTimer(timeoutDurationInstance, function() targetTimeout[npcID] = false end)
end
end
else
@@ -606,7 +657,8 @@ local function _CreateOfferFrame(popupText, discordURL, index)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieDebugOffer] - An offer is already open, not creating new frame")
return
end
local debugFrame = CreateFrame("Frame", "QuestieDebugOfferFrame", UIParent, BackdropTemplateMixin and "BackdropTemplate")
local debugFrame = CreateFrame("Frame", "QuestieDebugOfferFrame", UIParent,
BackdropTemplateMixin and "BackdropTemplate")
debugFrame:SetPoint("CENTER")
debugFrame:SetMovable(true)
debugFrame:EnableMouse(true)
@@ -623,7 +675,8 @@ local function _CreateOfferFrame(popupText, discordURL, index)
debugFrame.title = debugFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
debugFrame.title:SetPoint("TOP", 0, -15)
debugFrame.title:SetText("|TInterface\\Addons\\Questie\\Icons\\startendstart.tga:16|t |cFFFED218" .. l10n("Questie Debug Info") .. "|r |TInterface\\Addons\\Questie\\Icons\\startendstart.tga:16|t")
debugFrame.title:SetText("|TInterface\\Addons\\Questie\\Icons\\startendstart.tga:16|t |cFFFED218" ..
l10n("Questie Debug Info") .. "|r |TInterface\\Addons\\Questie\\Icons\\startendstart.tga:16|t")
-- Create a single large edit box with no background
debugFrame.dataEditBox = CreateFrame("EditBox", nil, debugFrame)
@@ -646,7 +699,10 @@ local function _CreateOfferFrame(popupText, discordURL, index)
debugFrame.discordText = debugFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
debugFrame.discordText:SetPoint("TOP", debugFrame.dataEditBox, "BOTTOM", 0, -15)
debugFrame.discordText:SetText("|cFFAAAAAA" .. l10n("Please share this info with us on") .. " |TInterface\\Addons\\Questie\\Icons\\discord.blp:16|t |cFF5765ECDiscord|r\n" .. "(" .. l10n("You can copy the data above") .. ")")
debugFrame.discordText:SetText("|cFFAAAAAA" ..
l10n("Please share this info with us on") ..
" |TInterface\\Addons\\Questie\\Icons\\discord.blp:16|t |cFF5765ECDiscord|r\n" ..
"(" .. l10n("You can copy the data above") .. ")")
debugFrame.discordLinkEditBox = CreateFrame("EditBox", nil, debugFrame, "InputBoxTemplate")
debugFrame.discordLinkEditBox:SetSize(200, 20)
@@ -666,7 +722,9 @@ local function _CreateOfferFrame(popupText, discordURL, index)
debugFrame:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
tile = true,
tileSize = 32,
edgeSize = 32,
insets = { left = 8, right = 8, top = 8, bottom = 8 }
})
@@ -680,7 +738,8 @@ end
function QuestieDebugOffer.ShowOffer(link)
-- We also have access to the questie.dev domain (purchased by Logon)
local discordURL = "https://discord.gg/Q6j4qByndw" -- redirect to #bug-redirect
local i = tonumber(string.sub(link,21))
local i = tonumber(string.sub(link, 21))
if not i then return end
local popupText = DebugInformation[i]
_CreateOfferFrame(popupText, discordURL, i)
+16 -8
View File
@@ -91,7 +91,7 @@ function QuestieLearner:LearnNPC(npcId, name, level, subName, npcFlags, factionS
end
end
if not found then
table.insert(existing[7][zoneId], {x, y})
table.insert(existing[7][zoneId], { x, y })
end
end
@@ -117,7 +117,7 @@ function QuestieLearner:LearnQuest(questId, name, questLevel, requiredLevel, zon
if type(objectives) == "table" then
existing[8] = objectives
elseif type(objectives) == "string" then
existing[8] = {objectives}
existing[8] = { objectives }
end
end
@@ -140,7 +140,9 @@ function QuestieLearner:LearnQuestGiver(questId, npcId, isStart)
existing[2][1] = existing[2][1] or {}
local found = false
for _, id in ipairs(existing[2][1]) do
if id == npcId then found = true; break end
if id == npcId then
found = true; break
end
end
if not found then table.insert(existing[2][1], npcId) end
else
@@ -148,7 +150,9 @@ function QuestieLearner:LearnQuestGiver(questId, npcId, isStart)
existing[3][1] = existing[3][1] or {}
local found = false
for _, id in ipairs(existing[3][1]) do
if id == npcId then found = true; break end
if id == npcId then
found = true; break
end
end
if not found then table.insert(existing[3][1], npcId) end
end
@@ -188,7 +192,9 @@ function QuestieLearner:LearnItemDrop(itemId, npcId)
existing[2] = existing[2] or {}
local found = false
for _, id in ipairs(existing[2]) do
if id == npcId then found = true; break end
if id == npcId then
found = true; break
end
end
if not found then table.insert(existing[2], npcId) end
end
@@ -221,7 +227,7 @@ function QuestieLearner:LearnObject(objectId, name)
end
end
if not found then
table.insert(existing[4][zoneId], {x, y})
table.insert(existing[4][zoneId], { x, y })
end
end
@@ -513,13 +519,15 @@ function QuestieLearner:OnLootOpened()
local numItems = GetNumLootItems()
for i = 1, numItems do
local lootIcon, lootName, lootQuantity, currencyID, lootQuality, locked, isQuestItem, questId, isActive = GetLootSlotInfo(i)
local lootIcon, lootName, lootQuantity, currencyID, lootQuality, locked, isQuestItem, questId, isActive =
GetLootSlotInfo(i)
if lootName then
local link = GetLootSlotLink(i)
if link then
local itemId = tonumber(string.match(link, "item:(%d+)"))
if itemId then
local _, _, _, itemLevel, requiredLevel, itemType, itemSubType, _, _, _, _, itemClassId, itemSubClassId = GetItemInfo(link)
local _, _, _, itemLevel, requiredLevel, itemType, itemSubType, _, _, _, _, itemClassId, itemSubClassId =
GetItemInfo(link)
self:LearnItem(itemId, lootName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
if npcId then
+21 -17
View File
@@ -6,7 +6,7 @@ Flow:
----> Game cache should have all quests in it, data of each quest may be invalid.
If data is invalid, Wait for next QUEST_LOG_UPDATE and check again.
-----> Game Cache ok. Call possible callback functions.
]]--
]] --
---@class QuestieValidateGameCache
local QuestieValidateGameCache = QuestieLoader:CreateModule("QuestieValidateGameCache")
@@ -22,7 +22,7 @@ local GetQuestObjectives = QuestieCompat.C_QuestLog.GetQuestObjectives
local HaveQuestData = QuestieCompat.HaveQuestData
local stringByte, tremove = string.byte, table.remove
local tpack = QuestieLib.tpack
local tpack = QuestieLib.tpack
local tunpack = QuestieLib.tunpack
-- 3 * (Max possible number of quests in game quest log)
@@ -50,11 +50,10 @@ function QuestieValidateGameCache.AddCallback(func, ...)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieValidateGameCache] Calling a callback function imediately.")
func(...)
else
callbacks[#callbacks+1] = {func, tpack(...)}
callbacks[#callbacks + 1] = { func, tpack(...) }
end
end
local function DestroyEventFrame()
if eventFrame then
eventFrame:UnregisterAllEvents()
@@ -72,7 +71,9 @@ local function OnQuestLogUpdate()
-- This is to wait until cache has atleast some refreshed data from a game server.
if numberOfQuestLogUpdatesToSkip > 0 then
numberOfQuestLogUpdatesToSkip = numberOfQuestLogUpdatesToSkip - 1
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieValidateGameCache] Skipping a QUEST_LOG_UPDATE event. Quest log has entries, quests:", numEntries, numQuests)
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieValidateGameCache] Skipping a QUEST_LOG_UPDATE event. Quest log has entries, quests:", numEntries,
numQuests)
return
end
@@ -80,11 +81,8 @@ local function OnQuestLogUpdate()
local goodQuestsCount = 0 -- for debug stats
for i = 1, MAX_QUEST_LOG_INDEX do
local title, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(i)
if (not title) then
break -- We exceeded the data in the quest log
end
if (not isHeader) then
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(i)
if title and questId and (not isHeader) then
if (not HaveQuestData(questId)) then
isQuestLogGood = false
else
@@ -95,11 +93,13 @@ local function OnQuestLogUpdate()
-- On WotLK private servers, GetQuestObjectives can return nil for quests with
-- no trackable objectives. This is not a broken cache state; treat it as an
-- empty (valid) objective list so goodQuestsCount increments correctly.
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieValidateGameCache] GetQuestObjectives returned non-table for questId:", questId, "- treating as empty objectives")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieValidateGameCache] GetQuestObjectives returned non-table for questId:", questId,
"- treating as empty objectives")
objectiveList = {}
end
for _, objective in pairs(objectiveList) do -- objectiveList may be {}, which is also a valid cached quest in quest log
for _, objective in pairs(objectiveList) do -- objectiveList may be {}, which is also a valid cached quest in quest log
if (not objective.text) or (stringByte(objective.text, 1) == 32) then -- if (text starts with a space " ") then
-- Game hasn't cached the quest fully yet
isQuestLogGood = false
@@ -117,7 +117,8 @@ local function OnQuestLogUpdate()
end
if not isQuestLogGood then
Questie:Debug(Questie.DEBUG_INFO, "[QuestieValidateGameCache] Quest log is NOT yet okey. Good quest:", goodQuestsCount.."/"..numQuests )
Questie:Debug(Questie.DEBUG_INFO, "[QuestieValidateGameCache] Quest log is NOT yet okey. Good quest:",
goodQuestsCount .. "/" .. numQuests)
return
end
@@ -125,12 +126,15 @@ local function OnQuestLogUpdate()
-- This count mismatch can occur on WotLK private servers where GetNumQuestLogEntries
-- returns a different value than objectives-loop counted. The cache is valid because
-- isQuestLogGood already passed above. Log at debug level only.
Questie:Debug(Questie.DEBUG_INFO, "[QuestieValidateGameCache] Quest count mismatch (expected "..tostring(numQuests)..", validated "..tostring(goodQuestsCount).."). Cache is still valid.")
Questie:Debug(Questie.DEBUG_INFO,
"[QuestieValidateGameCache] Quest count mismatch (expected " ..
tostring(numQuests) .. ", validated " .. tostring(goodQuestsCount) .. "). Cache is still valid.")
end
DestroyEventFrame()
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieValidateGameCache] Quest log is ok. Good quest:", goodQuestsCount.."/"..numQuests )
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieValidateGameCache] Quest log is ok. Good quest:",
goodQuestsCount .. "/" .. numQuests)
isCacheGood = true
@@ -148,7 +152,7 @@ local function OnPlayerEnteringWorld(_, _, isInitialLogin, isReloadingUi)
if QuestieCompat.Is335 then
isInitialLogin, isReloadingUi = false, true -- 335 Not skipping for now
end
assert(isInitialLogin or isReloadingUi) -- We should get to here only at login or at /reload.
assert(isInitialLogin or isReloadingUi) -- We should get to here only at login or at /reload.
-- Game's quest log has still old cached data on the first QUEST_LOG_UPDATE after PLAYER_ENTERING_WORLD during login.
-- So we need to skip that event.
@@ -167,4 +171,4 @@ function QuestieValidateGameCache.StartCheck()
eventFrame = CreateFrame("Frame")
eventFrame:SetScript("OnEvent", OnPlayerEnteringWorld)
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
end
end
+67 -35
View File
@@ -42,7 +42,7 @@ local function _Questie_SilentGetQuestLogIndexByID(questId)
local n = (GetNumQuestLogEntries and select(1, GetNumQuestLogEntries())) or 0
if n > 0 and GetQuestLogTitle then
for i = 1, n do
local _, _, _, _, isHeader, _, _, _, qid = GetQuestLogTitle(i)
local _, _, _, isHeader, _, _, _, qid = GetQuestLogTitle(i)
qid = tonumber(qid)
if (not isHeader) and qid and qid == questId then
return i
@@ -133,7 +133,8 @@ function MapIconTooltip:Show()
local iconData = icon.data
if not iconData then
Questie:Error("[MapIconTooltip:Show] handleMapIcon - iconData is nil! self.data.Id =", self.data.Id, "- Aborting!")
Questie:Error("[MapIconTooltip:Show] handleMapIcon - iconData is nil! self.data.Id =", self.data.Id,
"- Aborting!")
return
end
@@ -166,14 +167,14 @@ function MapIconTooltip:Show()
end
local orderedTooltips = {}
local _qid = tonumber(iconData.Id)
if _qid and _Questie_SilentGetQuestLogIndexByID(_qid) <= 0 then
return
end
local _ok = pcall(iconData.ObjectiveData.Update, iconData.ObjectiveData)
if not _ok then
return
end
local _qid = tonumber(iconData.Id)
if _qid and _Questie_SilentGetQuestLogIndexByID(_qid) <= 0 then
return
end
local _ok = pcall(iconData.ObjectiveData.Update, iconData.ObjectiveData)
if not _ok then
return
end
if iconData.Type == "event" then
local tip = _MapIconTooltip:GetEventObjectiveTooltip(icon.data)
@@ -257,26 +258,33 @@ end
local quest = QuestieDB.GetQuest(questData.questId)
local rewardString = ""
if (quest and shift) then
local xpReward = QuestXP:GetQuestLogRewardXP(questData.questId, Questie.db.profile.showQuestXpAtMaxLevel)
local xpReward = QuestXP:GetQuestLogRewardXP(questData.questId,
Questie.db.profile.showQuestXpAtMaxLevel)
if xpReward > 0 then
rewardString = QuestieLib:PrintDifficultyColor(quest.level, "(" .. FormatLargeNumber(xpReward) .. xpString .. ") ", QuestieDB.IsRepeatable(questData.questId), QuestieDB.IsActiveEventQuest(questData.questId), QuestieDB.IsPvPQuest(questData.questId))
rewardString = QuestieLib:PrintDifficultyColor(quest.level,
"(" .. FormatLargeNumber(xpReward) .. xpString .. ") ",
QuestieDB.IsRepeatable(questData.questId),
QuestieDB.IsActiveEventQuest(questData.questId), QuestieDB.IsPvPQuest(questData.questId))
end
local moneyReward = QuestXP.GetQuestRewardMoney(questData.questId)
if moneyReward > 0 then
rewardString = rewardString .. Questie:Colorize("(" .. GetCoinTextureString(moneyReward) .. ") ", "white")
rewardString = rewardString ..
Questie:Colorize("(" .. GetCoinTextureString(moneyReward) .. ") ", "white")
end
end
rewardString = rewardString .. questData.type
if (not shift) and reputationReward and next(reputationReward) then
self:AddDoubleLine(REPUTATION_ICON_TEXTURE .. " " .. questData.title, rewardString, 1, 1, 1, 1, 1, 0);
self:AddDoubleLine(REPUTATION_ICON_TEXTURE .. " " .. questData.title, rewardString, 1, 1, 1, 1, 1,
0);
else
if shift then
self:AddDoubleLine(questData.title, rewardString, 1, 1, 1, 1, 1, 0);
else
-- We use a transparent icon because this eases setting the correct margin
self:AddDoubleLine(TRANSPARENT_ICON_TEXTURE .. " " .. questData.title, rewardString, 1, 1, 1, 1, 1, 0);
self:AddDoubleLine(TRANSPARENT_ICON_TEXTURE .. " " .. questData.title, rewardString, 1, 1, 1,
1, 1, 0);
end
end
end
@@ -284,13 +292,15 @@ end
local dataType = type(questData.subData)
if dataType == "table" then
for _, rawLine in pairs(questData.subData) do
local lines = QuestieLib:TextWrap(rawLine, " ", false, math.max(375, Tooltip:GetWidth()), questData.questId) --275 is the default questlog width
local lines = QuestieLib:TextWrap(rawLine, " ", false, math.max(375, Tooltip:GetWidth()),
questData.questId) --275 is the default questlog width
for _, line in pairs(lines) do
self:AddLine(line, 0.86, 0.86, 0.86);
end
end
elseif dataType == "string" then
local lines = QuestieLib:TextWrap(questData.subData, " ", false, math.max(375, Tooltip:GetWidth())) --275 is the default questlog width
local lines = QuestieLib:TextWrap(questData.subData, " ", false,
math.max(375, Tooltip:GetWidth())) --275 is the default questlog width
for _, line in pairs(lines) do
self:AddLine(line, 0.86, 0.86, 0.86);
end
@@ -303,19 +313,21 @@ end
local nextQuest = QuestieDB.GetQuest(nextQuestInChain)
local firstInChain = true;
while nextQuest ~= nil do
local nextQuestTitleString;
local nextQuestXpRewardString = "";
local nextQuestMoneyRewardString = "";
local nextQuestIdString = "";
local nextQuestTagString = "";
if firstInChain then
self:AddLine(" |T" .. QuestieLib.AddonPath .. "Icons\\nextquest.blp:16|t " .. l10n("Next in chain:"), 0.86, 0.86, 0.86)
self:AddLine(
" |T" .. QuestieLib.AddonPath .. "Icons\\nextquest.blp:16|t " .. l10n("Next in chain:"),
0.86, 0.86, 0.86)
firstInChain = false;
end
if Questie.db.profile.enableTooltipsQuestLevel then
nextQuestTitleString = string.format("%s", QuestieLib:GetLevelString(nextQuest.Id, "", nextQuest.level, true) .. nextQuest.name)
nextQuestTitleString = string.format("%s",
QuestieLib:GetLevelString(nextQuest.Id, "", nextQuest.level, true) .. nextQuest.name)
else
nextQuestTitleString = string.format("%s", nextQuest.name)
end
@@ -324,14 +336,17 @@ end
nextQuestIdString = string.format(" (%d)", nextQuest.Id)
end
local nextQuestXpReward = QuestXP:GetQuestLogRewardXP(nextQuest.Id, Questie.db.profile.showQuestXpAtMaxLevel);
local nextQuestXpReward = QuestXP:GetQuestLogRewardXP(nextQuest.Id,
Questie.db.profile.showQuestXpAtMaxLevel);
if nextQuestXpReward > 0 then
nextQuestXpRewardString = string.format(" (%s%s)", FormatLargeNumber(nextQuestXpReward), xpString);
nextQuestXpRewardString = string.format(" (%s%s)", FormatLargeNumber(nextQuestXpReward),
xpString);
end
local nextQuestMoneyReward = QuestXP:GetQuestRewardMoney(nextQuest.Id);
if nextQuestMoneyReward > 0 then
nextQuestMoneyRewardString = Questie:Colorize(string.format(" (%s)", GetCoinTextureString(nextQuestMoneyReward)), "white");
nextQuestMoneyRewardString = Questie:Colorize(
string.format(" (%s)", GetCoinTextureString(nextQuestMoneyReward)), "white");
end
if (QuestieDB.IsGroupQuest(nextQuest.Id) or QuestieDB.IsDungeonQuest(nextQuest.Id) or QuestieDB.IsRaidQuest(nextQuest.Id)) then
@@ -339,8 +354,12 @@ end
nextQuestTagString = Questie:Colorize(string.format(" (%s)", nextQuestTag), "yellow")
end
local nextQuestString = string.format(" %s%s%s%s%s", nextQuestTitleString, nextQuestIdString, nextQuestXpRewardString, nextQuestMoneyRewardString, nextQuestTagString); -- we need an offset to align with description
self:AddLine(QuestieLib:PrintDifficultyColor(nextQuest.level, nextQuestString, QuestieDB.IsRepeatable(nextQuest.Id), QuestieDB.IsActiveEventQuest(nextQuest.Id), QuestieDB.IsPvPQuest(nextQuest.Id)), 1, 1, 1);
local nextQuestString = string.format(" %s%s%s%s%s", nextQuestTitleString, nextQuestIdString,
nextQuestXpRewardString, nextQuestMoneyRewardString, nextQuestTagString); -- we need an offset to align with description
self:AddLine(
QuestieLib:PrintDifficultyColor(nextQuest.level, nextQuestString,
QuestieDB.IsRepeatable(nextQuest.Id), QuestieDB.IsActiveEventQuest(nextQuest.Id),
QuestieDB.IsPvPQuest(nextQuest.Id)), 1, 1, 1);
nextQuest = QuestieDB.GetQuest(nextQuest.nextQuestInChain)
end
end
@@ -374,7 +393,8 @@ end
aldorPenalty = 0 - math.floor(rewardValue * 1.1)
end
rewardTable[#rewardTable + 1] = (rewardValue > 0 and "+" or "") .. rewardValue .. " " .. factionName
rewardTable[#rewardTable + 1] = (rewardValue > 0 and "+" or "") ..
rewardValue .. " " .. factionName
end
end
@@ -386,7 +406,9 @@ end
rewardTable[#rewardTable + 1] = scryersPenalty .. " " .. factionName
end
self:AddLine(REPUTATION_ICON_TEXTURE .. " " .. Questie:Colorize(table.concat(rewardTable, " / "), "reputationBlue"), 1, 1, 1, 1, 1, 0)
self:AddLine(
REPUTATION_ICON_TEXTURE ..
" " .. Questie:Colorize(table.concat(rewardTable, " / "), "reputationBlue"), 1, 1, 1, 1, 1, 0)
end
end
end
@@ -396,13 +418,16 @@ end
for questId, textList in pairs(self.questOrder) do -- this logic really needs to be improved
---@type Quest
local quest = QuestieDB.GetQuest(questId);
local questTitle = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true);
local questTitle = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true,
true);
local xpReward = QuestXP:GetQuestLogRewardXP(questId, Questie.db.profile.showQuestXpAtMaxLevel);
r, g, b = QuestieLib:GetDifficultyColorPercent(quest.level);
if haveGiver then
if shift and xpReward then
self:AddLine(" ");
self:AddDoubleLine(questTitle, "(" .. FormatLargeNumber(xpReward) .. xpString .. ") (" .. l10n("Active") .. ")", 0.2, 1, 0.2, 1, 1, 0);
self:AddDoubleLine(questTitle,
"(" .. FormatLargeNumber(xpReward) .. xpString .. ") (" .. l10n("Active") .. ")", 0.2, 1, 0.2, 1,
1, 0);
haveGiver = false -- looks better when only the first one shows (active)
else
self:AddLine(" ");
@@ -411,7 +436,8 @@ end
end
else
if (quest and shift and xpReward > 0) then
self:AddDoubleLine(questTitle, "(" .. FormatLargeNumber(xpReward) .. xpString .. ")", 0.2, 1, 0.2, r, g, b);
self:AddDoubleLine(questTitle, "(" .. FormatLargeNumber(xpReward) .. xpString .. ")", 0.2, 1, 0.2, r,
g, b);
firstLine = false;
elseif (firstLine and not shift) then
self:AddDoubleLine(questTitle, "(" .. l10n('Hold Shift') .. ")", 0.2, 1, 0.2, 0.43, 0.43, 0.43); --"(Shift+click)"
@@ -592,9 +618,12 @@ function _MapIconTooltip:GetObjectiveTooltip(icon)
local color = QuestieLib:GetRGBForObjective(iconData.ObjectiveData)
if iconData.ObjectiveData.Needed then
if iconData.ObjectiveData.Type == "spell" and iconData.ObjectiveData.spawnList[iconData.ObjectiveTargetId].ItemId then
text = color .. tostring(QuestieDB.QueryItemSingle(iconData.ObjectiveData.spawnList[iconData.ObjectiveTargetId].ItemId, "name"))
text = color ..
tostring(QuestieDB.QueryItemSingle(iconData.ObjectiveData.spawnList[iconData.ObjectiveTargetId].ItemId,
"name"))
else
text = color .. tostring(iconData.ObjectiveData.Collected) .. "/" .. tostring(iconData.ObjectiveData.Needed) .. " " .. text
text = color ..
tostring(iconData.ObjectiveData.Collected) .. "/" .. tostring(iconData.ObjectiveData.Needed) .. " " .. text
end
end
if QuestieComms then
@@ -617,17 +646,20 @@ function _MapIconTooltip:GetObjectiveTooltip(icon)
if playerColor then
local objectiveEntry = objectiveData[iconData.ObjectiveIndex]
if not objectiveEntry then
Questie:Debug(Questie.DEBUG_DEVELOP, "[_MapIconTooltip:GetObjectiveTooltip] No objective data for quest", quest.Id)
Questie:Debug(Questie.DEBUG_DEVELOP,
"[_MapIconTooltip:GetObjectiveTooltip] No objective data for quest", quest.Id)
objectiveEntry = {} -- This will make "GetRGBForObjective" return default color
end
local remoteColor = QuestieLib:GetRGBForObjective(objectiveEntry)
local colorizedPlayerName = " (" .. playerColor .. playerName .. "|r" .. remoteColor .. ")|r" .. playerType
local colorizedPlayerName = " (" ..
playerColor .. playerName .. "|r" .. remoteColor .. ")|r" .. playerType
local remoteText = iconData.ObjectiveData.Description
if objectiveEntry and objectiveEntry.fulfilled and objectiveEntry.required then
local fulfilled = objectiveEntry.fulfilled;
local required = objectiveEntry.required;
remoteText = remoteColor .. tostring(fulfilled) .. "/" .. tostring(required) .. " " .. remoteText .. colorizedPlayerName;
remoteText = remoteColor ..
tostring(fulfilled) .. "/" .. tostring(required) .. " " .. remoteText .. colorizedPlayerName;
else
remoteText = remoteColor .. remoteText .. colorizedPlayerName;
end
+20 -18
View File
@@ -259,12 +259,12 @@ function _QuestieTooltips:AddUnitDataToTooltip()
local type, _, _, _, _, npcId, _ = strsplit("-", guid or "");
if name and (type == "Creature" or type == "Vehicle") and (
name ~= QuestieTooltips.lastGametooltipUnit or
(not QuestieTooltips.lastGametooltipCount) or
_QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or
QuestieTooltips.lastGametooltipType ~= "monster" or
lastGuid ~= guid
) then
name ~= QuestieTooltips.lastGametooltipUnit or
(not QuestieTooltips.lastGametooltipCount) or
_QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or
QuestieTooltips.lastGametooltipType ~= "monster" or
lastGuid ~= guid
) then
QuestieTooltips.lastGametooltipUnit = name
local tooltipData = QuestieTooltips:GetTooltip("m_" .. npcId);
@@ -273,7 +273,7 @@ function _QuestieTooltips:AddUnitDataToTooltip()
if Questie.db.profile.enableTooltipsNPCID == true then
GameTooltip:AddDoubleLine("NPC ID", "|cFFFFFFFF" .. npcId .. "|r")
end
for _, v in pairs (tooltipData) do
for _, v in pairs(tooltipData) do
GameTooltip:AddLine(v)
end
else
@@ -307,23 +307,25 @@ function _QuestieTooltips:AddItemDataToTooltip()
local name, link = self:GetItem()
local itemId
if link then
itemId = select(3, string.match(link, "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%d*):?(%d*):?(%-?%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?"))
itemId = select(3,
string.match(link,
"|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%d*):?(%d*):?(%-?%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?"))
end
if name and itemId and (
name ~= QuestieTooltips.lastGametooltipItem or
(not QuestieTooltips.lastGametooltipCount) or
_QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or
QuestieTooltips.lastGametooltipType ~= "item" or
lastItemId ~= itemId or
QuestieTooltips.lastFrameName ~= self:GetName()
) then
name ~= QuestieTooltips.lastGametooltipItem or
(not QuestieTooltips.lastGametooltipCount) or
_QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or
QuestieTooltips.lastGametooltipType ~= "item" or
lastItemId ~= itemId or
QuestieTooltips.lastFrameName ~= self:GetName()
) then
QuestieTooltips.lastGametooltipItem = name
local tooltipData = QuestieTooltips:GetTooltip("i_" .. (itemId or 0));
if tooltipData then
if Questie.db.profile.enableTooltipsItemID == true then
GameTooltip:AddDoubleLine("Item ID", "|cFFFFFFFF" .. itemId .. "|r")
end
for _, v in pairs (tooltipData) do
for _, v in pairs(tooltipData) do
self:AddLine(v)
end
end
@@ -363,7 +365,7 @@ function _QuestieTooltips:AddObjectDataToTooltip(name)
if tooltipData[2] then
-- Quest has objectives
for index, line in pairs (tooltipData) do
for index, line in pairs(tooltipData) do
if index > 1 and (not alreadyAddedObjectiveLines[line]) then -- skip the first entry, it's the title
local _, _, acquired, needed = string.find(line, "(%d+)/(%d+)")
-- We need "tonumber", because acquired can contain parts of the color string
@@ -386,7 +388,7 @@ end
function _QuestieTooltips:CountTooltip()
local tooltipCount = 0
for i = 1, GameTooltip:NumLines() do
local frame = _G["GameTooltipTextLeft"..i]
local frame = _G["GameTooltipTextLeft" .. i]
if frame and frame:GetText() then
tooltipCount = tooltipCount + 1
else
-1
View File
@@ -2278,7 +2278,6 @@ function QuestieTracker.RemoveQuestWatch(index, isQuestie)
QuestieTracker:UntrackQuestId(questId)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - by Blizzard", questId)
end
end
else
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - by Questie")
+18 -15
View File
@@ -70,7 +70,8 @@ local bindTruthTable = {
['disabled'] = function() return false end,
}
local _QuestLogScrollBar = QuestLogScrollFrameScrollBar or QuestLogListScrollFrame.ScrollBar or QuestLogListScrollFrameScrollBar
local _QuestLogScrollBar = QuestLogScrollFrameScrollBar or QuestLogListScrollFrame.ScrollBar or
QuestLogListScrollFrameScrollBar
---@param quest table The table provided by QuestieDB.GetQuest(questId)
function TrackerUtils:ShowQuestLog(quest)
@@ -107,7 +108,6 @@ function TrackerUtils:ShowQuestLog(quest)
QuestLog_Update()
end
---@param title string The name of the WayPoint
---@param zone number The zone ID number
---@param x number X coordinate
@@ -670,7 +670,8 @@ local function _GetZoneName(zoneOrSort, questId)
else
-- The quest has no explicit zone or category. Fallback to "Unknown Zone"
zoneName = "Unknown Zone"
Questie:Debug(Questie.DEBUG_CRITICAL, "[TrackerUtils:_GetZoneName] zoneOrSort", zoneOrSort, "of quest", questId, "is not in the Database!")
Questie:Debug(Questie.DEBUG_CRITICAL, "[TrackerUtils:_GetZoneName] zoneOrSort", zoneOrSort, "of quest",
questId, "is not in the Database!")
end
else
-- Let's create custom Zones based on Sorting type.
@@ -890,15 +891,18 @@ function TrackerUtils:GetSortedQuestIds()
local playerPosition
questZoneProximityTimer = C_Timer.NewTicker(5.0, function()
if IsInInstance() and questZoneProximityTimer then
Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerUtils:GetSortedQuestIds] - Zone Proximity Timer Stoped!")
Questie:Debug(Questie.DEBUG_DEVELOP,
"[TrackerUtils:GetSortedQuestIds] - Zone Proximity Timer Stoped!")
questZoneProximityTimer:Cancel()
questZoneProximityTimer = nil
else
local position = _GetWorldPlayerPosition()
if position then
local distance = playerPosition and _GetDistance(position.x, position.y, playerPosition.x, playerPosition.y)
local distance = playerPosition and
_GetDistance(position.x, position.y, playerPosition.x, playerPosition.y)
if not distance or distance > 0.01 then -- Position has changed
Questie:Debug(Questie.DEBUG_SPAM, "[TrackerUtils:GetSortedQuestIds] - Zone Proximity Timer Updated!")
Questie:Debug(Questie.DEBUG_SPAM,
"[TrackerUtils:GetSortedQuestIds] - Zone Proximity Timer Updated!")
playerPosition = position
local orderCopy = {}
@@ -1006,9 +1010,11 @@ function TrackerUtils:GetSortedQuestIds()
else
local position = _GetWorldPlayerPosition()
if position then
local distance = playerPosition and _GetDistance(position.x, position.y, playerPosition.x, playerPosition.y)
local distance = playerPosition and
_GetDistance(position.x, position.y, playerPosition.x, playerPosition.y)
if not distance or distance > 0.01 then -- Position has changed
Questie:Debug(Questie.DEBUG_SPAM, "[TrackerUtils:GetSortedQuestIds] - Proximity Timer Updated!")
Questie:Debug(Questie.DEBUG_SPAM,
"[TrackerUtils:GetSortedQuestIds] - Proximity Timer Updated!")
playerPosition = position
local orderCopy = {}
@@ -1119,16 +1125,13 @@ function TrackerUtils:UpdateVoiceOverPlayButtons()
end
for i = 1, 75 do
local title, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(i)
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(i)
if not (title and questId) then
break
end
if not isHeader then
if title and questId and (not isHeader) then
if not VoiceOver.QuestOverlayUI.questPlayButtons[questId] then
VoiceOver.QuestOverlayUI:CreatePlayButton(questId)
table.insert(VoiceOver.QuestOverlayUI.displayedButtons, VoiceOver.QuestOverlayUI.questPlayButtons[questId])
table.insert(VoiceOver.QuestOverlayUI.displayedButtons,
VoiceOver.QuestOverlayUI.questPlayButtons[questId])
end
end
end
+1 -1
View File
@@ -6,7 +6,7 @@
## Notes-esES: Ayundante de misión
## Notes-ptBR: Ajudante de missão
## Notes-frFR: Assistant de quête
## Version: 9.8.6
## Version: 9.8.9
## RequiredDeps:
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
## SavedVariables: QuestieConfig
-7
View File
@@ -1,7 +0,0 @@
## New Ebonhold Quests
- **Outland**: Blade's Edge Trophy, Fel Scars of Nagrand, Netherstorm Trophy
- **Northrend**: Borean Trophy
- **Kalimdor**: Teldrassil Trophy, Darkshore Defense, Bloodmyst Recovery
- **Azeroth**: Arathi Trophy, Elwynn Errands, Modan Trophy, Redridge Resolve, Westfall Trophy, Wetlands Trophy
Includes precise spawn tracking data overrides for numerous rare NPCs across these zones.
+193 -134
View File
@@ -11,22 +11,26 @@ This workflow aggregates vital information from the [Questie Wiki](https://githu
**Wiki Page:** [Contributing](https://github.com/Questie/Questie/wiki/Contributing)
### Setup
* **Fork & Clone:** Fork the repo and clone it *outside* your WoW directory.
* **Symlink:** Create a symlink from your Clone to `Interface/AddOns/Questie`.
* **Windows:** `mklink /J "C:\Path\To\WoW\_classic_\Interface\AddOns\Questie" "C:\Dev\Questie"`
* **MacOS:** `ln -s ~/Dev/Questie /Applications/World\ of\ Warcraft/_classic_/Interface/AddOns/Questie`
* **IDE:** Recommended: IntelliJ + EmmyLua or VSCode + Lua Extension (sumneko).
* **Fork & Clone:** Fork the repo and clone it *outside* your WoW directory.
* **Symlink:** Create a symlink from your Clone to `Interface/AddOns/Questie`.
* **Windows:** `mklink /J "C:\Path\To\WoW\_classic_\Interface\AddOns\Questie" "C:\Dev\Questie"`
* **MacOS:** `ln -s ~/Dev/Questie /Applications/World\ of\ Warcraft/_classic_/Interface/AddOns/Questie`
* **IDE:** Recommended: IntelliJ + EmmyLua or VSCode + Lua Extension (sumneko).
### Commit Messages (Changelog)
Prefix commits to auto-generate changelog entries:
* `[quest]` e.g., `[quest] Fix pre quest for "Warm Welcome"`
* `[db]` e.g., `[db] Fix location of "Lar'korwi"`
* `[fix]` e.g., `[fix] Fix DMF dates for Era`
* `[feature]` e.g., `[feature] Add gold reward`
* `[locale]` e.g., `[locale] Add translation for "Next in chain"`
* `[quest]` e.g., `[quest] Fix pre quest for "Warm Welcome"`
* `[db]` e.g., `[db] Fix location of "Lar'korwi"`
* `[fix]` e.g., `[fix] Fix DMF dates for Era`
* `[feature]` e.g., `[feature] Add gold reward`
* `[locale]` e.g., `[locale] Add translation for "Next in chain"`
### Research & Documentation
* **Export Path:** Always copy research files (task.md, walkthrough.md, implementation_plan.md, and transcript) to `C:\Users\kance\Documents\Research\Questie\[Task-Name]`.
* **Export Path:** Always copy research files (task.md, walkthrough.md, implementation_plan.md, and transcript) to `C:\Users\kance\Documents\Research\Questie\[Task-Name]`.
---
@@ -37,18 +41,21 @@ Prefix commits to auto-generate changelog entries:
Questie employs a modular architecture where specialized systems handle different aspects of the addon.
### Core Modules
* **Initialization:** `Modules/QuestieInit.lua` - Manages the staged loading process.
* **Database:** `Database/QuestieDB.lua` - Central hub for quest, NPC, item, and object data.
* **Map Integration:** `Modules/Map/QuestieMap.lua` - Handles icon placement and world map/minimap logic.
* **Tracking:** `Modules/Tracker/QuestieTracker.lua` - Manages the on-screen quest objective tracker.
* **Journey:** `Modules/Journey/QuestieJourney.lua` - Handles the quest history and search UI.
* **Corrections:** `Database/Corrections/QuestieCorrections.lua` - Applies patches to the base game data.
* **Communication:** `Modules/Network/QuestieComms.lua` - Manages data sharing between party members.
* **Initialization:** `Modules/QuestieInit.lua` - Manages the staged loading process.
* **Database:** `Database/QuestieDB.lua` - Central hub for quest, NPC, item, and object data.
* **Map Integration:** `Modules/Map/QuestieMap.lua` - Handles icon placement and world map/minimap logic.
* **Tracking:** `Modules/Tracker/QuestieTracker.lua` - Manages the on-screen quest objective tracker.
* **Journey:** `Modules/Journey/QuestieJourney.lua` - Handles the quest history and search UI.
* **Corrections:** `Database/Corrections/QuestieCorrections.lua` - Applies patches to the base game data.
* **Communication:** `Modules/Network/QuestieComms.lua` - Manages data sharing between party members.
### Addon Initialization Flow
Questie uses a coroutine-based staged initialization to minimize performance impact:
1. **Stage 1: Data & Validation** - Loads database, applies corrections, and runs validators.
2. **Stage 3 (Stage 2 is internal wait): UI & Hooks** - Initializes trackers, maps, event handlers, and UI elements.
1. **Stage 1: Data & Validation** - Loads database, applies corrections, and runs validators.
2. **Stage 3 (Stage 2 is internal wait): UI & Hooks** - Initializes trackers, maps, event handlers, and UI elements.
**CRITICAL NOTE ON GLOBAL NAMESPACES:** Modules are injected into the global namespace via `QuestieCompat.PopulateGlobals()` during `ADDON_LOADED` or `PLAYER_LOGIN`. Event handlers (like `CHAT_MSG_SYSTEM` for auto-turn-in quests) can fire *before* global variables are injected depending on client load orders. **Always use localized module imports** (e.g., `local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer")`) at the top of files to prevent `attempt to index global '<Module>' (a nil value)` exceptions.
@@ -61,50 +68,56 @@ Questie uses a coroutine-based staged initialization to minimize performance imp
The database system is a central component that stores and manages all quest-related information.
### Database Categories
The database consists of four primary data types:
* **Quest Data:** Information about quest requirements, objectives, rewards, etc.
* **NPC Data:** Information about non-player characters, including spawn locations.
* **Item Data:** Information about items, including where they drop.
* **Object Data:** Information about world objects like chests, mining nodes, etc.
* **Quest Data:** Information about quest requirements, objectives, rewards, etc.
* **NPC Data:** Information about non-player characters, including spawn locations.
* **Item Data:** Information about items, including where they drop.
* **Object Data:** Information about world objects like chests, mining nodes, etc.
### Database Structure
* **Base Data:** Located in `Database/<Expansion>/` (e.g., `classicQuestDB.lua`). **DO NOT EDIT DIRECTLY.**
* **Corrections:** Located in `Database/Corrections/`. THIS is where you make changes.
* **Expansion NPC Database Fields (e.g., `wotlkNpcDB.lua`):**
* Index 1: `name`
* Index 7: `spawns` - Format: `{[zoneID] = { {x,y}, ... }}`
* Index 9: `zoneID` (Primary zone)
* Index 12: `factionID`
* **Base Data:** Located in `Database/<Expansion>/` (e.g., `classicQuestDB.lua`). **DO NOT EDIT DIRECTLY.**
* **Corrections:** Located in `Database/Corrections/`. THIS is where you make changes.
* **Expansion NPC Database Fields (e.g., `wotlkNpcDB.lua`):**
* Index 1: `name`
* Index 7: `spawns` - Format: `{[zoneID] = { {x,y}, ... }}`
* Index 9: `zoneID` (Primary zone)
* Index 12: `factionID`
### Zone & Area Management
`Database/Zones/zoneDB.lua` manages geographic data:
* **UI Map Mapping:** Maps internal Area IDs to UI Map IDs.
* **Dungeon Locations:** Tracks entrance coordinates for instances.
* **Subzone Mapping:** Relates subzones to their parent zones for proper icon inheritance.
* **UI Map Mapping:** Maps internal Area IDs to UI Map IDs.
* **Dungeon Locations:** Tracks entrance coordinates for instances.
* **Subzone Mapping:** Relates subzones to their parent zones for proper icon inheritance.
### NPC Data Verification Best Practices
When implementing custom quests that require NPC spawn data:
1. **Verify NPC vs Object Classification**
- Always check both `wotlkNpcDB.lua` AND `wotlkObjectDB.lua`
- Wowhead may list objects in NPC search results (e.g., "Dragonblight Mage Hunter" ID 32572)
- Use grep search: `grep -r "^\[NPC_ID\]" Database/Wotlk/` to confirm classification
* Always check both `wotlkNpcDB.lua` AND `wotlkObjectDB.lua`
* Wowhead may list objects in NPC search results (e.g., "Dragonblight Mage Hunter" ID 32572)
* Use grep search: `grep -r "^\[NPC_ID\]" Database/Wotlk/` to confirm classification
2. **Spawn Data Completeness Verification**
- NPCs in database may have nil spawns or incomplete spawn coordinates
- Always compare scraped spawn counts with existing database entries
- Common issues:
- NPCs with `nil` spawn data (no coordinates at all)
- NPCs with sparse spawn data (DB has 5 spawns, Wowhead shows 19)
- NPCs with single spawn when multiple exist
* NPCs in database may have nil spawns or incomplete spawn coordinates
* Always compare scraped spawn counts with existing database entries
* Common issues:
* NPCs with `nil` spawn data (no coordinates at all)
* NPCs with sparse spawn data (DB has 5 spawns, Wowhead shows 19)
* NPCs with single spawn when multiple exist
3. **Individual NPC Page Scraping**
- Scrape each NPC's individual Wowhead page for spawn coordinates
- Use JavaScript to extract from `g_mapperData[zoneId][0].coords`
- Don't rely on aggregate list views - they may not show all spawns
* Scrape each NPC's individual Wowhead page for spawn coordinates
* Use JavaScript to extract from `g_mapperData[zoneId][0].coords`
* Don't rely on aggregate list views - they may not show all spawns
4. **Database Comparison Process**
```bash
# Search for NPC in database
grep "^\[NPC_ID\]" Database/Wotlk/wotlkNpcDB.lua
@@ -114,17 +127,17 @@ When implementing custom quests that require NPC spawn data:
```
5. **Common Spawn Data Issues Found**
- **Infinite Dragonflight NPCs**: Often have nil spawns (dungeon/instance spawns not in overworld)
- **Quest Givers/Vendors**: Usually have 1 spawn (correct)
- **Patrol NPCs**: May have waypoint data instead of static spawns
- **Rare Spawns**: May have fewer coordinates than common NPCs
* **Infinite Dragonflight NPCs**: Often have nil spawns (dungeon/instance spawns not in overworld)
* **Quest Givers/Vendors**: Usually have 1 spawn (correct)
* **Patrol NPCs**: May have waypoint data instead of static spawns
* **Rare Spawns**: May have fewer coordinates than common NPCs
6. **Example: Quest 50064 Findings**
- 50 NPCs extracted from Wowhead
- 1 was an object (excluded)
- 6 NPCs had nil spawns in database
- 3 NPCs had incomplete spawn data (11 in DB vs 42 scraped)
- Result: 49 valid NPCs, spawn data inconsistencies documented
* 50 NPCs extracted from Wowhead
* 1 was an object (excluded)
* 6 NPCs had nil spawns in database
* 3 NPCs had incomplete spawn data (11 in DB vs 42 scraped)
* Result: 49 valid NPCs, spawn data inconsistencies documented
---
@@ -135,26 +148,29 @@ When implementing custom quests that require NPC spawn data:
Corrections patch the runtime database to fix incomplete or incorrect game data.
### Loading Order
1. **Base Data** for the running expansion is loaded (e.g., TBC).
2. **Corrections** are applied in order: Classic -> TBC -> WotLK.
* TBC corrections apply to WotLK.
* Classic corrections apply to TBC and WotLK.
* *Note:* Corrections in later files can overwrite earlier ones.
1. **Base Data** for the running expansion is loaded (e.g., TBC).
2. **Corrections** are applied in order: Classic -> TBC -> WotLK.
* TBC corrections apply to WotLK.
* Classic corrections apply to TBC and WotLK.
* *Note:* Corrections in later files can overwrite earlier ones.
### NPC Correction File Structure
**CRITICAL**: NPC correction files (e.g., `tbcNPCFixes.lua`) have TWO sections:
1. **Main `Load()` Function** (lines 9-1064): Returns a table of corrections that apply to **ALL players** (both factions)
- **Use this for**: General NPC spawn data, quest fixes, waypoints that apply to everyone
- **Example**: Adding missing spawn coordinates for dragonkin NPCs
* **Use this for**: General NPC spawn data, quest fixes, waypoints that apply to everyone
* **Example**: Adding missing spawn coordinates for dragonkin NPCs
2. **Faction-Specific `LoadFactionFixes()` Function** (lines 1068+): Returns faction-specific corrections
- **Use this for**: NPCs that only appear for Horde or Alliance (e.g., starting zone NPCs)
- **Structure**: Returns `npcFixesHorde` or `npcFixesAlliance` based on player faction
* **Use this for**: NPCs that only appear for Horde or Alliance (e.g., starting zone NPCs)
* **Structure**: Returns `npcFixesHorde` or `npcFixesAlliance` based on player faction
**When adding NPC spawn data**: Add it to the **main `Load()` function** unless the NPC is faction-specific.
### Syntax
```lua
-- Database/Corrections/tbcQuestFixes.lua
[8300] = {
@@ -164,71 +180,82 @@ Corrections patch the runtime database to fix incomplete or incorrect game data.
```
### Critical Keys (QuestieDB.questKeys)
* `startedBy` / `finishedBy`: `{ {Creature_IDs}, {Object_IDs}, {Item_IDs} }`.
* Use `nil` for empty slots. Example: `{{123}, nil, nil}`.
* `preQuestSingle`: List of required pre-quests.
* `exclusiveTo`: List of quests that hide this one.
* `nextQuestInChain`: ID of the next quest.
* `requiredLevel` (Index [4]): Minimum level required to accept the quest.
* `questLevel` (Index [5]): The actual level of the quest (determines XP/Colors).
* `requiredRaces` (Index [6]): Bitmask (use `QuestieDB.raceKeys`).
* **`zoneOrSort` (Index [17])**: **Zone ID** (positive for Area Table ID) or Quest Sort ID (negative). **This is the zone field for custom quests.**
* **WARNING:** Do NOT use Index [6] (`requiredRaces`) for Zone ID. This is a common mistake that causes "Unknown Zone" errors.
* `startedBy` / `finishedBy`: `{ {Creature_IDs}, {Object_IDs}, {Item_IDs} }`.
* Use `nil` for empty slots. Example: `{{123}, nil, nil}`.
* `preQuestSingle`: List of required pre-quests.
* `exclusiveTo`: List of quests that hide this one.
* `nextQuestInChain`: ID of the next quest.
* `requiredLevel` (Index [4]): Minimum level required to accept the quest.
* `questLevel` (Index [5]): The actual level of the quest (determines XP/Colors).
* `requiredRaces` (Index [6]): Bitmask (use `QuestieDB.raceKeys`).
* **`zoneOrSort` (Index [17])**: **Zone ID** (positive for Area Table ID) or Quest Sort ID (negative). **This is the zone field for custom quests.**
* **WARNING:** Do NOT use Index [6] (`requiredRaces`) for Zone ID. This is a common mistake that causes "Unknown Zone" errors.
---
## 5. Quest Tracking & Map Systems
### Quest Tracker Components
The tracker UI consists of several key components:
* **TrackerBaseFrame:** The main frame that contains the entire tracker.
* **TrackerHeaderFrame:** Contains the tracker's header with title and buttons.
* **TrackerQuestFrame:** Manages the display of individual quests.
* **TrackerLinePool:** A pool of line elements for quest objectives and text.
* **TrackerBaseFrame:** The main frame that contains the entire tracker.
* **TrackerHeaderFrame:** Contains the tracker's header with title and buttons.
* **TrackerQuestFrame:** Manages the display of individual quests.
* **TrackerLinePool:** A pool of line elements for quest objectives and text.
### Combat Queue System (`QuestieCombatQueue.lua`)
**CRITICAL:** Avoids UI "taint" and errors by queuing operations during combat:
* Queues operations requested during combat.
* Executes queued operations immediately upon leaving combat (`PLAYER_REGEN_ENABLED`).
* Essential for preventing interference with Blizzard's protected combat frames.
* Queues operations requested during combat.
* Executes queued operations immediately upon leaving combat (`PLAYER_REGEN_ENABLED`).
* Essential for preventing interference with Blizzard's protected combat frames.
### Map Icon & Tooltip System
* **Frame Pooling:** Icons are managed via `QuestieFramePool` to reuse frames and reduce memory overhead.
* **Icon Frames:** `QuestieFrame` displays the actual markers.
* **HBD Integration:** Uses `HereBeDragons` (via `HBDHooks.lua`) for coordinate conversions.
* **Tooltip System:** `MapIconTooltip` and `TooltipHandler` provide hover info, including party member progress.
* **Frame Pooling:** Icons are managed via `QuestieFramePool` to reuse frames and reduce memory overhead.
* **Icon Frames:** `QuestieFrame` displays the actual markers.
* **HBD Integration:** Uses `HereBeDragons` (via `HBDHooks.lua`) for coordinate conversions.
* **Tooltip System:** `MapIconTooltip` and `TooltipHandler` provide hover info, including party member progress.
### QuestieArrow & Finisher Tracking (`QuestieArrow.lua`)
* **Finisher Waypoints:** The Questie Arrow (`QuestieArrow:UpdateNearestTargets`) relies on `quest.isComplete` and `QuestieDB.IsComplete` to switch from targeting objectives to targeting the quest turn-in (Finisher) NPC or Object.
* **Object Properties Limit:** When evaluating the exact finisher location, Questie quest objects contain limited attributes for the Finisher: only `.Id` and `.Type` (`"monster"` or `"object"`). Any attempt to index `quest.Finisher.Name` will fail and cause finisher logic to be skipped entirely.
* **Distance Checks:** `_CollectFinisherSpawns` evaluates `HBD:GetWorldDistance`. If the finisher is in another zone and the player has auto-tracking enabled (`usingAutoLogic and zone ~= playerZoneId`), QuestieArrow will intentionally hide distant finisher marks.
* **Finisher Waypoints:** The Questie Arrow (`QuestieArrow:UpdateNearestTargets`) relies on `quest.isComplete` and `QuestieDB.IsComplete` to switch from targeting objectives to targeting the quest turn-in (Finisher) NPC or Object.
* **Object Properties Limit:** When evaluating the exact finisher location, Questie quest objects contain limited attributes for the Finisher: only `.Id` and `.Type` (`"monster"` or `"object"`). Any attempt to index `quest.Finisher.Name` will fail and cause finisher logic to be skipped entirely.
* **Distance Checks:** `_CollectFinisherSpawns` evaluates `HBD:GetWorldDistance`. If the finisher is in another zone and the player has auto-tracking enabled (`usingAutoLogic and zone ~= playerZoneId`), QuestieArrow will intentionally hide distant finisher marks.
### Completion State Evaluation (`QuestieDB.IsComplete`)
* **Server vs Client State:** The `finished` flag on quest objectives is governed by server `GetQuestLogTitle` packets. Because quests consuming items (e.g. freeing prisoners with keys) update numeric counts instantly but delay the `finished` flag until turn-in time, Questie manually evaluates completeness.
* **Logical Evaluation Bug**: When implementing loops iterating numerical objectives, never use inline ternary operators like `(object[1] and 0)` right at the start, as Lua will evaluate existency strings as truthy and short-circuit return 0 regardless of actually finishing the `numFulfilled == numRequired` checks. Iterate the full list and verify `allDone` instead.
* **Server vs Client State:** The `finished` flag on quest objectives is governed by server `GetQuestLogTitle` packets. Because quests consuming items (e.g. freeing prisoners with keys) update numeric counts instantly but delay the `finished` flag until turn-in time, Questie manually evaluates completeness.
* **Logical Evaluation Bug**: When implementing loops iterating numerical objectives, never use inline ternary operators like `(object[1] and 0)` right at the start, as Lua will evaluate existency strings as truthy and short-circuit return 0 regardless of actually finishing the `numFulfilled == numRequired` checks. Iterate the full list and verify `allDone` instead.
### Objective Priority & Hide Conditions
* **HideCondition Mechanism**: Allows hiding specific objectives (regular or extra) based on the player's quest log status.
* `hideIfQuestActive`: Hides the objective if the specified Quest ID is in the player's log OR already completed.
* `hideIfQuestComplete`: Hides the objective only if the specified Quest ID is already completed.
* **Implementation**:
* **Regular Objectives** (Monsters, Objects, Items): The `HideCondition` is stored as the **3rd field** in the objective sub-table (e.g., `{{NPC_ID, nil, {["hideIfQuestActive"] = 12966}}}`).
* **Extra Objectives** (Index 27): The `HideCondition` is stored as the **6th field** in the extra objective sub-table.
* **Immediate UI Feedback**: When a quest acceptance should immediately hide another objective or pivot the arrow, call `QuestieArrow:Refresh()` within the `AcceptQuest` TaskQueue in `QuestieQuest.lua`.
* **HideCondition Mechanism**: Allows hiding specific objectives (regular or extra) based on the player's quest log status.
* `hideIfQuestActive`: Hides the objective if the specified Quest ID is in the player's log OR already completed.
* `hideIfQuestComplete`: Hides the objective only if the specified Quest ID is already completed.
* **Implementation**:
* **Regular Objectives** (Monsters, Objects, Items): The `HideCondition` is stored as the **3rd field** in the objective sub-table (e.g., `{{NPC_ID, nil, {["hideIfQuestActive"] = 12966}}}`).
* **Extra Objectives** (Index 27): The `HideCondition` is stored as the **6th field** in the extra objective sub-table.
* **Immediate UI Feedback**: When a quest acceptance should immediately hide another objective or pivot the arrow, call `QuestieArrow:Refresh()` within the `AcceptQuest` TaskQueue in `QuestieQuest.lua`.
---
## 6. Additional Systems
### Journey System
* **My Journey:** A chronological record of the player's quest history.
* **Quests By Zone:** Browse quests by zone, filtering by availability and completion status.
* **Search:** Advanced search for quests, NPCs, items, and objects by name or ID.
* **My Journey:** A chronological record of the player's quest history.
* **Quests By Zone:** Browse quests by zone, filtering by availability and completion status.
* **Search:** Advanced search for quests, NPCs, items, and objects by name or ID.
### Network Communication
* **QuestieComms:** Manages data sharing between party members.
* **QuestieSerializer:** Converts complex tables to strings for transmission.
* **QuestieAnnounce:** Provides options for announcing quest progress to chat.
* **QuestieComms:** Manages data sharing between party members.
* **QuestieSerializer:** Converts complex tables to strings for transmission.
* **QuestieAnnounce:** Provides options for announcing quest progress to chat.
---
@@ -237,6 +264,7 @@ The tracker UI consists of several key components:
**Wiki Page:** [Localization](https://github.com/Questie/Questie/wiki/Localization-to-more-languages)
To test or add translations locally, use `QUESTIE_LOCALES_OVERRIDE` in a global scope (e.g. `Questie.lua` top):
```lua
QUESTIE_LOCALES_OVERRIDE = {
locale = 'deDE',
@@ -257,16 +285,20 @@ QUESTIE_LOCALES_OVERRIDE = {
**Wiki Page:** [Debugging](https://github.com/Questie/Questie/wiki/Debugging)
* **Mocking Player State:** Add to top of `Questie.lua`:
* **Mocking Player State:** Add to top of `Questie.lua`:
```lua
UnitLevel = function() return 10; end
UnitRace = function() return "nightelf", "nightelf"; end
```
* **Module Access:**
* **Module Access:**
```lua
QuestieLoader:ImportModule("QuestieDB").GetQuest(123)
```
* **Verification:** Enable "Advanced Options" -> "Debug", open Journey, use Search tab to see the *final* data after corrections.
* **Verification:** Enable "Advanced Options" -> "Debug", open Journey, use Search tab to see the *final* data after corrections.
---
@@ -275,55 +307,75 @@ QUESTIE_LOCALES_OVERRIDE = {
### Manual Spawn Extraction (Traditional Method)
**Page:** [Extracting Spawns](https://github.com/Questie/Questie/wiki/Extracting-spawn-locations-from-wowhead)
* Use the Javascript snippet on Wowhead maps to generate Lua table output `{{x,y},...}}`.
* Use the Javascript snippet on Wowhead maps to generate Lua table output `{{x,y},...}}`.
### Automated NPC Extraction (Browser Automation)
For quests requiring all NPCs of a specific type in a zone (e.g., "kill all beasts"), use browser automation to extract NPC IDs from Wowhead's database.
**Wowhead Filter URL Format:**
```
https://www.wowhead.com/wotlk/npcs/beasts?filter=6;ZONE_ID;0
```
- Replace `beasts` with the creature type (beasts, humanoids, dragonkin, etc.)
- Replace `ZONE_ID` with the zone ID (e.g., 3522 for Blade's Edge Mountains, 3523 for Netherstorm)
* Replace `ZONE_ID` with the zone ID (e.g., 3522 for Blade's Edge Mountains, 3523 for Netherstorm)
**Extraction Method:**
1. Navigate to the filtered Wowhead URL
2. Use JavaScript to access Wowhead's internal data:
```javascript
const listview = Object.values(g_listviews).find(lv => lv.id === 'npcs');
listview.data.map(npc => npc.id + ' - ' + npc.name).join('\n');
```
3. This returns all NPC IDs and names matching the filter
**Example Use Cases:**
- Quest 50085 (Savage Heights): Extracted 47 beast NPCs from Blade's Edge Mountains
- Quest 50086 (Unstable Fauna): Extracted 18 beast NPCs from Netherstorm
* Quest 50085 (Savage Heights): Extracted 47 beast NPCs from Blade's Edge Mountains
* Quest 50086 (Unstable Fauna): Extracted 18 beast NPCs from Netherstorm
**Benefits:**
- Faster than manual lookup
- Ensures no NPCs are missed
- Provides complete list for `killCreditObjective` implementation
* Faster than manual lookup
* Ensures no NPCs are missed
* Provides complete list for `killCreditObjective` implementation
---
Custom server data should **NOT** use the Corrections system. Use separate database modules (e.g., `Database/Ebonhold/`).
* **Quests:** Added to `EbonholdDB.questData` (or `EbonholdQuestDB.questData`).
* **NPCs:** Added to `EbonholdDB.npcData`.
* **Quests:** Added to `EbonholdDB.questData` (or `EbonholdQuestDB.questData`).
* **NPCs:** Added to `EbonholdDB.npcData`.
### Missing Base Data (Upstream Preservation)
If the original database files (standard Questie DB) are missing data (e.g. missing NPC spawns in TBC content), **DO NOT** add this data to the standard `Database/Corrections` folder.
Instead, add the missing data to the **specific server's database folder** (e.g., `Database/Ebonhold/`).
* **Reason:** Updating the main Questie database files (including standard corrections) will wipe local changes. Custom server database files are preserved during updates.
* **Reason:** Updating the main Questie database files (including standard corrections) will wipe local changes. Custom server database files are preserved during updates.
**CRITICAL OVERRIDE WARNING FOR CUSTOM DBs:**
When adding missing coordinate data to pre-existing base NPCs via `EbonholdNpcDB.lua`, the custom database engine **does not perform a deep merge**. If you inject an entry that only defines the `[npcKeys.spawns]` array, it will obliterate the entire base NPC node (wiping its name, health, rank, and other metrics). This results in `attempt to index nil` errors rendering the Map Tooltips.
To safely override base coordinates, you must:
1. Copy the **entire** data array for the NPC from the `classicNpcDB.lua` or `wotlkNpcDB.lua` into your custom `EbonholdNpcDB.lua` and enrich it there.
2. OR, if injecting massive coordinates is necessary without full transcribing, they can be injected into the base files (`Database/Corrections/wotlkNPCFixes.lua` deep-merges correctly), understanding that these edits are at risk of being wiped during a standard Questie version upgrade.
### Critical Rules for Custom Quests
#### 1. Never Duplicate Existing NPCs/Objects
NPCs/objects from base WotLK/TBC/Classic databases already exist with spawn data. Only add to custom DB if it's truly custom content.
#### 2. Object Data Structure - Use Indexed Fields
**✅ CORRECT:**
```lua
[600600] = {
[1] = "Elemental Shrine", -- Indexed fields
@@ -341,6 +393,7 @@ NPCs/objects from base WotLK/TBC/Classic databases already exist with spawn data
**Solution:** Use `killCreditObjective` (objectives[5]) to map multiple NPC IDs to a single counter while showing all NPC spawns on the map.
**Structure:**
```lua
[10] = { -- objectives
nil, -- [1] creatureObjective (not used)
@@ -358,6 +411,7 @@ NPCs/objects from base WotLK/TBC/Classic databases already exist with spawn data
```
**How It Works:**
1. **Database Processing** (`QuestieDB.lua:1276-1293`): Creates a `killcredit` objective with `IdList` and `RootId`
2. **Spawn Handler** (`QuestieQuestPrivates.lua:65-73`): Iterates through `IdList` and creates map icons for each NPC
3. **Tracker**: Shows single entry (e.g., "Shattered Rumbler slain: 0/30")
@@ -365,10 +419,9 @@ NPCs/objects from base WotLK/TBC/Classic databases already exist with spawn data
**Example Use Case:**
Quest 50026 requires killing 30 elementals. Six different elemental NPC types exist (17156, 17157, 22309, 22310, 22311, 22313). Using `killCreditObjective` ensures:
- All six NPC types show on the map
- Single tracker counter increments for any kill
- No duplicate "0/30" entries in the tracker
* All six NPC types show on the map
* Single tracker counter increments for any kill
* No duplicate "0/30" entries in the tracker
#### 4. Server-Tracked Objectives ("Complete N Quests in Zone")
@@ -381,6 +434,7 @@ Missing objective data for quest <ID> Complete 6 quests in <Zone>
**Root Cause:** `QuestieQuest:PopulateQuestLogInfo` (line 1578) requires a `quest.ObjectiveData[index]` entry for every objective the server reports. If the quest has no objectives defined (no `[10]` field) and no `triggerEnd` (`[9]`), `ObjectiveData` is empty and the error fires.
**Wrong Approach (do not do this):**
```lua
-- Adding an all-nil [10] table does NOT create ObjectiveData entries
[50111] = {
@@ -407,6 +461,7 @@ Missing objective data for quest <ID> Complete 6 quests in <Zone>
```
**How it works:**
1. `QuestieDB.GetQuest` reads `rawdata[9]` → sets `QO.triggerEnd`
2. At line 1312-1320: `ObjectiveData[#ObjectiveData+1] = {Type="event", Text=..., Coordinates=nil}`
3. `PopulateQuestLogInfo` finds `ObjectiveData[1]` → no error
@@ -423,6 +478,7 @@ Missing objective data for quest <ID> Complete 6 quests in <Zone>
**Solution:** Omit the `finishedBy` field (index 3) from the quest data. This prevents Questie from showing a turn-in icon or expecting a return trip.
**Structure:**
```lua
[50026] = {
[1] = "Quest Name",
@@ -437,12 +493,12 @@ Missing objective data for quest <ID> Complete 6 quests in <Zone>
```
**Behavior:**
- Quest starter icon appears on the map
- No turn-in icon appears after objectives are complete
- Quest automatically completes on the server when objectives finish
* Quest starter icon appears on the map
* No turn-in icon appears after objectives are complete
* Quest automatically completes on the server when objectives finish
#### 5. Quest Data Structure
```lua
[50026] = {
[1] = "Quest Name",
@@ -460,21 +516,24 @@ Missing objective data for quest <ID> Complete 6 quests in <Zone>
## 11. Common Solutions
### No Objectives Shown
* **Page:** [No Objectives](https://github.com/Questie/Questie/wiki/Example%3A-No-Objectives)
* **Cause:** Quest references an item/object/NPC that has no spawn data.
* **Fix:** Ensure the target NPC/Object exists and has `[4]` (spawns) filled. If it's an item, check `npcDrops`.
* **Page:** [No Objectives](https://github.com/Questie/Questie/wiki/Example%3A-No-Objectives)
* **Cause:** Quest references an item/object/NPC that has no spawn data.
* **Fix:** Ensure the target NPC/Object exists and has `[4]` (spawns) filled. If it's an item, check `npcDrops`.
### Breadcrumb Quests
* **Page:** [Breadcrumb Quests](https://github.com/Questie/Questie/wiki/Example%3A-Breadcrumb-quests)
* **Fix:** Use `nextQuestInChain` or `exclusiveTo` to hide the breadcrumb when the main quest is taken.
* **Page:** [Breadcrumb Quests](https://github.com/Questie/Questie/wiki/Example%3A-Breadcrumb-quests)
* **Fix:** Use `nextQuestInChain` or `exclusiveTo` to hide the breadcrumb when the main quest is taken.
---
## 13. Debugging Stuck Arrows
### Scenario: Arrow persists on an NPC after picking up a follow-up quest
1. **Check for Multiple Quests**: Verify if more than one active quest has an objective at that NPC. Use Journey search for the NPC ID.
2. **Verify NPC Coordinate Overlap**: Compare NPC coordinates in `wotlkNpcDB.lua`. NPCs in the same hub (like Dun Niffelem) often share base coordinates. If Questie switches targets to a different NPC at the same location, the arrow will appear "stuck."
- **Fix**: Override the quest finisher in `wotlkQuestFixes.lua` to point to a distinct NPC or Object (e.g., moving the arrow from the King to the Anvil).
3. **Apply HideConditions**: If an objective is redundant once a breadcrumb quest is accepted, apply `hideIfQuestActive` to the correction data for that objective.
4. **Confirm Database Loading**: Ensure `QuestieDB.GetQuest` is correctly parsing the `HideCondition` field for the objective type.
1. **Check for Multiple Quests**: Verify if more than one active quest has an objective at that NPC. Use Journey search for the NPC ID.
2. **Verify NPC Coordinate Overlap**: Compare NPC coordinates in `wotlkNpcDB.lua`. NPCs in the same hub (like Dun Niffelem) often share base coordinates. If Questie switches targets to a different NPC at the same location, the arrow will appear "stuck."
* **Fix**: Override the quest finisher in `wotlkQuestFixes.lua` to point to a distinct NPC or Object (e.g., moving the arrow from the King to the Anvil).
3. **Apply HideConditions**: If an objective is redundant once a breadcrumb quest is accepted, apply `hideIfQuestActive` to the correction data for that objective.
4. **Confirm Database Loading**: Ensure `QuestieDB.GetQuest` is correctly parsing the `HideCondition` field for the objective type.