Advertisement
Guest User

QuestHubber.lua for 6.0.2

a guest
Oct 16th, 2014
506
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 31.62 KB | None | 0 0
  1. QuestHubber = LibStub("AceAddon-3.0"):NewAddon("QuestHubber", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceHook-3.0");
  2. local core = QuestHubber;
  3. local L = LibStub("AceLocale-3.0"):GetLocale("QuestHubber");
  4. local G = LibStub:GetLibrary("LibGratuity-3.0");
  5.  
  6. -- upvalues for globals used often
  7. local ipairs, pairs, tonumber, select, table, bit = ipairs, pairs, tonumber, select, table, bit
  8. local GetCurrentMapAreaID, UnitLevel, GetNumQuestLogEntries, GetQuestLogTitle = GetCurrentMapAreaID, UnitLevel, GetNumQuestLogEntries, GetQuestLogTitle
  9. local UIParent, WorldMapFrame, WorldMapDetailFrame = UIParent, WorldMapFrame, WorldMapDetailFrame
  10.  
  11. -- constants for clarity and order in selects
  12. local SHOW_ALWAYS = 0
  13. local SHOW_COND = 1
  14. local SHOW_NEVER = 2
  15.  
  16. local options_setter = function(info, v) local t=core.db.profile for k=1,#info-1 do t=t[info[k]] end t[info[#info]]=v core:UpdatePins(true) end
  17. local options_getter = function(info) local t=core.db.profile for k=1,#info-1 do t=t[info[k]] end return t[info[#info]] end
  18. local options = {
  19.     name = "QuestHubber",
  20.     type = 'group',
  21.     set = options_setter,
  22.     get = options_getter,
  23.     args = {
  24.         displayMap = {
  25.             name = L["World Map Display"],
  26.             type = 'toggle',
  27.             desc = L["Display a quick toggle and quest count in the corner of the world map"],
  28.             order = 10,
  29.             set = function(info, v) options_setter(info, v) core:SetMapModuleDisplay(v) end,
  30.         },
  31.         quests = {
  32.             name = L["Normal Quests"],
  33.             type = 'group',
  34.             inline = true,
  35.             order = 20,
  36.             args = {
  37.                 display = {
  38.                     name = L["Display on map"],
  39.                     type = 'toggle',
  40.                     order = 10,
  41.                 },
  42.                 lowLevel = {
  43.                     name = L["Show Low Level Quests"],
  44.                     type = 'select',
  45.                     desc = L["Choose how to handle quests below your character's level range"],
  46.                     values = {
  47.                         [SHOW_ALWAYS] = L["Always"],
  48.                         [SHOW_COND] = L["When tracking"],
  49.                         [SHOW_NEVER] = L["Never"],
  50.                     },
  51.                     order = 20,
  52.                 },
  53.                 highLevel = {
  54.                     name = L["Show High Level Quests"],
  55.                     type = 'select',
  56.                     desc = L["Choose how to handle quests above your character's level range"],
  57.                     values = {
  58.                         [SHOW_ALWAYS] = L["Always"],
  59.                         [SHOW_NEVER] = L["Never"],
  60.                     },
  61.                     order = 30,
  62.                 },
  63.                 br = {
  64.                     name = '',
  65.                     type = 'description',
  66.                     order = 32,
  67.                     width = 'full',
  68.                 },
  69.                 hidePreBreadcrumb = {
  70.                     name = L["Hide Skipped Quests"],
  71.                     type = 'toggle',
  72.                     desc = ("|cffff0000%s|r\n%s\n\n|cff9999ff%s|r"):format(L["Warning: Experimental"], L["Do not show pins on the map for quests that were optional lead-ins to quests you have already completed."], L["Note: Because data on Wowhead is limited, the full quest chains are not always available, so this option may incorrectly show some unavailable quests"]),
  73.                     order = 35,
  74.                 },
  75.                 hideBreadcrumb = {
  76.                     name = L["Hide Unavailable Chain Quests"],
  77.                     type = 'toggle',
  78.                     desc = ("|cffff0000%s|r\n%s\n\n|cff9999ff%s|r"):format(L["Warning: Experimental"], L["Do not show pins on the map that require another quest to be completed."], L["Note: Because data on Wowhead is limited, the full quest chains are not always available, so this option may incorrectly show some unavailable or hide some available quests"]),
  79.                     order = 40,
  80.                     width = 'double',
  81.                 },
  82.             },
  83.         },
  84.         daily = {
  85.             name = L["Daily Quests"],
  86.             type = 'group',
  87.             inline = true,
  88.             order = 30,
  89.             args = {
  90.                 display = {
  91.                     name = L["Display on map"],
  92.                     type = 'toggle',
  93.                     order = 10,
  94.                 },
  95.                 range = {
  96.                     name = L["Use level filters"],
  97.                     type = 'toggle',
  98.                     desc = L["The filters set in the Normal Quests section for high and low level quests will apply to dailies as well"],
  99.                     order = 20,
  100.                 },
  101.             },
  102.         },
  103.         grouping = {
  104.             name = L["Quest Pin Grouping"],
  105.             type = 'group',
  106.             inline = true,
  107.             hidden = true,
  108.             args = {
  109.                 enable = {
  110.                     name = L["Enable"],
  111.                     type = 'toggle',
  112.                     desc = L["If enabled, pins within a certain range of each other will be grouped on your world map as a single pin"],
  113.                     order = 10,
  114.                 },
  115.                 group = {
  116.                     name = L["Radius"],
  117.                     type = 'range',
  118.                     desc = L["Group any pins within this many number of yards"],
  119.                     min = 0,
  120.                     max = 100,
  121.                     step = 1,
  122.                     order = 20,
  123.                 },
  124.             },
  125.             order = 40,
  126.         },
  127.         status = {
  128.             name = '',
  129.             type = 'description',
  130.             order = 90,
  131.         },
  132.     },
  133. };
  134. local defaults = {
  135.     profile = {
  136.         debug = 0,
  137.         display = true, -- Master switch shown on minimap; is not accounted for if displayMap=false
  138.  
  139.         displayMap = true,
  140.         quests = {
  141.             display = true,
  142.             lowLevel = SHOW_COND,
  143.             highLevel = SHOW_NEVER,
  144.             hidePreBreadcrumb = true,
  145.             hideBreadcrumb = false,
  146.         },
  147.         daily = {
  148.             display = false,
  149.             range = false,
  150.         },
  151.         grouping = {
  152.             enable = true,
  153.             group = 10,
  154.         },
  155.     },
  156. };
  157. local FACTION_MAP = {
  158.     Alliance = 1,
  159.     Horde = 2,
  160. };
  161. local IS_ENGLISH = (GetLocale()=="enUS" or GetLocale()=="enGB")
  162. local RACE_ID = {
  163.     ["Human"]       = 1,
  164.     ["Orc"]         = 2,
  165.     ["Dwarf"]       = 4,
  166.     ["Night Elf"]   = 8,
  167.     ["Scourge"]     = 16,
  168.     ["Tauren"]      = 32,
  169.     ["Gnome"]       = 64,
  170.     ["Troll"]       = 128,
  171.     ["Goblin"]      = 256,
  172.     ["Blood Elf"]   = 512,
  173.     ["Draenei"]     = 1024,
  174.     ["Worgen"]      = 2048,
  175. }
  176. local CLASS_ID = {
  177.     ["WARRIOR"]     = 1,
  178.     ["PALADIN"]     = 2,
  179.     ["HUNTER"]      = 4,
  180.     ["ROGUE"]       = 8,
  181.     ["PRIEST"]      = 16,
  182.     ["DEATH KNIGHT"]= 32,
  183.     ["MAGE"]        = 128,
  184.     ["SHAMAN"]      = 64,
  185.     ["WARLOCK"]     = 256,
  186.     ["DRUID"]       = 1024,
  187. }
  188. local FLAG_STR = {
  189.     [0x002] = "  |TInterface\\MINIMAP\\TRACKING\\Banker:0|t",
  190.     [0x040] = L["Daily"],
  191.     [0x080] = L["Escort"],
  192.     [0x100] = L["Dungeon"],
  193.     [0x200] = L["Raid"],
  194.     [0x400] = L["PvP"],
  195. }
  196.  
  197. core.failedQueries = 0
  198. core.dataLoaded = false;        -- flag set after LoadStep() is done
  199. core.currentZone = -1;          -- current World Map zone id
  200. core.pinReg = {};               -- registry of all used pins
  201. core.frameReg = {};             -- registry of all unused pin frmaes
  202. core.playerQuests = {};         -- quests the player has completed, queried from server
  203. core.questLink = {              -- table linking optional quests to the mandatory quests they lead to which take away the initial
  204.     [25985] = 27874,
  205.     [27727] = 27203,
  206. };
  207. core.questLog = {};             -- current quest log, used to see when quests are accepted or completed
  208. core.questInit = false;         -- flag set after the quest log has been initially recorded
  209. core.abandonQuest = -1;         -- when "Abandon" is clicked, sets the quest that was selected at the time; used after confirmation
  210.  
  211. core.cache = {
  212.     quests = {},                -- [questid] = "Localized Name",
  213.     npcs = {},                  -- [npcid] = "Localized Name",
  214.     invalid = {},               -- [npcid] = true, -- flag set so you don't get double debug warnings about a single npc
  215. };
  216. core.data = {
  217.     zones = {},                 -- each zone has a table of quests available
  218.     npcs = {},                  -- npcs that drop a quest item
  219. };
  220.  
  221. -- LibDataBroker-1.1 support
  222. local ldb = LibStub:GetLibrary("LibDataBroker-1.1");
  223. local dataObj = ldb:NewDataObject("QuestHubber", {
  224.     type = "data source",
  225.     text = "0",
  226.     value = "0",
  227.     icon = "Interface\\AddOns\\QuestHubber\\QuestIcon.tga",
  228.     OnClick = function(self, button)
  229.         if button == "LeftButton" then
  230.             if IsControlKeyDown() then
  231.                 core:ToggleTracking()
  232.             else
  233.                 ToggleFrame(WorldMapFrame)
  234.             end
  235.         elseif button == "RightButton" then
  236.             InterfaceOptionsFrame_OpenToCategory("QuestHubber")
  237.         end
  238.     end,
  239.     OnTooltipShow = function(self)
  240.         local shown, total = core.mapModule.shown, core.mapModule.total
  241.         self:ClearLines()
  242.         self:AddLine(("QuestHubber - %s"):format(GetRealZoneText()))
  243.         self:AddLine(L["%d quests available in current zone"]:format(shown))
  244.         self:AddLine(L["%d additional quests not shown due to filter restrictions"]:format(total-shown));
  245.         self:AddLine(" ")
  246.         self:AddLine("|cFFEDA55FClick|r to open the World Map", 0.2, 1, 0.2, 1)
  247.         self:AddLine("|cFFEDA55FRight-Click|r to open the QuestHubber options", 0.2, 1, 0.2, 1)
  248.         self:AddLine("|cFFEDA55FCtrl-Click|r to toggle low level quest tracking", 0.2, 1, 0.2, 1)
  249.     end,
  250. })
  251. function core:UpdateLDB(text)
  252.     dataObj.text = text
  253. end
  254.  
  255.    
  256. function core:OnInitialize()
  257.     self.db = LibStub("AceDB-3.0"):New("QuestHubberDB", defaults, "Default");
  258.     self:RegisterChatCommand("qh", "SlashCommand")
  259.     self:RegisterChatCommand("qhub", "SlashCommand")
  260.     self:RegisterChatCommand("questhubber", "SlashCommand")
  261.    
  262.     LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("QuestHubber", options);
  263.     local ACD = LibStub("AceConfigDialog-3.0");
  264.     ACD:AddToBlizOptions("QuestHubber", "QuestHubber");
  265.    
  266.     self:SetupMapModule();
  267.     self:SetupTooltip();
  268.  
  269. end
  270.  
  271. function core:SlashCommand()
  272.     InterfaceOptionsFrame_OpenToCategory("QuestHubber")
  273. end
  274.  
  275. function core:OnEnable()
  276.     self:QueryQuestsCompleted();
  277.  
  278.     self:RegisterEvent("WORLD_MAP_UPDATE");             -- map change
  279.    
  280.     self:RegisterEvent("QUEST_LOG_UPDATE");             -- scan for quest completion/pickup
  281.    
  282.     self:RegisterEvent("MINIMAP_UPDATE_TRACKING");      -- for toggling Low Level Quests
  283.    
  284.     self:Hook("SetAbandonQuest", true);                 -- hook for abandoned quests
  285.     self:Hook("AbandonQuest", true);                    -- hook for confirmation
  286.    
  287.     self.mapModule:Show();
  288. end
  289.  
  290. function core:OnDisable()
  291.     self.mapModule:Hide(); 
  292. end
  293.  
  294. function core:SlashHandle(message)
  295.  
  296. end
  297.  
  298. function core:QueryQuestsCompleted()
  299.     -- We don't need to query anymore
  300.     self.playerQuests = GetQuestsCompleted();
  301.   -- Some people are having no result here, and I don't know why
  302.   if not next(self.playerQuests) then
  303.     -- self:Print("Failed to load quests, trying again...")
  304.     self:ScheduleTimer(function()
  305.       self:QueryQuestsCompleted()
  306.     end, 1)
  307.   else
  308.     self:UpdatePins(true);
  309.   end
  310. end
  311.  
  312. local numScans = 0
  313. local curScans = {}
  314. function core:QuestScan(id)
  315.     -- self:Debug("Scanning", id)
  316.     if (id and id > 0) then
  317.         -- check if we're scanning too many
  318.         if (numScans < 100 or curScans[id]) then
  319.             G:SetHyperlink(("quest:%d"):format(id));
  320.             local text = G:GetText();
  321.             if (text and text[1] and text[1][1]) then
  322.                 -- success, remove from scan list if it was queried
  323.                 if (curScans[id]) then
  324.                     curScans[id] = nil;
  325.                     numScans = numScans - 1;
  326.                     -- self:Debug("Removing a query", numScans);
  327.                 end
  328.                 self.cache.quests[id] = text[1][1];
  329.                 local mouse = GetMouseFocus()
  330.                 if (mouse and mouse.questHubberID == id) then
  331.                     self:ShowTooltip(mouse)
  332.                 end
  333.                 -- self:Debug(format("Quest id %d = %q", q, text[1][1]))
  334.             else
  335.                 -- self:Debug("No name for quest "..id);
  336.                 -- no result, so the server has been queried
  337.                 if (not curScans[id]) then
  338.                     numScans = numScans + 1;
  339.                     curScans[id] = true;
  340.                     -- self:Debug("Adding another query", numScans);
  341.                 end
  342.                 self:ScheduleTimer(function()
  343.                     self:QuestScan(id)
  344.                 end, 2)
  345.             end
  346.         else
  347.             -- self:Debug("NOT QUERYING, TOO HIGH")
  348.             self:ScheduleTimer(function()
  349.                 self:QuestScan(id)
  350.             end, 2.5)
  351.         end
  352.     end
  353. end
  354.  
  355. function core:NPCScan(id)
  356.     G:SetHyperlink(("unit:0xF53%05X00000000"):format(id));
  357.     local text = G:GetText();
  358.     if not text and not self.cache.invalid[id] then
  359.         -- self:Debug("Invalid NPC id:", id, "- perhaps not cached?");
  360.         self.cache.invalid[id] = true;
  361.     end
  362.     if (text and text[1] and text[1][1]) then
  363.         self.cache.npcs[id] = text[1][1];
  364.     end
  365. end
  366.  
  367. function core:WORLD_MAP_UPDATE()
  368.     if (self.dataLoaded) then
  369.         self:UpdatePins();
  370.     end
  371. end
  372.  
  373. -----------------------------------------------
  374. -- Pin management
  375. -----------------------------------------------
  376.  
  377. function core:UpdatePins(force)
  378.     local id = GetCurrentMapAreaID();
  379.     local filtered, shown = 0, 0;
  380.     if (id ~= self.currentZone or force) then
  381.         -- update all pins
  382.         self.currentZone = id;
  383.         self:HideAllPins();
  384.         if (self.data.zones[id]) then
  385.             for q,v in pairs(self.data.zones[id]) do
  386.                 if (self:QuestAvailable(q, v)) then -- if we're not on the quest or have completed it
  387.                     if (self:QuestUnfiltered(q, v)) then
  388.                         if (self.db.profile.display or not self.db.profile.displayMap) then -- don't want to restrict if we hid the map overlay
  389.                             self:ShowPin(q, v);
  390.                         end
  391.                         shown = shown + 1;
  392.                         -- self:Debug("Showing pin",q,"on map",id)
  393.                     else
  394.                         filtered = filtered + 1;
  395.                     end
  396.                 else
  397.                     -- self:Debug("Not showing pin",q,"on map",id)
  398.                 end
  399.             end
  400.             -- self:Debug("Showing",shown,"pins");
  401.         end
  402.         self.mapModule:SetCount(shown, shown+filtered);
  403.     end
  404. end
  405.  
  406. function core:ShowPin(id, data)
  407.     local flags, class, race, level, minlevel, xcoord, ycoord, npc, name = self:GetQuestData(data);
  408.     if (not flags or not tonumber(xcoord) or not tonumber(ycoord)) then
  409.         -- self:Debug("Bad quest data:", id, data, ";;;", flags, level, xcoord, ycoord, npc, name)
  410.         return
  411.     end
  412.    
  413.     if (bit.band(flags, 0x02) > 0) then
  414.         self.data.npcs[tonumber(npc)] = id;
  415.     end
  416.    
  417.     -- check the cache for quest name; scan tooltip if not
  418.     if (not self.cache.quests[id]) then
  419.         self:QuestScan(id);
  420.     end
  421.     -- check the cache for npc name; scan tooltip if not (and id belongs to an npc and not an object)
  422.     if (not self.cache.npcs[npc] and bit.band(flags, 0x010)>0) then
  423.         self:NPCScan(npc);
  424.     end
  425.  
  426.     -- check level and set pin type
  427.     local texType, level, minlevel = nil, tonumber(level), tonumber(minlevel)
  428.     if bit.band(flags, 0x040) > 0 then
  429.         texType = "daily"
  430.     elseif UnitLevel("player") < minlevel then
  431.         texType = "high"
  432.     elseif UnitLevel("player") > level + 10 then
  433.         texType = "low"
  434.     end
  435.    
  436.     local pin = self:GetPin();
  437.    
  438.     pin:SetType(texType);
  439.     pin:ClearAllPoints();
  440.     pin.questHubberID = id;
  441.     -- pin:SetParent(WorldMapDetailFrame);
  442.     pin:SetFrameLevel(WorldMapPOIFrame:GetFrameLevel()+1);
  443.     pin:SetPoint("CENTER", WorldMapDetailFrame, "TOPLEFT", xcoord/10000*WorldMapDetailFrame:GetWidth(), -ycoord/10000*WorldMapDetailFrame:GetHeight());
  444.     -- pin:SetFrameStrata("FULLSCREEN");
  445.     pin:Show();
  446.    
  447.     self.pinReg[id] = pin;
  448. end
  449.  
  450. function core:HidePin(id)
  451.     if (self.pinReg[id]) then
  452.         self.pinReg[id]:Hide();
  453.         table.insert(self.frameReg, self.pinReg[id]);
  454.         self.pinReg[id] = nil;
  455.     end
  456. end
  457.  
  458. function core:HideAllPins()
  459.     for i,v in pairs(self.pinReg) do
  460.         self:HidePin(i);
  461.     end
  462. end
  463.  
  464. -- tile sizes and coord starts for Interface\\MINIMAP\\ObjectIcons.blp
  465. local tileX, tileY = 1/8, 1/8
  466. local dailyX, dailyY = 3, 1
  467. local questX, questY = 1, 1
  468. function core:GetPin()
  469.     if (#self.frameReg > 0) then
  470.         return table.remove(self.frameReg);
  471.     end
  472.    
  473.     -- create new
  474.     local pin = CreateFrame("Frame", nil, WorldMapDetailFrame);
  475.     pin:SetWidth(16);
  476.     pin:SetHeight(16);
  477.     pin:EnableMouse(true);
  478.     pin:SetScript("OnEnter", function(pin)
  479.         self:ShowTooltip(pin);
  480.     end);
  481.     pin:SetScript("OnLeave", function()
  482.         self:HideTooltip();
  483.     end);
  484.     pin.SetType = function(self, texType)
  485.         if self.texType == texType then return end -- don't need to make changes
  486.  
  487.         -- 256x64, 8x4
  488.         self.texture:SetDesaturated(false);
  489.         self.texture:SetVertexColor(1, 1, 1);
  490.         if texType == "daily" then
  491.             self.texture:SetTexCoord(dailyX*tileX, (dailyX+1)*tileX, dailyY*tileY, (dailyY+1)*tileY)
  492.         elseif texType == "low" then
  493.             -- I don't like this one, it's too hard to see.
  494.             -- self.texture:SetTexCoord(4*0.125, 5*0.125, 3*0.250, 4*0.250);
  495.             self.texture:SetTexCoord(questX*tileX, (questX+1)*tileX, questY*tileY, (questY+1)*tileY)
  496.             self.texture:SetVertexColor(0.75, 0.75, 0.75);
  497.         elseif texType == "high" then
  498.             -- Note: not all systems can desaturate. If they don't, I don't really care.
  499.             self.texture:SetTexCoord(questX*tileX, (questX+1)*tileX, questY*tileY, (questY+1)*tileY)
  500.             self.texture:SetDesaturated(1);
  501.         else
  502.             self.texture:SetTexCoord(questX*tileX, (questX+1)*tileX, questY*tileY, (questY+1)*tileY)
  503.         end
  504.  
  505.         self.texType = texType
  506.     end
  507.  
  508.     pin.texture = pin:CreateTexture();
  509.     pin.texture:SetTexture("Interface\\MINIMAP\\ObjectIcons.blp");
  510.     pin.texture:SetAllPoints();
  511.     pin:SetType("default")
  512.    
  513.     return pin;
  514. end
  515.  
  516. -- QuestAvailable() returns whether or not the quest is available for pickup right now
  517. function core:QuestAvailable(id, data)
  518.     if data then
  519.         local flags, class, race, _, _, _, _, _, _, pre, post, final = self:GetQuestData(data);
  520.         class, race = tonumber(class), tonumber(race)
  521.        
  522.         -- check race
  523.         local urace = RACE_ID[select(2, UnitRace("player"))]
  524.         if (race > 0 and urace and bit.band(race, urace) == 0) then
  525.             -- self:Debug("Hid", id, "because of race restriction")
  526.             return false
  527.         end        
  528.        
  529.         -- check class
  530.         local uclass = CLASS_ID[select(2, UnitClass("player"))]
  531.         if (class > 0 and uclass and bit.band(class, uclass) == 0) then
  532.             -- self:Debug("Hid", id, "because of class restriction")
  533.             return false
  534.         end    
  535.  
  536.         -- check daily - we don't want it to add to total if we have it turned off
  537.         if (bit.band(flags, 0x040) > 0 and not self.db.profile.daily.display) then
  538.             return false
  539.         end
  540.  
  541.         -- check if this is a pre-quest that cannot be completed
  542.         if (self.db.profile.quests.hidePreBreadcrumb) then
  543.             -- check post and final of this quest
  544.             if (post ~= "0" and self:CompletedBreadcrumbs(post)) or (final ~= "0" and self:CompletedBreadcrumbs(final)) then
  545.                 return false
  546.             end
  547.         end
  548.  
  549.         -- check if this is a future quest that cannot be picked up yet
  550.         if (self.db.profile.quests.hideBreadcrumb) then
  551.             -- check pre of this quest
  552.             if pre ~= "0" and not self:CompletedBreadcrumbs(pre) then
  553.                 return false
  554.             end
  555.         end
  556.     end
  557.    
  558.     -- check based on chain - TODO: delete
  559.     if (not self.questLog[id] and not self.playerQuests[id] and self.questLink[id]) then
  560.         return not self.playerQuests[self.questLink[id]];
  561.     end
  562.    
  563.     -- now only return true if its not in your logs or completed list
  564.     return not self.questLog[id] and not self.playerQuests[id];
  565. end
  566.  
  567. -- QuestUnfiltered() returns whether or not the quest is being filtered (i.e., level requirement)
  568. -- add daily flag?
  569. function core:QuestUnfiltered(id, data)
  570.     local db = self.db.profile
  571.  
  572.     if data then
  573.         local flags, _, _, level, minlevel = self:GetQuestData(data)
  574.         level, minlevel = tonumber(level), tonumber(minlevel)
  575.  
  576.         -- check daily quest
  577.         -- I am taking advantage of the fact that there are only two filters. This will need to change if I add more
  578.         -- We definitely want to display if we have range off
  579.         if (bit.band(flags, 0x040) > 0) then
  580.             if db.daily.display and not db.daily.range then
  581.                 return true
  582.             end
  583.         else
  584.             -- check if we're displaying normal quests
  585.             if not db.quests.display then
  586.                 return false
  587.             end
  588.         end
  589.  
  590.         -- check level requirement
  591.         if  (db.quests.lowLevel == SHOW_NEVER and UnitLevel("player") > level + 10)                             or
  592.             (db.quests.lowLevel == SHOW_COND and UnitLevel("player") > level + 10 and not self:TrackingLow())   or
  593.             (db.quests.highLevel == SHOW_NEVER and UnitLevel("player") < minlevel)                              then
  594.             -- self:Debug("Hid", id, "because of level restriction")
  595.             return false
  596.         end
  597.     end
  598.  
  599.     return true
  600. end
  601.  
  602. -- checks whether the player is tracking low level quests
  603. local trackIndex
  604. local trackTexture = "Interface\\Minimap\\Tracking\\TrivialQuests"
  605. function core:TrackingLow()
  606.     if not trackIndex or select(2, GetTrackingInfo(trackIndex)) ~= trackTexture then
  607.         trackIndex = nil -- if the second condition fired, let's discard the old value just in case
  608.         for i=1,GetNumTrackingTypes() do
  609.             local _, texture = GetTrackingInfo(i)
  610.             if texture == trackTexture then
  611.                 trackIndex = i
  612.                 break
  613.             end
  614.         end
  615.     end
  616.    
  617.     if trackIndex then
  618.         return select(3, GetTrackingInfo(trackIndex))
  619.     end
  620. end
  621.  
  622. -- when minimap tracking changes, see if we need to fire a map update
  623. local oldTrack = core:TrackingLow()
  624. function core:MINIMAP_UPDATE_TRACKING()
  625.     if self:TrackingLow() ~= oldTrack and self.db.profile.quests.lowLevel == SHOW_COND then
  626.         self:UpdatePins(true)
  627.         oldTrack = not oldTrack
  628.     end
  629. end
  630.  
  631. -- toggles the low level quest tracking
  632. function core:ToggleTracking()
  633.     -- force refresh to make sure our vars are correct
  634.     local current = self:TrackingLow()
  635.  
  636.     if trackIndex then
  637.         SetTracking(trackIndex, not current)
  638.     end
  639. end
  640.  
  641. -- CompletedBreadcrumbs() takes a quest id string joined with | (or) or & (and) and returns the result respectively
  642. function core:CompletedBreadcrumbs(qidStr)
  643.     local qid, delim, cdr = qidStr:match("(%d+)([|&]*)(%s*)")
  644.     local completed = self.playerQuests[tonumber(qid) or 0]
  645.    
  646.     if delim == "" then
  647.         return completed
  648.     elseif delim == "|" then
  649.         return completed or self:CompletedBreadcrumbs(cdr)
  650.     elseif delim == "&" then
  651.         return completed and self:CompletedBreadcrumbs(cdr)
  652.     end
  653. end
  654.  
  655. -----------------------------------------------
  656. -- Tooltip management
  657. -----------------------------------------------
  658.  
  659. function core:GetMousePosition()
  660.     local left, top = WorldMapDetailFrame:GetLeft(), WorldMapDetailFrame:GetTop();
  661.     local width, height = WorldMapDetailFrame:GetWidth(), WorldMapDetailFrame:GetHeight();
  662.     local scale = WorldMapDetailFrame:GetEffectiveScale();
  663.  
  664.     local x, y = GetCursorPosition();
  665.     local cx = (x/scale - left) / width;
  666.     local cy = (top - y/scale) / height;
  667.    
  668.     return math.min(math.max(cx, 0), 1), math.min(math.max(cy, 0), 1);
  669. end
  670.  
  671. function core:Distance(x1, y1, x2, y2)
  672.     -- gets distance in map units TODO: use yards?
  673.     local distRatio = WorldMapDetailFrame:GetHeight() / WorldMapDetailFrame:GetWidth();
  674.    
  675.     return math.sqrt( (x1 - x2)^2 + ((y1 - y2)/distRatio)^2 );
  676. end
  677.  
  678. local listedQuests = {} -- this will be wiped and filled with names that are added to the tooltip so there are no dupes
  679. function core:ShowTooltip(pin)
  680.     -- check to make sure the tooltip can be shown because of the parent
  681.     if not UIParent:IsShown() then
  682.         if self.tooltip:GetParent() == UIParent then
  683.             self.tooltip:SetParent(WorldMapFrame);
  684.             self.tooltip:SetFrameStrata("TOOLTIP");
  685.         end
  686.     elseif self.tooltip:GetParent() ~= UIParent then
  687.         self.tooltip:SetParent(UIParent);
  688.         self.tooltip:SetFrameStrata("TOOLTIP");
  689.     end
  690.  
  691.     self.tooltip:SetOwner(pin, "ANCHOR_RIGHT");
  692.     self.tooltip:ClearLines();
  693.    
  694.     -- find all quests in range of hover
  695.     local mx, my = self:GetMousePosition();
  696.     local npcList = {};
  697.     local npcNames = {};
  698.     for q,v in pairs(self.data.zones[self.currentZone]) do
  699.         if (self:QuestAvailable(q, v) and self:QuestUnfiltered(q, v)) then
  700.             local _, _, _, _, _, xcoord, ycoord, npc, npcname = self:GetQuestData(v);
  701.             if (tonumber(xcoord) and tonumber(ycoord)) then
  702.                 local dist = self:Distance(mx, my, xcoord/10000, ycoord/10000);
  703.                 -- self:Print("d:",dist,v.begin.x,v.begin.y);
  704.                 if (dist <= 0.02) then
  705.                     if (not npcList[npc]) then
  706.                         npcList[npc] = {};
  707.                         npcNames[npc] = npcname;
  708.                     end
  709.                     table.insert(npcList[npc], q);
  710.                 end
  711.             end
  712.         end
  713.     end
  714.    
  715.     local first = true;
  716.     local cZone = self.data.zones[self.currentZone];
  717.     for npc,questList in pairs(npcList) do
  718.         wipe(listedQuests)
  719.         if (not first) then
  720.             self.tooltip:AddLine(" ");
  721.         else
  722.             first = false;
  723.         end
  724.         table.sort(questList);
  725.         for _,q in ipairs(questList) do
  726.             local flags, class, race, level, minlevel, _, _, _, npcname = self:GetQuestData(cZone[q]);
  727.             local questName = self.cache.quests[q]
  728.             local leftStr = ("[%d] %s"):format(level, questName or "(Querying server...)")
  729.             local rightStr = "";
  730.            
  731.             -- checking flags for rightStr text
  732.             -- rightStr = "|TInterface\\CURSOR\\CURSORICONSNEW:16:16:0:0:128:256:32:63:128:159|t" -- -64:-96
  733.             for flg, text in pairs(FLAG_STR) do
  734.                 if bit.band(flags, flg) > 0 then
  735.                     if text:match("%s.+") then
  736.                         leftStr = leftStr .. text
  737.                     else
  738.                         rightStr = text
  739.                     end
  740.                 end
  741.             end
  742.  
  743.             -- check if already printed - this is for spam quests like the human starting area that haven't been labeled correctly
  744.             if not questName or not listedQuests[questName] then
  745.                 self.tooltip:AddDoubleLine(leftStr, rightStr, 255/255, 210/255, 0/255, 255/255, 210/255, 0/255);
  746.                 -- self.tooltip:AddLine(leftStr .. rightStr, 255/255, 210/255, 0/255, 1);
  747.                 self.tooltip:SetLastFont(self.tooltip.large);
  748.                 self.tooltip:SetLastFont(self.tooltip.small, true);
  749.                 if questName then
  750.                     listedQuests[questName] = true
  751.                 end
  752.             end
  753.         end
  754.         -- if the npc name is cached, display it (will be localized); if not, if we are on enUS or enGB, display the hard-coded name as it should be localized; if on a different locale, display the hard-coded name in parenthesis to denote that it has not been discovered yet
  755.         self.tooltip:AddLine(self.cache.npcs[npc] or (IS_ENGLISH and npcNames[npc]) or ("(%s)"):format(npcNames[npc]), 1, 1, 1, 1);
  756.         self.tooltip:SetLastFont(self.tooltip.small);
  757.     end
  758.    
  759.     self.tooltip:Show();
  760. end
  761.  
  762. function core:HideTooltip()
  763.     self.tooltip:Hide();
  764. end
  765.  
  766. -----------------------------------------------
  767. -- Quest log management
  768. -----------------------------------------------
  769.  
  770. function core:QUEST_LOG_UPDATE()
  771.     -- get closed tabs
  772.     local closed = {}
  773.     for i=1,GetNumQuestLogEntries() do
  774.         local title, _, _, isHeader, isCollapsed = GetQuestLogTitle(i);
  775.         if (isCollapsed) then
  776.             closed[title] = true
  777.         end
  778.     end
  779.     ExpandQuestHeader(0)
  780.    
  781.     if (not self.questInit) then
  782.         for i=1,GetNumQuestLogEntries() do
  783.             local _, _, _, _, _, _, _, id = GetQuestLogTitle(i);
  784.             if (id and id > 0) then
  785.                 self.questLog[id] = true;
  786.             end
  787.         end
  788.         self:Debug("Quest Log snapshot saved");
  789.         -- self:UnregisterEvent("QUEST_LOG_UPDATE");
  790.         self.questInit = true;
  791.     else
  792.         -- self:Debug("QUEST_LOG_UPDATE");
  793.         local tempLog = {};
  794.         for i=1,GetNumQuestLogEntries() do
  795.             local name, _, _, _, _, _, _, id = GetQuestLogTitle(i);
  796.             -- self:Debug("   ", name, id);
  797.             if (id and id > 0) then
  798.                 tempLog[id] = name;
  799.             end
  800.         end
  801.         -- self:Debug("_END_");
  802.        
  803.         for i,v in pairs(self.questLog) do
  804.             if (not tempLog[i]) then
  805.                 -- quest removed; hopefully completed
  806.                 self:Debug("Quest completed:",i);
  807.                 self.questLog[i] = nil;
  808.                 tempLog[i] = nil;
  809.                 self.playerQuests[i] = true;
  810.                 -- force a full update in case any optional pre-reqs should be turned off
  811.                 self:UpdatePins(true);
  812.             end
  813.         end
  814.        
  815.         -- merge added
  816.         for i,v in pairs(tempLog) do
  817.             if (not self.questLog[i]) then
  818.                 self:Debug("Quest accepted:", v);
  819.                 self.questLog[i] = true;
  820.                 self:HidePin(i);
  821.             end
  822.         end
  823.     end
  824.    
  825.     -- restore closed tabs
  826.     for i=1,GetNumQuestLogEntries() do
  827.         local title, _, _, isHeader = GetQuestLogTitle(i);
  828.         if closed[title] then
  829.             CollapseQuestHeader(i)
  830.         end
  831.     end
  832. end
  833.  
  834. function core:SetAbandonQuest()
  835.     self.abandonQuest = GetQuestLogSelection();
  836. end
  837.  
  838. function core:AbandonQuest()
  839.     -- saved quest was abandoned
  840.     -- self:Debug("AbandonQuest");
  841.     local name, _, _, _, _, _, _, id = GetQuestLogTitle(self.abandonQuest);
  842.     self:Debug("Quest abandoned:",name);
  843.     self.questLog[id] = nil;
  844.     self:UpdatePins(true)
  845. end
  846.  
  847. function core:Debug(level, ...)
  848.     if (type(level) ~= "number") then
  849.         self:Debug(1, level, ...);
  850.         return;
  851.     end
  852.     if self.db.profile.debug >= level then
  853.         self:Print(...);
  854.     end
  855. end
  856.  
  857. local loader  -- timer that runs UpdatePins after everything is loaded
  858. function core:RegisterQuests(zones)
  859.     for i,v in pairs(zones) do
  860.         if (not self.data.zones[i]) then
  861.             self.data.zones[i] = {};
  862.         end
  863.        
  864.         for q,qv in pairs(v) do
  865.             self.data.zones[i][q] = qv;
  866.         end
  867.     end
  868.     self.dataLoaded = true;
  869.     if (not loader) then
  870.         loader = self:ScheduleTimer(function()
  871.             self:UpdatePins(true);
  872.             self:SecureHookScript(GameTooltip, "OnTooltipSetUnit", "CheckNPCTooltip"); -- used for adding notes to mobs' tooltips
  873.         end, 1);
  874.     end
  875. end
  876.  
  877. -- returns flags, class, race, level, xcoord, ycoord, npc, npcname
  878. function core:GetQuestData(questStr)
  879.     return questStr:match("(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(.-)::([0-9|&]+):([0-9|&]+):([0-9|&]+)")
  880. end
  881.  
  882. -- local oprint = print
  883. -- local function print(...)
  884. --  local toprint = {...}
  885. --  core:ScheduleTimer(function()
  886. --      oprint(unpack(toprint))
  887. --  end, 1)
  888. -- end
  889.  
  890. function core:SetupMapModule()
  891.     -- frame contains checkbox and text label
  892.     local frame = CreateFrame("Frame", nil, WorldMapDetailFrame);
  893.     frame:SetSize(250, 20);
  894.     frame:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 5, -2);
  895.     frame:Hide();
  896.     frame:EnableMouse(1);
  897.     frame:SetFrameStrata("HIGH");
  898.     frame:SetFrameLevel(5);
  899.     frame:SetScript("OnEnter", function(self)
  900.         core.tooltip:ClearLines();
  901.         core.tooltip:SetOwner(self);
  902.         core.tooltip:AddLine(L["%d quests available in current zone"]:format(self.shown))
  903.         if self.total > self.shown then
  904.             core.tooltip:AddLine(L["%d additional quests not shown due to filter restrictions"]:format(self.total-self.shown));
  905.             core.tooltip:SetLastFont(core.tooltip.small);
  906.         end
  907.         core.tooltip:Show();
  908.         core.tooltip:ClearAllPoints();
  909.         core.tooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 20, 0);
  910.     end);
  911.     frame:SetScript("OnLeave", function(self)
  912.         core.tooltip:Hide();
  913.     end);
  914.    
  915.     frame.check = CreateFrame("CheckButton", nil, frame, "OptionsBaseCheckButtonTemplate");
  916.     frame.check:SetSize(18, 18);
  917.     frame.check:SetHitRectInsets(0, 0, 0, 0);
  918.     frame.check:SetPoint("LEFT");
  919.     frame.check:SetChecked(self.db.profile.display);
  920.     frame.check:SetFrameLevel(6);
  921.     frame.check:SetScript("OnClick", function(self)
  922.         if (self:GetChecked()) then
  923.             core.db.profile.display = true
  924.         else
  925.             core.db.profile.display = false
  926.         end
  927.         core:UpdatePins(true)
  928.     end)
  929.     frame.check:SetScript("OnEnter", function(self)
  930.         -- core:Print("Enter");
  931.         core.tooltip:ClearLines();
  932.         core.tooltip:SetOwner(self, "ANCHOR_RIGHT");
  933.         core.tooltip:AddLine(L["Show/Hide all pins"]);
  934.         core.tooltip:Show();
  935.     end);
  936.     frame.check:SetScript("OnLeave", function(self)
  937.         -- core:Print("Leave");
  938.         core.tooltip:Hide();
  939.     end);
  940.    
  941.     local font, size = GameTooltipHeader:GetFont();
  942.     frame.text = frame:CreateFontString();
  943.     frame.text:SetFont(font, size, "OUTLINE");
  944.     frame.text:SetTextColor(255/255, 210/255, 0/255, 1);
  945.     frame.text:SetText("QuestHubber");
  946.     frame.text:SetPoint("LEFT", frame.check, "RIGHT");
  947.    
  948.     core.mapModule = frame;
  949.     core.mapModule.SetCount = function(self, m, n)
  950.         -- sets the text to QuestHubber (m/n) and shows a tooltip saying how many are hidden
  951.         self.shown, self.total = m, n or m
  952.         local suffix = ""
  953.         if (m and (n or m)>0) then
  954.             if not n or n <= m then
  955.                 suffix = ("(%d)"):format(m)
  956.             else
  957.                 suffix = ("(%d/%d)"):format(m, n)
  958.             end
  959.         end
  960.         self.text:SetText("QuestHubber " .. suffix)
  961.  
  962.         -- also update our ldb object
  963.         core:UpdateLDB(suffix=="" and 0 or suffix:sub(2, -2))
  964.     end
  965. end
  966.  
  967. function core:SetMapModuleDisplay(shown)
  968.     if shown then
  969.         core.mapModule:Show()
  970.     else
  971.         core.mapModule:Hide()
  972.     end
  973. end
  974.  
  975.    
  976.  
  977. function core:SetupTooltip()
  978.     self.tooltip = CreateFrame("GameTooltip", "QuestHubberTooltip", UIParent, "GameTooltipTemplate");
  979.     self.tooltip:SetFrameStrata("TOOLTIP");
  980.     self.tooltip.large = QuestHubberTooltipTextLeft1:GetFontObject();
  981.     self.tooltip.small = QuestHubberTooltipTextLeft2:GetFontObject();
  982.     self.tooltip.SetLastFont = function(self, fontObj, rightText)
  983.         local txt = "Left"
  984.         if rightText then
  985.             txt = "Right"
  986.         end
  987.         _G[("QuestHubberTooltipText%s%d"):format(txt, self:NumLines())]:SetFont(fontObj:GetFont());
  988.         -- _G["QuestHubberTooltipTextRight"..self:NumLines()]:SetFont(fontObj:GetFont());
  989.     end
  990.    
  991.     -- if the UI panel disappears (maximized WorldMapFrame) we need to change parents
  992.     self:HookScript(UIParent, "OnHide", function()
  993.         self.tooltip:SetParent(WorldMapFrame);
  994.         self.tooltip:SetFrameStrata("TOOLTIP");
  995.     end);
  996.     self:HookScript(UIParent, "OnShow", function()
  997.         self.tooltip:SetParent(UIParent);
  998.         self.tooltip:SetFrameStrata("TOOLTIP");
  999.     end);
  1000. end
  1001.  
  1002. function core:CheckNPCTooltip(tooltip)
  1003.     if (not UnitIsPlayer("mouseover") or true) then
  1004.         -- check if this npc drops a quest item
  1005.         local guid = UnitGUID("mouseover");
  1006.         if ( guid ) then
  1007.             local id = tonumber(guid:sub(7, 10), 16);
  1008.             local q = self.data.npcs[id];
  1009.             if (q and self:QuestAvailable(q) and self:QuestUnfiltered(q)) then
  1010.                 local leftStr = ("|TInterface\\MINIMAP\\ObjectIcons:17:9:0:0:256:128:40:56:32:63|t %s"):format(L["Drops an item which starts [%s]"]:format(self.cache.quests[q] or ("(%s)"):format(L["Querying server..."])))
  1011.                 tooltip:AddLine(leftStr);
  1012.                 tooltip:Show();
  1013.             end
  1014.         end
  1015.     end
  1016. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement