Nenue@33: -- WorldPlan Nenue@33: -- WorldQuests.lua Nenue@33: -- Created: 11/2/2016 3:40 PM Nenue@33: -- %file-revision% Nenue@33: Nenue@35: WorldPlanDataProvider = {} Nenue@35: WorldPlanDataPinMixin = {} Nenue@33: WorldPlanQuestsMixin = { Nenue@33: QuestsByZone = {}, Nenue@33: QuestsByID = {}, Nenue@33: freePins = {}, Nenue@33: } Nenue@33: local WorldQuests = WorldPlanQuestsMixin Nenue@33: Nenue@33: local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo Nenue@33: local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented Nenue@33: local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID Nenue@33: local GetMapInfo = GetMapInfo Nenue@33: local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end Nenue@33: local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end Nenue@33: local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end Nenue@33: local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end Nenue@35: local mprint = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or function() end Nenue@33: Nenue@33: Nenue@33: local PinBaseIndex = 1600 Nenue@33: local BROKEN_ISLES_ID, DALARAN_ID, AZSUNA_ID, VALSHARAH_ID, HIGHMOUNTAIN_ID, STORMHEIM_ID, SURAMAR_ID, EOA_ID = 1007, 1014, 1015,1018, 1024, 1017, 1033, 1096 Nenue@33: local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70', [AZSUNA_ID] = 'Azsuna', [VALSHARAH_ID] = "Val'sharah", Nenue@33: [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim', [SURAMAR_ID] = 'Suramar', [EOA_ID] = 'EyeOfAszhara', } Nenue@33: Nenue@33: local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD Nenue@33: local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER Nenue@33: local REWARD_GEAR = WORLD_QUEST_REWARD_TYPE_FLAG_EQUIPMENT Nenue@33: local REWARD_CURRENCY = WORLD_QUEST_REWARD_TYPE_FLAG_ORDER_RESOURCES Nenue@33: local REWARD_REAGENT = WORLD_QUEST_REWARD_TYPE_FLAG_MATERIALS Nenue@33: Nenue@33: Nenue@33: local numPins = 0 Nenue@33: local ZoneInfo = {} Nenue@33: local NumPinFrames = 1 Nenue@33: Nenue@33: Nenue@33: --%debug% Nenue@33: local SetTimedCallbackForAllPins = function(seconds, callback) Nenue@33: C_Timer.After(seconds, function() Nenue@33: for id, pin in pairs(WorldPlanQuests.QuestsByID) do Nenue@33: callback(pin) Nenue@33: end Nenue@33: end) Nenue@33: end Nenue@33: Nenue@33: function WorldQuests:Setup() Nenue@33: Nenue@33: Nenue@33: for mapID, mapName in pairs(WORLD_QUEST_MAPS) do Nenue@33: self.QuestsByZone[mapID] = {} Nenue@33: end Nenue@33: Nenue@33: Nenue@33: -- refresh positions any time blizzard does so (i.e. mousewheel zoom) Nenue@33: hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function() Nenue@33: self:Refresh(true) Nenue@33: end) Nenue@33: Nenue@33: -- hide the original world quest POIs Nenue@33: hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function() Nenue@33: for i = 1, NUM_WORLDMAP_TASK_POIS do Nenue@33: local button = _G['WorldMapFrameTaskPOI'..i] Nenue@33: if button and button.worldQuest then Nenue@33: button:Hide() Nenue@33: end Nenue@33: end Nenue@33: end) Nenue@33: end Nenue@33: local WorldMapPOIFrame Nenue@33: local defaults = {} Nenue@33: function WorldQuests:OnLoad() Nenue@33: print('|cFF00FF88'..self:GetName()..':OnLoad') Nenue@33: Nenue@33: WorldPlan:AddHandler(self, defaults) Nenue@33: Nenue@33: local rgbWhite = {1, 1, 1} Nenue@33: WorldPlan:AddTypeInfo(self, REWARD_REAGENT, { r = 0, g = 1, b = 1 }) Nenue@33: WorldPlan:AddTypeInfo(self, REWARD_ARTIFACT_POWER, { r = 1, g = .25, b = .5, hasNumeric = true, numberRGB = rgbWhite }) Nenue@33: WorldPlan:AddTypeInfo(self, REWARD_GEAR, { r = .1, g = .2, b = 1 }) Nenue@33: WorldPlan:AddTypeInfo(self, REWARD_CURRENCY, { r = 1, g = 1, b = 0, hasNumeric = true, numberRGB = {1,1,0}, }) Nenue@33: WorldPlan:AddTypeInfo(self, REWARD_CASH, { r = 0, g = 0, b = 0, }) Nenue@33: Nenue@33: for areaID, fileName in pairs(WORLD_QUEST_MAPS) do Nenue@33: self.QuestsByZone[areaID] = {} Nenue@33: end Nenue@33: Nenue@33: self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL') Nenue@33: self:RegisterEvent('SKILL_LINES_CHANGED') Nenue@33: Nenue@33: WorldMapPOIFrame = _G.WorldMapPOIFrame Nenue@33: Nenue@33: end Nenue@33: Nenue@33: function WorldQuests:OnEvent (event, ...) Nenue@33: local print = wqprint Nenue@33: print('|cFFFFFF00'..self:GetName()..':OnEvent()'..event..'|r', GetTime(), ...) Nenue@33: if event == 'QUEST_LOG_UPDATE' then Nenue@33: local questID, added = ... Nenue@33: if questID and added then Nenue@33: local questPOI = self:AcquirePin(questID) Nenue@33: self.isStale, self.isPending = questPOI:RefreshData() Nenue@33: else Nenue@33: self:RefreshData() Nenue@33: end Nenue@33: print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.isStale) Nenue@34: elseif event == 'WORLD_MAP_UPDATE' or event == 'PLAYER_ENTERING_WORLD' then Nenue@33: self.isStale = true Nenue@33: elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then Nenue@33: local questID = ... Nenue@33: if questID and self.QuestsByID[questID] then Nenue@33: self:ReleasePin(self.QuestsByID[questID]) Nenue@33: end Nenue@33: elseif event == 'SKILL_LINES_CHANGED' then Nenue@33: self.isStale = true Nenue@33: end Nenue@33: end Nenue@33: Nenue@33: local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation Nenue@33: function WorldQuests:AcquirePin (questID, mapID) Nenue@33: local pin = self.QuestsByID[questID] Nenue@33: local isNew = false Nenue@33: if not pin then Nenue@33: isNew = true Nenue@33: local numFree = #self.freePins Nenue@33: if numFree >= 1 then Nenue@33: pin = tremove(self.freePins, numFree) Nenue@33: --print('|cFF00FF00Re-using', pin:GetName()) Nenue@33: else Nenue@33: local name = 'WorldPlanQuestMarker' .. NumPinFrames Nenue@33: --print('|cFF00FF00Creating', name) Nenue@33: pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin') Nenue@33: Nenue@33: pin:SetFrameStrata('HIGH') Nenue@33: pin.GetTypeInfo = function(frame, typeID) Nenue@33: return self:GetTypeInfo(typeID) Nenue@33: end Nenue@33: NumPinFrames = NumPinFrames + 1 Nenue@33: --pin.iconBorder:SetVertexColor(0,0,0,1) Nenue@33: end Nenue@33: pin:SetID(questID) Nenue@33: pin.isNew = true Nenue@33: pin.currentWidth = nil Nenue@33: Nenue@33: -- used by TaskPOI_x scripts Nenue@33: pin.questID = questID Nenue@33: pin.worldQuest = true Nenue@33: Nenue@33: self.QuestsByID[questID] = pin Nenue@33: else Nenue@33: --print('|cFF00FF00Using', pin:GetName()) Nenue@33: end Nenue@33: mapID = mapID or TQ_GetQuestZoneID(questID) Nenue@33: self.QuestsByZone[mapID][questID] = pin Nenue@33: Nenue@33: return pin, isNew Nenue@33: end Nenue@33: Nenue@33: -- remove from index and add it to the recycling heap Nenue@33: function WorldQuests:ReleasePin (pin) Nenue@33: Nenue@33: local id = pin.questId Nenue@33: if id then Nenue@33: self.QuestsByID[id] = nil Nenue@33: for i, zone in pairs(self.QuestsByZone) do Nenue@33: print('-', i, zone[i]) Nenue@33: zone[id] = nil Nenue@33: end Nenue@33: end Nenue@33: pin:Hide() Nenue@33: pin:ClearAllPoints() Nenue@33: tinsert(self.freePins, pin) Nenue@33: print('|cFFFF4400Clearing out', pin:GetName(),id) Nenue@33: end Nenue@33: Nenue@33: -- create of update quest pins for a map and its underlying zones Nenue@33: function WorldQuests:RefreshData (mapID) Nenue@33: local print = wqprint Nenue@35: mapID = mapID or WorldPlan.currentMapID Nenue@33: if not mapID then Nenue@33: -- info not available yet Nenue@33: return Nenue@33: end Nenue@33: Nenue@34: Nenue@34: print('|cFF00FF88'..self:GetName()..':RefreshData()|r', 'map:', mapID, 'realMap:', GetCurrentMapAreaID()) Nenue@33: Nenue@33: if mapID == BROKEN_ISLES_ID then Nenue@33: self.isStale = false Nenue@33: print('|cFF00FFFFContinent:|r', mapID, GetMapNameByID(mapID), superTrackedID) Nenue@33: self.fullSearch = true Nenue@33: for i = 1, MC_GetNumZones(mapID) do Nenue@33: local submapID, name, depth = MC_GetZoneInfo(mapID, i) Nenue@33: self:RefreshData(submapID) Nenue@33: end Nenue@33: self.fullSearch = nil Nenue@33: elseif self.QuestsByZone[mapID] then Nenue@35: local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID, WorldPlan.currentMapID) Nenue@33: local numQuests = 0 Nenue@33: if taskInfo and #taskInfo >= 1 then Nenue@33: print('|cFF00FFFF Zone:|r', mapID, GetMapNameByID(mapID), #taskInfo) Nenue@33: wipe(self.QuestsByZone[mapID]) Nenue@33: ZoneInfo[mapID] = taskInfo Nenue@33: qprint('|cFFFF4400START of', GetMapNameByID(mapID)) Nenue@33: for taskID, info in pairs(taskInfo) do Nenue@33: local questID = info.questId Nenue@33: info.mapID = mapID Nenue@33: local questPOI = self:AcquirePin(questID, mapID) Nenue@33: local hasUpdate, isPending = questPOI:RefreshData(info) Nenue@34: -- WorldPlan:print('|cFF0088FF'..questPOI.title..'|r', hasUpdate) Nenue@33: self.isStale = (self.isStale or hasUpdate) Nenue@33: self.isPending = (self.isPending or isPending) Nenue@33: numQuests = numQuests + 1 Nenue@33: end Nenue@33: qprint('|cFFFF4400END of', GetMapNameByID(mapID)) Nenue@33: end Nenue@33: end Nenue@33: Nenue@33: if not self.fullSearch then Nenue@33: print(' hasUpdate:', self.isStale, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or '')) Nenue@33: --WorldPlan.isStale = (self.isStale or WorldPlan.isStale) Nenue@33: end Nenue@33: Nenue@33: end Nenue@33: Nenue@35: function WorldQuests:Refresh(forced) Nenue@33: local print = wqprint Nenue@33: print('|cFF00FF88'..self:GetName()..':Refresh()|r') Nenue@34: if not self:IsVisible() then Nenue@34: self.isStale = true Nenue@34: print('frame closed, do it later') Nenue@34: return Nenue@34: end Nenue@34: Nenue@33: self:Reset() Nenue@33: self:UpdateAnchors() Nenue@33: self:Cleanup () Nenue@33: end Nenue@33: Nenue@33: -- prepares elements for a map update Nenue@33: function WorldQuests:Reset () Nenue@33: local print = wqprint Nenue@33: print('|cFF00FF88'..self:GetName()..':Reset()|r') Nenue@33: for questID, pin in pairs(self.QuestsByID) do Nenue@33: pin.used = nil Nenue@33: end Nenue@33: end Nenue@33: Nenue@33: -- update visibility states of all pins Nenue@33: function WorldQuests:UpdateAnchors (submapID) Nenue@33: Nenue@33: local print = wqprint Nenue@33: local db = WorldPlan.db Nenue@33: local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo() Nenue@33: if isMicroDungeon then Nenue@33: return Nenue@33: end Nenue@33: Nenue@33: local currentMap = GetCurrentMapAreaID() Nenue@33: local submapID = submapID or currentMap Nenue@33: Nenue@33: if submapID == BROKEN_ISLES_ID and (not db.DisplayContinentPins) then Nenue@33: print('not updating map for reasons') Nenue@33: return Nenue@33: end Nenue@33: print('|cFF88FF00'..self:GetName()..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins) Nenue@33: local numZones = MC_GetNumZones(submapID) Nenue@33: if numZones then Nenue@33: for i = 1, numZones do Nenue@33: local subMapID = MC_GetZoneInfo(submapID, i) Nenue@33: self:UpdateAnchors(subMapID) Nenue@33: end Nenue@33: end Nenue@33: local pins = self.QuestsByZone[submapID] Nenue@33: Nenue@33: if pins then Nenue@33: local hostFrame = WorldMapPOIFrame Nenue@33: local mapWidth, mapHeight = hostFrame:GetSize() Nenue@33: for questID, pin in pairs(pins) do Nenue@33: pin:IsShowable() Nenue@33: if pin.used then Nenue@35: pin.isStale = true Nenue@35: pin:SetFrameLevel(PinBaseIndex+ numPins) Nenue@35: print('level', PinBaseIndex+ numPins) Nenue@34: pin:SetAnchor(_G.WorldMapPOIFrame, currentMap, mapWidth, mapHeight) Nenue@33: numPins = numPins + 1 Nenue@33: end Nenue@33: end Nenue@33: end Nenue@33: end Nenue@33: Nenue@33: -- shows, animates, or hides pins based on their current visibility flags Nenue@33: local debug_show = {} Nenue@33: local debug_animate = {} Nenue@33: local debug_hide = {} Nenue@33: function WorldQuests:Cleanup () Nenue@33: local print = wqprint Nenue@34: local showQuestPOI = WorldPlan.db.EnablePins Nenue@35: print('|cFFFFFF00'..self:GetName()..':Cleanup()|r') Nenue@34: -- continent or zone sizing Nenue@33: Nenue@33: Nenue@33: numPins = 0 Nenue@33: for questID, pin in pairs(self.QuestsByID) do Nenue@34: pin:SetShown((showQuestPOI and pin.used)) Nenue@33: end Nenue@33: self.isStale = nil Nenue@33: end Nenue@33: Nenue@33: function WorldQuests:FilterCheckByID(questID) Nenue@33: local pin = WorldQuests:GetPinByQuestID(questID) Nenue@33: return pin:IsShowable() Nenue@33: end Nenue@35: Nenue@35: Nenue@35: Nenue@35: Nenue@35: function WorldPlanDataProvider:OnShow() Nenue@35: assert(self.ticker == nil); Nenue@35: self.ticker = C_Timer.NewTicker(10, function() self:RefreshAllData() end); Nenue@35: end Nenue@35: function WorldPlanDataProvider:OnHide() Nenue@35: self.ticker:Cancel(); Nenue@35: self.ticker = nil; Nenue@35: end Nenue@35: Nenue@35: function WorldPlanDataProvider:OnAdded(mapCanvas) Nenue@35: self.activePins = {}; Nenue@35: self.owningMap = mapCanvas Nenue@35: end Nenue@35: Nenue@35: Nenue@35: Nenue@35: function WorldPlanDataProvider:RefreshAllData() Nenue@35: local print = mprint Nenue@35: print('|cFFFF0088'..self.owningMap:GetName()..':RefreshAllData()|r') Nenue@35: Nenue@35: Nenue@35: local pinsToRemove = {}; Nenue@35: for questId in pairs(self.activePins) do Nenue@35: pinsToRemove[questId] = true; Nenue@35: end Nenue@35: Nenue@35: SetMapZoom(8) Nenue@35: Nenue@35: local mapAreaID = self:GetMap():GetMapID(); Nenue@35: for zoneIndex = 1, C_MapCanvas.GetNumZones(mapAreaID) do Nenue@35: local zoneMapID, zoneName, zoneDepth, left, right, top, bottom = C_MapCanvas.GetZoneInfo(mapAreaID, zoneIndex); Nenue@35: print(zoneMapID, zoneName) Nenue@35: if zoneDepth <= 1 then -- Exclude subzones Nenue@35: local taskInfo = C_TaskQuest.GetQuestsForPlayerByMapID(zoneMapID, mapAreaID); Nenue@35: Nenue@35: if taskInfo then Nenue@35: for i, info in ipairs(taskInfo) do Nenue@35: if HaveQuestData(info.questId) then Nenue@35: if QuestUtils_IsQuestWorldQuest(info.questId) then Nenue@35: local pin = WorldPlanQuests:AcquirePin(info.questId, zoneMapID) Nenue@35: pin:RefreshData(info) Nenue@35: pin:IsShowable() Nenue@35: if pin.used then Nenue@35: print(i, pin.x, pin.y, pin.used, pin.isNew, pin.isStale, pin:IsShown(), pin:GetAlpha()) Nenue@35: pinsToRemove[info.questId] = nil; Nenue@35: Nenue@35: local frame = self.activePins[info.questId] Nenue@35: if not frame then Nenue@35: frame = self:GetMap():AcquirePin("WorldPlanPinContainer") Nenue@35: frame:SetAlphaLimits(1, 0.7, 1) Nenue@35: frame:SetScalingLimits(1, 3, 1.5); Nenue@35: frame:SetFrameLevel(1000 + self:GetMap():GetNumActivePinsByTemplate("WorldPlanPinContainer")); Nenue@35: frame:Show() Nenue@35: self.activePins[info.questId] = frame Nenue@35: end Nenue@35: frame:SetPosition(info.x, info.y) Nenue@35: frame.pin = pin Nenue@35: Nenue@35: pin.isStale = true Nenue@35: pin:SetParent(frame) Nenue@35: pin:ClearAllPoints() Nenue@35: pin:SetPoint('CENTER', frame, 'CENTER') Nenue@35: Nenue@35: end Nenue@35: pin:SetShown(pin.used) Nenue@35: end Nenue@35: end Nenue@35: end Nenue@35: end Nenue@35: end Nenue@35: end Nenue@35: Nenue@35: for questId in pairs(pinsToRemove) do Nenue@35: self:GetMap():RemovePin(self.activePins[questId]); Nenue@35: self.activePins[questId] = nil; Nenue@35: end Nenue@35: self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate"); Nenue@35: Nenue@35: end Nenue@35: Nenue@35: function WorldPlanDataPinMixin:OnShow() Nenue@35: mprint('|cFFFFFF00'..tostring(self:GetName())..':OnShow()|r') Nenue@35: end Nenue@35: Nenue@35: function WorldPlanDataPinMixin:OnMouseEnter () Nenue@35: self.pin:OnEnter() Nenue@35: end Nenue@35: Nenue@35: function WorldPlanDataPinMixin:OnMouseLeave () Nenue@35: self.pin:OnLeave() Nenue@35: end Nenue@35: Nenue@35: function WorldPlanDataPinMixin:RemoveAllData() Nenue@35: wipe(self.activePins); Nenue@35: self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate"); Nenue@35: end