Mercurial > wow > worldplan
diff WorldMap.lua @ 109:caa482329919
POI optimization
author | Nenue |
---|---|
date | Mon, 10 Jul 2017 18:34:11 -0400 |
parents | WorldQuests.lua@b67ba1078824 |
children | f6ef9a9f5476 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WorldMap.lua Mon Jul 10 18:34:11 2017 -0400 @@ -0,0 +1,660 @@ +-- WorldPlan +-- WorldMap.lua +-- Created: 11/2/2016 3:40 PM +-- %file-revision% + +local print = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or nop +local rprint = DEVIAN_WORKSPACE and function(...) _G.print('WQRefresh', ...) end or nop +local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or nop +local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or nop +local mprint = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or nop +local _, db = ... +local Module = { + UsedPositions = {}, +} +WorldPlanMapMixin = Module + +local _G = _G +local type, tostring, tonumber, pairs, ipairs = type, tostring, tonumber, pairs, ipairs +local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo +local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented +local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID +local TQ_IsActive = C_TaskQuest.IsActive +local TQ_RequestPreloadRewardData = C_TaskQuest.RequestPreloadRewardData +local pairs, ipairs, tinsert, tremove, wipe = pairs, ipairs, tinsert, tremove, table.wipe +local GetTaskInfo, GetTasksTable, HaveQuestData = GetTaskInfo, GetTasksTable, HaveQuestData +local GetTime = GetTime +local SpellCanTargetQuest, IsQuestIDValidSpellTarget = SpellCanTargetQuest, IsQuestIDValidSpellTarget +local tonumber, abs = tonumber, math.abs +local GetQuestLogRewardInfo = GetQuestLogRewardInfo +local GetCurrentMapAreaID, GetMapInfo, GetMapNameByID = GetCurrentMapAreaID, GetMapInfo, GetMapNameByID +local GetQuestBountyInfoForMapID, GetQuestLogTitle, GetQuestLogIndexByID, IsQuestComplete = GetQuestBountyInfoForMapID, GetQuestLogTitle, GetQuestLogIndexByID, IsQuestComplete +local HaveQuestRewardData = HaveQuestRewardData +local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation +local InCombatLockdown, hooksecurefunc = InCombatLockdown, hooksecurefunc + +local ToggleButton = {} +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 +local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70', [AZSUNA_ID] = 'Azsuna', [VALSHARAH_ID] = "Val'sharah", + [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim', [SURAMAR_ID] = 'Suramar', [EOA_ID] = 'EyeOfAszhara', } + +local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD +local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER +local REWARD_GEAR = WORLD_QUEST_REWARD_TYPE_FLAG_EQUIPMENT +local REWARD_CURRENCY = WORLD_QUEST_REWARD_TYPE_FLAG_ORDER_RESOURCES +local REWARD_REAGENT = WORLD_QUEST_REWARD_TYPE_FLAG_MATERIALS +local SCALE_FACTORS = { 0.25, 0.7, 1 } + +local BountyBoard = WorldMapFrame.UIElementsFrame.BountyBoard +local ActionButton = WorldMapFrame.UIElementsFrame.ActionButton +local defaults = {} +local completedQuests = {} + +local continentScanned +local layoutDirty = true +local bountiesDirty = true +local artifactPowerDirty = true +local hooksDirty = true +local currentScale = WorldMapDetailFrame:GetScale() +local canTargetQuests +local isDataLoaded = true +local artifactKnowledgeLevel +local superTrackedQuestID +local lastRefresh +local refreshReason + +local bountyQuests = {} +local bountyInfo = {} +local bountyDisplayLocation, bountyLockedQuestID, selectedBountyIndex, selectedBountyQuestID + +local totalPins = 0 +local numShown = 0 +local numLoaded = 0 +local numOverlays = 1 +local scaleConstant = 1 +local pinBaseIndex = 1550 +local overlayBaseIndex = 1600 + +local artifactKnowldegeSpells = { + [207856] = true, + [209203] = true, + [209204] = true, + [209205] = true, + [209206] = true, + [209207] = true, + [209208] = true, + [209209] = true, + [209210] = true, + [209211] = true, + [209212] = true, + [219978] = true, + [227852] = true, + [236477] = true, + [236489] = true, + [236302] = true, + [236488] = true, + [236490] = true, + [240475] = true, + [243176] = true, + [243177] = true, + [243178] = true, + [243182] = true, + [243183] = true, + [243187] = true, + [245133] = true, +} + +--%debug% +local SetTimedCallbackForAllPins = function(seconds, callback) + C_Timer.After(seconds, function() + for id, pin in pairs(WorldPlanQuests.QuestsByID) do + callback(pin) + end + end) +end + +function Module:OnLoad() + --print('|cFFFF4400'..self:GetName()..':OnLoad()') + + self:SetParent(WorldMapFrame.UIElementsFrame) + WorldPlan:AddHandler(self, defaults) + + for areaID, fileName in pairs(WORLD_QUEST_MAPS) do + db.QuestsByZone[areaID] = {} + end + + -- WORLD_MAP_UPDATE and PLAYER_ENTERING_WORLD are passed down from a higher level + self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL') + self:RegisterEvent('SUPER_TRACKED_QUEST_CHANGED') + self:RegisterEvent('SKILL_LINES_CHANGED') + self:RegisterEvent('ARTIFACT_UPDATE') + self:RegisterEvent('QUEST_LOG_UPDATE') + self:RegisterEvent('UNIT_SPELLCAST_STOP') +end + +function Module:OnEvent (event, ...) + print('|cFFFFFF00OnEvent() '..event..'|r', GetTime(), ...) + if (event == 'QUEST_LOG_UPDATE') then + self:UpdateBounties(event) + elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then + local questID = ... + if questID and db.QuestsByID[questID] then + completedQuests[questID] = true + db.QuestsByID[questID]:Release() + end + self:Refresh(event) + elseif event == 'SKILL_LINES_CHANGED' or event == 'CURRENT_SPELL_CAST_CHANGED' then + self:Refresh(event) + elseif event == 'ARTIFACT_UPDATE' then + self:UpdateArtifactPower() + elseif event == 'MODIFIER_STATE_CHANGED' then + self:UpdateModifierState() + elseif event == 'SUPER_TRACKED_QUEST_CHANGED' then + if superTrackedQuestID and db.QuestsByID[superTrackedQuestID] then + db.QuestsByID[superTrackedQuestID].isStale = true + end + local newID = GetSuperTrackedQuestID() + if newID and db.QuestsByID[newID] then + db.QuestsByID[newID].isStale = true + end + elseif event == 'UNIT_SPELLCAST_STOP' then + local name, _, _, _, spellID = ... + if artifactKnowldegeSpells[spellID] then + db.log('AK spellcast ended ' .. tostring(name) .. ' ('.. tostring(spellID)..')') + self:UpdateArtifactPower() + end + end +end + +function Module:OnUpdate(sinceLast) + if WorldPlanData.DebugEnabled then + if self.refreshBenchMarkTicker then + --print(self.refreshBenchMarkTicker) + self.refreshBenchMarkTicker = self.refreshBenchMarkTicker - 1 + + if self.refreshBenchMarkTicker == 0 then + + self.refreshTime = floor((GetTime() - self.refreshBenchMark) * 1000) + self.debugMessage:SetText(self.refreshTime) + self.refreshBenchMarkTicker = nil + end + else + self.refreshBenchMark = GetTime() + end + end + + if self.filtersDirty or self.isStale then + self:Refresh() + end + + if #db.UpdatedPins >= 1 then + --print('|cFF00FF88pending update', #db.UpdatedPins) + self:UpdateNext() + end +end + +local callbacks = {} +callbacks.ClickWorldMapActionButton = function(WorldQuests) + WorldQuests:Refresh('CLICK_MAP_ACTION_BUTTON') +end +callbacks.WorldMap_UpdateQuestBonusObjectives = function(WorldQuests) + WorldQuests:UpdateTaskPOIs() +end +callbacks.WorldMapFrame_UpdateMap = function(WorldQuests) + WorldQuests:RefreshIfChanged('WMF_UPDATE') +end +callbacks.WorldMapScrollFrame_ReanchorQuestPOIs = function (WorldQuests) + WorldQuests:RefreshIfChanged('WMF_REANCHOR') +end + +callbacks[BountyBoard] = {} +callbacks[BountyBoard].SetSelectedBountyIndex = function(WorldQuests) + WorldQuests:UpdateBounties('BOUNTY_SELECTED') + for questID, pin in pairs(db.QuestsByID) do + pin.checkCriteria = true + pin:Refresh() + end +end + +callbacks[ActionButton] = {} +callbacks[ActionButton].UpdateCastingState = function(WorldQuests) + for questID, pin in pairs(db.QuestsByID) do + pin.checkCursor = true + pin:Refresh() + end +end + +callbacks.UseWorldMapActionButtonSpellOnQuest = function(questID) + local pin = db.QuestsByID[questID] + -- calling this implies that the pin is used in some way + if pin then + db.log(pin.title .. ' completed by spell?', IsQuestComplete(pin.questID)) + pin:OnFilters() + pin.isStale = true + end +end + +function Module:SetupCallbacks() + if InCombatLockdown() then + return true + end + print('SetupCallbacks()') + for target, arg in pairs(callbacks) do + --print(type(target)) + if type(target) == 'table' then + local callerName = target:GetName() or tostring(target) + for name, method in pairs(arg) do + --print(callerName, arg) + hooksecurefunc(target, name, function(...) + self:OnSecureHook(callerName .. '.' .. name, method, ...) + end) + end + else + hooksecurefunc(target, function(...) + self:OnSecureHook(target, arg, ...) + end) + end + end +end + +function Module:Setup() + --print('|cFFFF4400'..self:GetName()..':Setup()') + for mapID, mapName in pairs(WORLD_QUEST_MAPS) do + db.QuestsByZone[mapID] = {} + end + + hooksDirty = self:SetupCallbacks() + + self:SetAllPoints(WorldMapFrame.UIElementsFrame) + self:UpdateArtifactPower() + self:UpdateBounties('SETUP') + self:Show() +end + +function Module:OnMapInfo(isBrokenIsle, isZoomedOut, mapAreaID, isNewMap, isMapOpen) + if isNewMap or self.isStale then + print('|cFF0088FFOnMapInfo()|r, mapAreaID =', mapAreaID,'visible =', isMapOpen, 'changed =', isNewMap) + layoutDirty = true + self:Refresh('WORLD_MAP_CHANGED') + end +end + +function Module:OnConfigUpdate() + --print('|cFFFFFF00OnConfigUpdate()|r') + if db.Config.FadeWhileGrouped then + db.PinAlpha = 0.15 + else + db.PinAlpha = 1 + end + + if not db.Config.EnablePins then + for _, pin in pairs(db.QuestsByID) do + pin:SetShown(false) + end + end +end + +function Module:OnSecureHook(callbackName, func, ...) + print('|cFFFF4400'..callbackName..'|r', ...) + func(self, ...) +end + +function Module:UpdateModifierState() + +end + +function Module:UpdateTaskPOIs() + canTargetQuests = SpellCanTargetQuest() + for i = 1, NUM_WORLDMAP_TASK_POIS do + local poiFrame = _G['WorldMapFrameTaskPOI'..i] + if poiFrame and poiFrame.worldQuest then + local pin = db.QuestsByID[poiFrame.questID] + if pin and pin.used and canTargetQuests and IsQuestIDValidSpellTarget(pin.questID) then + poiFrame:Show() + else + poiFrame:Hide() + end + end + end +end +-- re-anchors and scales pins that have had either of these changed due to data loading delays +function Module:UpdateNext() + --print('|cFF00FF88UpdateNext()') + local pin = tremove(db.UpdatedPins) + pin:OnFilters() + + local scaleFactor = SCALE_FACTORS[(pin.dataLoaded and not pin.filtered) and scaleConstant or 1] + --print(pin.title, pin.dataLoaded and not pin.filtered, scaleFactor) + if pin.used then + pin:SetShown(true) + pin:SetAnchor(nil, pin.x, pin.y, self.hostWidth, self.hostHeight, scaleFactor) + pin:Refresh() + else + print('|cFFFF4400flagging queued pin that got hidden:', pin.title) + pin.isStale = true + end +end + +function Module:UpdateBounties(...) + bountiesDirty = nil + print('|cFF00FF88BountyInfo()|r', ...) + wipe(db.BountiesByFactionID) + wipe(db.BountiesByQuestID) + + db.selectedBounty = nil + selectedBountyIndex = BountyBoard:GetSelectedBountyIndex() + db.Bounties, bountyDisplayLocation, bountyLockedQuestID = GetQuestBountyInfoForMapID(db.currentMapID, db.Bounties) + local numBounties = 0 + for index, info in ipairs(db.Bounties) do + numBounties = numBounties + 1 + info.index = index + info.complete = IsQuestComplete(info.questID) + if not info.complete then + db.BountiesByFactionID[info.factionID] = info + db.BountiesByQuestID[info.questID] = info + if index == selectedBountyIndex then + db.selectedBounty = info + selectedBountyQuestID = info.questID + end + print(' ', index, info.factionID, GetQuestLogTitle(GetQuestLogIndexByID(info.questID)), info.complete, (index == selectedBountyIndex) and 'SELECTED' or '') + end + end +end + +-- check current artifact knowledge and update pins accordingly +function Module:UpdateArtifactPower(overrideLevel) + if InCombatLockdown() then + artifactPowerDirty = true + return + end + + print('|cFF00FF88UpdateArtifactPower()|r') + local _, akLevel = GetCurrencyInfo(1171) + if overrideLevel then + akLevel = overrideLevel + end + + --db.print('current AK', akLevel) + if akLevel and (akLevel ~= artifactKnowledgeLevel) or (not artifactKnowledgeLevel) then + --print('new ak level', akLevel) + db.log('AK update ' .. tostring(artifactKnowledgeLevel) .. ' to '.. tostring(akLevel)) + for _, pin in pairs(db.QuestsByID) do + if (pin.rewardType == REWARD_ARTIFACT_POWER) then + print(pin.title, pin.itemNumber) + local newAP = pin:UpdateArtifactPower() + if newAP then + pin.itemNumber = newAP + print(newAP) + else + pin.dataLoaded = nil + end + pin.isStale = true + end + end + artifactKnowledgeLevel = akLevel + end + artifactPowerDirty = nil +end + +local msg = '|cFF00FF88WorldQuests:Refresh()|r|cFF00FFFF' +function Module:Refresh(...) + + if hooksDirty then + hooksDirty = self:SetupCallbacks() + end + + + if not self:IsVisible() then + print('|cFFFF4400Refresh()|r', ...) + return + else + if lastRefresh == GetTime() then + print('|cFFFF4400multiple refreshes tried') + end + lastRefresh = GetTime() + print(msg, lastRefresh, ...) + end + + + if bountiesDirty then + self:UpdateBounties() + end + + if not db.Config.EnablePins then + numShown = 0 + self.refreshBenchMark = GetTime() + self.refreshBenchMarkTicker = 2 + print('starting bench', self.refreshBenchMark) + return + end + + scaleConstant = db.isContinentMap and 2 or 3 + canTargetQuests = SpellCanTargetQuest() + + for index, pin in pairs(db.QuestsByID) do + pin.used = nil + end + + self:UpdateAnchors(...) + + if artifactPowerDirty and not InCombatLockdown() then + self:UpdateArtifactPower() + end + -- calculate quests shown + numShown = 0 + numLoaded = 0 + for questID, pin in pairs(db.QuestsByID) do + local oV = pin:IsShown() + if pin.used then + print('show', pin.title) + pin.throttle = 1 + pin:SetShown(true) + numShown = numShown + 1 + if pin.dataLoaded then + numLoaded = numLoaded + 1 + end + + pin.checkCriteria = true + pin.checkFilters = true + pin:Refresh('WORLDMAP_REFRESH ' .. GetTime()) + + else + if pin:IsShown() then + print('|cFFFF4400need to remove', pin.title) + + end + + pin.hideReason = "Not used in map area " .. (db.currentMapID) + pin:SetShown(false) + end + + end + + +-- + self.refreshBenchMark = GetTime() + self.refreshBenchMarkTicker = 2 + print('starting bench', self.refreshBenchMark) + +-- + + layoutDirty = nil + self.isStale = nil + self.sizesDirty = nil + self.isZoomDirty = nil + + if WorldPlanSummary then + WorldPlanSummary.isStale = true + end + +end + +function Module:RefreshIfChanged(event) + local scaleCheck = WorldMapDetailFrame:GetScale() + refreshReason = nil + if scaleCheck ~= currentScale then + refreshReason = 'map scale updated' + currentScale = scaleCheck + layoutDirty = true + elseif self.isStale or layoutDirty then + refreshReason = 'layout is marked dirty' + end + if not refreshReason then + return + end + + if self:IsVisible() then + print('|cFF00FFFFRefreshIfChanged()|r', refreshReason) + self:Refresh(event) + else + print('|cFF00FFFFRefreshIfChanged()|r', refreshReason) + self.isStale = true + end +end + +-- Walks the current map tree and fires updates as needed +function Module:UpdateAnchors (event) + wipe(self.UsedPositions) + local hostWidth, hostHeight = WorldMapPOIFrame:GetSize() + + if (hostWidth ~= self.hostWidth) or (hostHeight ~= self.hostHeight) then + self.hostWidth, self.hostHeight = hostWidth, hostHeight + layoutDirty = true + end + + print('|cFF00FF00UpdateAnchors()', event) + local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo() + if isMicroDungeon then + return + end + + isDataLoaded = true + local taskInfo = TQ_GetQuestsForPlayerByMapID(db.currentMapID) + if taskInfo then + self:UpdateQuestsForMap(taskInfo, db.currentMapID) + end + local numZones = MC_GetNumZones(db.currentMapID) + if numZones then + for i = 1, numZones do + local mapAreaID = MC_GetZoneInfo(db.currentMapID, i) + local taskInfo = TQ_GetQuestsForPlayerByMapID(mapAreaID, db.currentMapID) + + db.QuestsByZone[mapAreaID] = db.QuestsByZone[mapAreaID] or {} + + if taskInfo then + self:UpdateQuestsForMap(taskInfo, mapAreaID) + end + end + end +end + +-- Attempt to display the pins for quests in taskInfo +function Module:UpdateQuestsForMap(taskInfo, mapID) + print('|cFF00FF00UpdateQuestsForMap()|r', GetMapNameByID(mapID), GetMapNameByID(db.currentMapID), layoutDirty) + if db.QuestsByZone[mapID] then + wipe(db.QuestsByZone[mapID]) + elseif db.isBrokenIsle then + continentScanned = true + end + db.PinStrata = WorldMapFrame_InWindowedMode() and 'HIGH' or 'FULLSCREEN' + print('layoutDirty =',layoutDirty) + + for index, info in pairs(taskInfo) do + local questID, x, y = info.questId, info.x, info.y + local pin = self:AcquirePin(info) + if pin then + if pin.canShow then + pin.used = true + print('using', pin.title, (pin.owningFrame ~= WorldMapFrame)) + if layoutDirty or (pin.owningFrame ~= WorldMapFrame) then + local scaleFactor = SCALE_FACTORS[(not pin.filtered and scaleConstant) or 1] + pin.owningFrame = WorldMapFrame + pin:SetAnchor(WorldMapPOIFrame, x, y, self.hostWidth, self.hostHeight, scaleFactor) + + end + if db.QuestsByZone[mapID] then + db.QuestsByZone[mapID][questID] = pin + end + else + print('|cFFFF4400discarding|r', pin.title) + end + end + end +end + +-- locates or creates a corresponding pin frame for the provided TaskInfo data +function Module:AcquirePin (info) + local questID = info.questId + if not (questID and QuestUtils_IsQuestWorldQuest(questID)) then + return nil + end + local pin = db.QuestsByID[questID] + -- check to avoid creating unnecessary frames + if IsQuestComplete(questID) or completedQuests[questID] then + completedQuests[questID] = true + if pin then + pin:Release() + end + return nil + end + + if not pin then + local numFree = #db.FreePins + if numFree >= 1 then + pin = tremove(db.FreePins, numFree) + print('|cFF00FF00Acquire()|r Re-using', pin:GetName()) + else + totalPins = totalPins + 1 + local name = 'WorldPlanQuestMarker' .. numOverlays + print('|cFF00FF00Acquire()|r Creating', name) + pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin') + + pin:SetID(totalPins) + numOverlays = numOverlays + 1 + --pin.iconBorder:SetVertexColor(0,0,0,1) + end + pin.questID = questID + pin.throttle = pin.updateRate + pin.currentWidth = nil + + db.QuestsByID[questID] = pin + tinsert(db.UsedPins, pin) + end + + if info then + pin.inProgress = info.inProgress + pin.floor = info.floor + pin.numObjectives = info.numObjectives or 0 + if info.x and info.y then + if (info.x ~= pin.x) or (info.y ~= pin.y) then + pin.isStale = true + --rprint('|cFFFF4400SetCoords|r', info.x, info.y) + end + end + end + + pin.x = info.x or pin.x + pin.y = info.y or pin.y + + if not HaveQuestRewardData(questID) then + TQ_RequestPreloadRewardData(questID); + end + + if (not pin.dataLoaded) then + local dataLoaded = pin:GetData() + if dataLoaded then + WorldPlan.dataFlush = true + else + isDataLoaded = false + end + end + + pin:OnFilters() + pin.isActive = TQ_IsActive(questID) + --rprint(pin:GetID(), pin.filtered, pin.used) + return pin +end + +function Module:Debug(...) + print(...) +end \ No newline at end of file