---@class QuestieMap local QuestieMap = QuestieLoader:CreateModule("QuestieMap"); ---@type QuestieMapUtils QuestieMap.utils = QuestieMap.utils or {} ------------------------- --Import modules. ------------------------- ---@type QuestieFramePool local QuestieFramePool = QuestieLoader:ImportModule("QuestieFramePool"); ---@type QuestieDBMIntegration local QuestieDBMIntegration = QuestieLoader:ImportModule("QuestieDBMIntegration"); ---@type QuestieLib local QuestieLib = QuestieLoader:ImportModule("QuestieLib"); ---@type QuestiePlayer local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer"); ---@type QuestieDB local QuestieDB = QuestieLoader:ImportModule("QuestieDB"); ---@type ZoneDB local ZoneDB = QuestieLoader:ImportModule("ZoneDB") ---@type l10n local l10n = QuestieLoader:ImportModule("l10n") ---@type WeaponMasterSkills local WeaponMasterSkills = QuestieLoader:ImportModule("WeaponMasterSkills") --- COMPATIBILITY --- local C_Timer = QuestieCompat.C_Timer local C_Map = QuestieCompat.C_Map QuestieMap.ICON_MAP_TYPE = "MAP"; QuestieMap.ICON_MINIMAP_TYPE = "MINIMAP"; --Useful links. -- https://github.com/tomrus88/BlizzardInterfaceCode/blob/master/Interface/SharedXML/Pools.lua -- https://www.townlong-yak.com/framexml/27101/Blizzard_MapCanvas/Blizzard_MapCanvas.lua -- https://www.townlong-yak.com/framexml/27101/Blizzard_MapCanvas/MapCanvas_DataProviderBase.lua -- https://www.townlong-yak.com/framexml/27101/Blizzard_MapCanvas/MapCanvas_PinFrameLevelsManager.lua -- List of frames sorted by quest ID (automatic notes) -- E.g. {[questId] = {[frameName] = frame, ...}, ...} -- For details about frame.data see calls to QuestieMap.DrawWorldIcon QuestieMap.questIdFrames = {} -- List of frames sorted by NPC/object ID (manual notes) -- id > 0: NPC -- id < 0: object -- E.g. {[-objectId] = {[frameName] = frame, ...}, ...} -- For details about frame.data see QuestieMap.ShowNPC and QuestieMap.ShowObject QuestieMap.manualFrames = {} --Used in my fadelogic. local fadeOverDistance = 10; local normalizedValue = 1 / fadeOverDistance; --Opacity / Distance to fade over local HBD = QuestieCompat.HBD or LibStub("HereBeDragonsQuestie-2.0") local HBDPins = QuestieCompat.HBDPins or LibStub("HereBeDragonsQuestie-Pins-2.0") --We should really try and squeeze out all the performance we can, especially in this. local tostring = tostring; local tinsert = table.insert; local tremove = table.remove; local tunpack = unpack; local drawTimer local fadeLogicTimerShown local fadeLogicCoroutine local isDrawQueueDisabled = false --* TODO: How the frames are handled needs to be reworked, why are we getting them from _G --Get the frames for a quest, this returns all of the frames function QuestieMap:GetFramesForQuest(questId) local frames = {} --If no frames exists or if the quest does not exist we just return an empty list if QuestieMap.questIdFrames[questId] then for _, name in next, QuestieMap.questIdFrames[questId] do if _G[name] then frames[name] = _G[name] end end end return frames end function QuestieMap:UnloadQuestFrames(questId, iconType) if QuestieMap.questIdFrames[questId] then if not iconType then local frameList = QuestieMap:GetFramesForQuest(questId) for _, frame in next, frameList do frame:Unload(); end QuestieMap.questIdFrames[questId] = nil; else local frameList = QuestieMap:GetFramesForQuest(questId) for name, frame in next, frameList do if frame and frame.data and frame.data.Icon == iconType then frame:Unload(); QuestieMap.questIdFrames[questId][name] = nil _G[name] = nil end end end Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap] Unloading quest frames for questid:", questId) end end --Get the frames for manual note, this returns all of the frames/spawns ---@param id number @The ID of the NPC (>0) or object (<0) function QuestieMap:GetManualFrames(id, typ) typ = typ or "any" local frames = {} --If no frames exists or if the quest does not exist we just return an empty list if QuestieMap.manualFrames[typ] and (QuestieMap.manualFrames[typ][id]) then for _, name in next, QuestieMap.manualFrames[typ][id] do tinsert(frames, _G[name]) end end return frames end ---@param id number @The ID of the NPC (>0) or object (<0) function QuestieMap:UnloadManualFrames(id, typ) typ = typ or "any" if QuestieMap.manualFrames[typ] and (QuestieMap.manualFrames[typ][id]) then local frameList = QuestieMap:GetManualFrames(id, typ) for _, frame in next, frameList do frame:Unload(); end QuestieMap.manualFrames[typ][id] = nil; end end function QuestieMap:ResetManualFrames(typ) typ = typ or "any" for id in next, QuestieMap.manualFrames[typ] do QuestieMap:UnloadManualFrames(id, typ) end end -- Rescale all the icons function QuestieMap:RescaleIcons() local mapScale = QuestieMap.GetScaleValue() for _, framelist in next, QuestieMap.questIdFrames do for _, frameName in next, framelist do QuestieMap.utils:RescaleIcon(frameName, mapScale) end end for _, frameTypeList in next, QuestieMap.manualFrames do for _, framelist in next, frameTypeList do for _, frameName in next, framelist do QuestieMap.utils:RescaleIcon(frameName, mapScale) end end end end local mapDrawQueue = {}; local minimapDrawQueue = {}; QuestieMap._mapDrawQueue = mapDrawQueue QuestieMap._minimapDrawQueue = minimapDrawQueue function QuestieMap:InitializeQueue() -- now called on every loading screen Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap] Starting draw queue timer!") local isInInstance, instanceType = IsInInstance() if (not isInInstance) or instanceType ~= "raid" then -- only run map updates when not in a raid isDrawQueueDisabled = false if not drawTimer then drawTimer = C_Timer.NewTicker(0.2, QuestieMap.ProcessQueue) -- ! Remember to update the distance variable in ProcessShownMinimapIcons if you change the timer fadeLogicTimerShown = C_Timer.NewTicker(0.1, function() if fadeLogicCoroutine and coroutine.status(fadeLogicCoroutine) == "suspended" then local success, errorMsg = coroutine.resume(fadeLogicCoroutine) if (not success) then Questie:Error("Please report on Github or Discord. Minimap pins fade logic coroutine stopped:", errorMsg) fadeLogicCoroutine = nil end end end) end if not fadeLogicCoroutine then fadeLogicCoroutine = coroutine.create(QuestieMap.ProcessShownMinimapIcons) end else if drawTimer then -- cancel existing timer while in dungeon/raid drawTimer:Cancel() drawTimer = nil fadeLogicTimerShown:Cancel() fadeLogicTimerShown = nil end isDrawQueueDisabled = true end end ---@return number @A scale value that is based of the map currently open, smaller icons for World and Continent function QuestieMap.GetScaleValue() local mapId = HBDPins.worldmapProvider:GetMap():GetMapID(); local scaling = 1; if C_Map and C_Map.GetAreaInfo then local mapInfo = C_Map.GetMapInfo(mapId) if (mapInfo.mapType == 0) then --? Cosmic, This is probably not needed but for the sake of completion... scaling = 0.85 elseif (mapInfo.mapType == 1) then -- World scaling = 0.85 elseif (mapInfo.mapType == 2) then -- Continent scaling = 0.9 end end return scaling end function QuestieMap:ProcessShownMinimapIcons() --Upvalue the most used functions in here local getTime, cYield, getWorldPos = GetTime, coroutine.yield, HBD.GetPlayerWorldPosition --Max icons per tick local maxCount = 50 --Local variables defined here instead of in loop --saves time because it doesn't need to remake the variables local doEdgeUpdate = true local playerX, playerY local count local lastUpdate = getTime() local xd, yd local totalDistance = 0 --This coroutine never dies, we want it to keep looping forever --yield stops it from being "infinite" and crashing the game while true do count = 0 playerX, playerY = getWorldPos() --Calculate squared distance -- No need for absolute values as these are used only as squared xd = (playerX or 0) - (QuestieMap.playerX or 0) yd = (playerY or 0) - (QuestieMap.playerY or 0) --Instead of math.sqrt we just used the square distance for speed totalDistance = totalDistance + (xd * xd + yd * yd) --These variables are used inside the fadelogic QuestieMap.playerX = playerX QuestieMap.playerY = playerY -- Only update icons on the edge every 1 seconds -- totalDistance is used because sometimes we move so fast that we need to update it more often. -- ! Remember to update the distance variable if you change the timer if totalDistance > 3 or getTime() - lastUpdate >= 1 then doEdgeUpdate = true lastUpdate = getTime() --print("Dist:", totalDistance) totalDistance = 0 end ---@param minimapFrame IconFrame for minimapFrame, data in next, HBDPins.activeMinimapPins do if minimapFrame.miniMapIcon and ((data.distanceFromMinimapCenter < 1.1) or doEdgeUpdate) then if minimapFrame.FadeLogic then minimapFrame:FadeLogic() end if minimapFrame.GlowUpdate then minimapFrame:GlowUpdate() end end --Never run more than maxCount in a single run if count > maxCount then cYield() if (not HBDPins.activeMinimapPins[minimapFrame]) then -- table has been edited during traversal at critical key. we can't continue iterating over it. stop iteration and start again. Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap:ProcessShownMinimapIcons] FadeLogic loop coroutine: HBDPins.activeMinimapPins doesn't have the key anymore.") -- force reupdate imeadiately totalDistance = 9000 break end count = 0 else count = count + 1 end end cYield() doEdgeUpdate = false end end function QuestieMap:QueueDraw(drawType, ...) if (not isDrawQueueDisabled) then -- dont queue when in raid if (drawType == QuestieMap.ICON_MAP_TYPE) then tinsert(mapDrawQueue, { ... }); elseif (drawType == QuestieMap.ICON_MINIMAP_TYPE) then tinsert(minimapDrawQueue, { ... }); end end end function QuestieMap.ProcessQueue() if (not next(mapDrawQueue) and (not next(minimapDrawQueue))) then -- Nothing to process return end local scaleValue = QuestieMap.GetScaleValue() for _ = 1, math.min(24, math.max(table.getn(mapDrawQueue), table.getn(minimapDrawQueue))) do local mapDrawCall = tremove(mapDrawQueue, 1); if mapDrawCall then local frame = mapDrawCall[2]; HBDPins:AddWorldMapIconMap(tunpack(mapDrawCall)); --? If you ever chanage this logic, make sure you change the logic in QuestieMap.utils:RescaleIcon function too! local size = (16 * (frame.data.IconScale or 1) * (Questie.db.profile.globalScale or 0.7)) * scaleValue; frame:SetSize(size, size) QuestieMap.utils:SetDrawOrder(frame); end local minimapDrawCall = tremove(minimapDrawQueue, 1); if minimapDrawCall then local frame = minimapDrawCall[2]; HBDPins:AddMinimapIconMap(tunpack(minimapDrawCall)); QuestieMap.utils:SetDrawOrder(frame); end mapDrawCall[2]._loaded = true if mapDrawCall[2]._needsUnload then mapDrawCall[2]:Unload() end minimapDrawCall[2]._loaded = true if minimapDrawCall[2]._needsUnload then minimapDrawCall[2]:Unload() end end end -- Show NPC on map -- This function does the same for manualFrames as similar functions in -- QuestieQuest do for questIdFrames ---@param npcID number @The ID of the NPC function QuestieMap:ShowNPC(npcID, icon, scale, title, body, disableShiftToRemove, typ, excludeDungeon) if type(npcID) ~= "number" then Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:ShowNPC] Got <" .. type(npcID) .. "> instead of ") return end -- get the NPC data local npc = QuestieDB:GetNPC(npcID) if (not npc) or (not npc.spawns) then return end -- create the icon data local data = {} data.id = npc.id data.Icon = icon or "Interface\\WorldMap\\WorldMapPartyIcon" data.GetIconScale = function() return scale or Questie.db.profile.manualScale or 0.7 end data.IconScale = data:GetIconScale() data.Type = "manual" data.spawnType = "monster" data.npcData = npc data.Name = npc.name data.IsObjectiveNote = false data.ManualTooltipData = {} local baseTitle = title or (npc.name .. " (" .. l10n("NPC") .. ")") data.ManualTooltipData.Title = WeaponMasterSkills.AppendSkillsToTitle(baseTitle, data.id) local level = tostring(npc.minLevel) local health = tostring(npc.minLevelHealth) if npc.minLevel ~= npc.maxLevel then level = level .. '-' .. tostring(npc.maxLevel) health = health .. '-' .. tostring(npc.maxLevelHealth) end data.ManualTooltipData.Body = body or { { 'ID:', tostring(npc.id) }, { 'Level:', level }, { 'Health:', health }, } data.ManualTooltipData.disableShiftToRemove = disableShiftToRemove local manualIcons = {} -- draw the notes for zone, spawns in next, npc.spawns do if (zone ~= nil and spawns ~= nil) and ((not excludeDungeon) or (not ZoneDB.IsDungeonZone(zone))) then local spIndex = 1 while spawns[spIndex] do local coords = spawns[spIndex] -- instance spawn, draw entrance on map local dungeonLocation = ZoneDB:GetDungeonLocation(zone) if dungeonLocation ~= nil then local dlIndex = 1 while dungeonLocation[dlIndex] do local value = dungeonLocation[dlIndex] QuestieMap:DrawManualIcon(data, value[1], value[2], value[3], typ) dlIndex = dlIndex + 1 end -- world spawn else manualIcons[zone] = QuestieMap:DrawManualIcon(data, zone, coords[1], coords[2], typ) end spIndex = spIndex + 1 end end end -- draw waypoints if npc.waypoints then for zone, waypoints in next, npc.waypoints do if not ZoneDB:GetDungeonLocation(zone) and waypoints[1] and waypoints[1][1] and waypoints[1][1][1] then if not manualIcons[zone] then manualIcons[zone] = QuestieMap:DrawManualIcon(data, zone, waypoints[1][1][1], waypoints[1][1][2]) end QuestieMap:DrawWaypoints(manualIcons[zone], waypoints, zone) end end end end -- Show object on map -- This function does the same for manualFrames as similar functions in -- QuestieQuest do for questIdFrames ---@param objectID number function QuestieMap:ShowObject(objectID, icon, scale, title, body, disableShiftToRemove, typ) if type(objectID) ~= "number" then return end -- get the gameobject data local object = QuestieDB:GetObject(objectID) if not object or not object.spawns then return end -- create the icon data local data = {} -- hack: clean up this code, we shouldnt be using negative indexes if typ then data.id = object.id else data.id = -object.id end data.Icon = icon or "Interface\\WorldMap\\WorldMapPartyIcon" data.GetIconScale = function() return scale or Questie.db.profile.manualScale or 0.7 end data.IconScale = data:GetIconScale() data.Type = "manual" data.spawnType = "object" data.objectData = object data.Name = object.name data.IsObjectiveNote = false data.ManualTooltipData = {} data.ManualTooltipData.Title = title or (object.name .. " (object)") data.ManualTooltipData.Body = body or { { 'ID:', tostring(object.id) }, } data.ManualTooltipData.disableShiftToRemove = disableShiftToRemove -- draw the notes for zone, spawns in next, object.spawns do if (zone ~= nil and spawns ~= nil) then local spIndex = 1 while spawns[spIndex] do local coords = spawns[spIndex] -- instance spawn, draw entrance on map local dungeonLocation = ZoneDB:GetDungeonLocation(zone) if dungeonLocation ~= nil then local dlIndex = 1 while dungeonLocation[dlIndex] do local value = dungeonLocation[dlIndex] QuestieMap:DrawManualIcon(data, value[1], value[2], value[3], typ) dlIndex = dlIndex + 1 end -- world spawn else QuestieMap:DrawManualIcon(data, zone, coords[1], coords[2], typ) end spIndex = spIndex + 1 end end end end function QuestieMap:DrawLineIcon(lineFrame, areaID, x, y) if type(areaID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then error("Questie" .. ": AddWorldMapIconMap: 'AreaID', 'x' and 'y' must be numbers " .. areaID .. " " .. x .. " " .. y) end local uiMapId = ZoneDB:GetUiMapIdByAreaId(areaID) HBDPins:AddWorldMapIconMap(Questie, lineFrame, uiMapId, x, y, HBD_PINS_WORLDMAP_SHOW_CURRENT) end -- Draw manually added NPC/object notes -- TODO: item and custom notes --@param data table<...> @A table created by the calling function, must contain `id`, `Name`, `GetIconScale()`, and `Type` --@param AreaID number @The zone ID from the raw data --@param x float @The X coordinate in 0-100 format --@param y float @The Y coordinate in 0-100 format function QuestieMap:DrawManualIcon(data, areaID, x, y, typ) if type(data) ~= "table" then error("Questie" .. ": AddWorldMapIconMap: must have some data") end if type(areaID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then error("Questie" .. ": AddWorldMapIconMap: 'AreaID', 'x' and 'y' must be numbers " .. areaID .. " " .. x .. " " .. y) end if type(data.id) ~= "number" or type(data.id) ~= "number" then error("Questie" .. "Data.id must be set to the NPC or object ID!") end -- this needs to be refactored. Fix the capitalization. Who made this id instead of Id? data.Id = data.id local uiMapId = ZoneDB:GetUiMapIdByAreaId(areaID) if (not uiMapId) then Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawManualIcon] No UiMapID for areaId:", areaID, tostring(data.Name)) return nil, nil end -- set the icon local texture = data.Icon or "Interface\\WorldMap\\WorldMapPartyIcon" -- Save new zone ID format, used in QuestieFramePool -- create a list for all frames belonging to a NPC (id > 0) or an object (id < 0) typ = typ or "any" if not QuestieMap.manualFrames[typ] then QuestieMap.manualFrames[typ] = {} end if not QuestieMap.manualFrames[typ][data.id] then QuestieMap.manualFrames[typ][data.id] = {} end -- create the map icon local icon = QuestieFramePool:GetFrame() icon.data = data icon.x = x icon.y = y icon.AreaID = areaID -- used by QuestieFramePool icon.UiMapID = uiMapId icon.miniMapIcon = false; icon.texture:SetTexture(texture) icon:SetWidth(16 * (data:GetIconScale() or 0.7)) icon:SetHeight(16 * (data:GetIconScale() or 0.7)) -- add the map icon QuestieMap:QueueDraw(QuestieMap.ICON_MAP_TYPE, Questie, icon, icon.UiMapID, x / 100, y / 100, 3) -- showFlag) tinsert(QuestieMap.manualFrames[typ][data.id], icon:GetName()) -- create the minimap icon local iconMinimap = QuestieFramePool:GetFrame() local colorsMinimap = { 1, 1, 1 } if data.IconColor ~= nil and Questie.db.profile.questMinimapObjectiveColors then colorsMinimap = data.IconColor end iconMinimap:SetWidth(16 * ((data:GetIconScale() or 1) * (Questie.db.profile.globalMiniMapScale or 0.7))) iconMinimap:SetHeight(16 * ((data:GetIconScale() or 1) * (Questie.db.profile.globalMiniMapScale or 0.7))) iconMinimap.data = data iconMinimap.x = x iconMinimap.y = y iconMinimap.AreaID = areaID -- used by QuestieFramePool iconMinimap.UiMapID = uiMapId iconMinimap.texture:SetTexture(texture) iconMinimap.texture:SetVertexColor(colorsMinimap[1], colorsMinimap[2], colorsMinimap[3], 1); iconMinimap.miniMapIcon = true; -- add the minimap icon QuestieMap:QueueDraw(QuestieMap.ICON_MINIMAP_TYPE, Questie, iconMinimap, iconMinimap.UiMapID, x / 100, y / 100, true, true); tinsert(QuestieMap.manualFrames[typ][data.id], iconMinimap:GetName()) -- make sure notes are only shown when they are supposed to if (not Questie.db.profile.enabled) then -- TODO: or (not Questie.db.profile.manualNotes) icon:FakeHide() iconMinimap:FakeHide() else if (not Questie.db.profile.enableMapIcons) then icon:FakeHide() end if (not Questie.db.profile.enableMiniMapIcons) then iconMinimap:FakeHide() end end QuestieMap.utils:RescaleIcon(icon) -- return the frames in case they need to be stored seperately from QuestieMap.manualFrames return icon, iconMinimap; end --A layer to keep the area convertion away from the other parts of the code --coordinates need to be 0-1 instead of 0-100 --showFlag isn't required but may want to be Modified ---@return IconFrame, IconFrame function QuestieMap:DrawWorldIcon(data, areaID, x, y, showFlag) if type(data) ~= "table" then error("Questie" .. ": AddWorldMapIconMap: must have some data") end --if type(areaID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then -- error("Questie"..": AddWorldMapIconMap: 'AreaID', 'x' and 'y' must be numbers "..areaID.." "..x.." "..y.." "..tostring(showFlag)) --end --if type(data.Id) ~= "number" or type(data.Id) ~= "number"then -- error("Questie".."Data.Id must be set to the quests ID!") --end local uiMapId = ZoneDB:GetUiMapIdByAreaId(areaID) if (not uiMapId) then local parentMapId local mapInfo = C_Map.GetMapInfo(areaID) if mapInfo then parentMapId = mapInfo.parentMapID else parentMapId = ZoneDB:GetParentZoneId(areaID) end if (not parentMapId) then error("No UiMapID or fitting parentAreaId for areaId : " .. areaID .. " - " .. tostring(data.Name)) return nil, nil else areaID = parentMapId uiMapId = ZoneDB:GetUiMapIdByAreaId(areaID) end end if (not showFlag) then showFlag = HBD_PINS_WORLDMAP_SHOW_WORLD end --print("UIMAPID: " .. tostring(uiMapId)) if not uiMapId then --ZoneDB:GetUiMapIdByAreaId error("No UiMapID or fitting uiMapId for areaId : " .. areaID .. " - " .. tostring(data.Name)) return nil, nil end local floatOnEdge = true ---@type IconFrame local iconMap = QuestieFramePool:GetFrame() iconMap.data = data iconMap.x = x iconMap.y = y iconMap.AreaID = areaID iconMap.UiMapID = uiMapId iconMap.miniMapIcon = false; iconMap:UpdateTexture(Questie.usedIcons[data.Icon]); ---@type IconFrame local iconMinimap = QuestieFramePool:GetFrame() iconMinimap.data = data iconMinimap.x = x iconMinimap.y = y iconMinimap.AreaID = areaID iconMinimap.UiMapID = uiMapId --data.refMiniMap = iconMinimap -- used for removing --Are we a minimap note? iconMinimap.miniMapIcon = true; iconMinimap:UpdateTexture(Questie.usedIcons[data.Icon]); if (not iconMinimap.FadeLogic) then function iconMinimap:SetFade(value) if self.lastGlowFade ~= value then self.lastGlowFade = value if self.glowTexture then local r, g, b = self.glowTexture:GetVertexColor() self.glowTexture:SetVertexColor(r, g, b, value) end self.texture:SetVertexColor(self.texture.r, self.texture.g, self.texture.b, value) end end function iconMinimap:FadeLogic() local profile = Questie.db.profile if self.miniMapIcon and self.x and self.y and self.texture and self.UiMapID and self.texture.SetVertexColor and HBD and HBD.GetPlayerZonePosition and QuestieLib and QuestieLib.Euclid then if (QuestieMap.playerX and QuestieMap.playerY) then local x, y if not self.worldX then x, y = HBD:GetWorldCoordinatesFromZone(self.x / 100, self.y / 100, self.UiMapID) self.worldX = x self.worldY = y else x = self.worldX y = self.worldY end if (x and y) then --Very small value before, hard to work with. local distance = QuestieLib:Euclid(QuestieMap.playerX, QuestieMap.playerY, x, y) / 10; if (distance > profile.fadeLevel) then local fade = 1 - (math.min(10, (distance - profile.fadeLevel)) * normalizedValue); self:SetFade(fade) elseif (distance < profile.fadeOverPlayerDistance) and profile.fadeOverPlayer then local fadeAmount = profile.fadeOverPlayerLevel + distance * (1 - profile.fadeOverPlayerLevel) / profile.fadeOverPlayerDistance -- local fadeAmount = math.max(fadeAmount, 0.5); if self.faded and fadeAmount > profile.iconFadeLevel then fadeAmount = profile.iconFadeLevel end self:SetFade(fadeAmount) else if self.faded then self:SetFade(profile.iconFadeLevel) else self:SetFade(1) end end end else if self.faded then self:SetFade(profile.iconFadeLevel) else self:SetFade(1) end end end end -- We do not want to hook the OnUpdate again! -- iconMinimap:SetScript("OnUpdate", ) end QuestieMap:QueueDraw(QuestieMap.ICON_MINIMAP_TYPE, Questie, iconMinimap, uiMapId, x / 100, y / 100, true, floatOnEdge) QuestieMap:QueueDraw(QuestieMap.ICON_MAP_TYPE, Questie, iconMap, uiMapId, x / 100, y / 100, showFlag) local r, g, b = iconMinimap.texture:GetVertexColor() if QuestieDBMIntegration.RegisterHudQuestIcon then QuestieDBMIntegration:RegisterHudQuestIcon(tostring(iconMap), data.Icon, uiMapId, x, y, r, g, b) end if not QuestieMap.questIdFrames[data.Id] then QuestieMap.questIdFrames[data.Id] = {} end -- tinsert(QuestieMap.questIdFrames[data.Id], iconMap:GetName()) -- tinsert(QuestieMap.questIdFrames[data.Id], iconMinimap:GetName()) QuestieMap.questIdFrames[data.Id][iconMap:GetName()] = iconMap:GetName() QuestieMap.questIdFrames[data.Id][iconMinimap:GetName()] = iconMinimap:GetName() if iconMap:ShouldBeHidden() then iconMap:FakeHide() end if iconMinimap:ShouldBeHidden() then iconMinimap:FakeHide() end return iconMap, iconMinimap; end --- The return type also contains, distance, zone and type but we never really use it. ---@type table local closestStarter = {} function QuestieMap:FindClosestStarter() local playerX, playerY, _ = HBD:GetPlayerWorldPosition(); local playerZone = HBD:GetPlayerWorldPosition(); for questId in next, QuestiePlayer.currentQuestlog do if (not closestStarter[questId]) then local quest = QuestieDB.GetQuest(questId); if quest then closestStarter[questId] = { distance = 999999, x = -1, y = -1, zone = -1, type = "", } for starterType, starters in next, quest.Starts do if (starterType == "GameObject") then local stIndex = 1 while starters and starters[stIndex] do local ObjectID = starters[stIndex] local obj = QuestieDB:GetObject(ObjectID) if (obj ~= nil and obj.spawns ~= nil) then for Zone, Spawns in next, obj.spawns do if (Zone ~= nil and Spawns ~= nil) then local spIndex = 1 while Spawns[spIndex] do local coords = Spawns[spIndex] if (coords[1] == -1 or coords[2] == -1) then -- instace locations local dungeonLocation = ZoneDB:GetDungeonLocation(Zone) if dungeonLocation ~= nil then local dlIndex = 1 while dungeonLocation[dlIndex] do local value = dungeonLocation[dlIndex] if (value[1] and value[2]) then local x, y, _ = HBD:GetWorldCoordinatesFromZone(value[1] / 100, value[2] / 100, ZoneDB:GetUiMapIdByAreaId(value[3])) if (x and y) then local distance = QuestieLib:Euclid(playerX or 0, playerY or 0, x, y); if (closestStarter[questId].distance > distance) then closestStarter[questId].distance = distance; closestStarter[questId].x = x; closestStarter[questId].y = y; closestStarter[questId].zone = ZoneDB:GetUiMapIdByAreaId(Zone); closestStarter[questId].type = "GameObject - " .. obj.name; end end end dlIndex = dlIndex + 1 end end else local uiMapId = ZoneDB:GetUiMapIdByAreaId(Zone) local x, y, _ = HBD:GetWorldCoordinatesFromZone(coords[1] / 100, coords[2] / 100, uiMapId) if (x and y) then local distance = QuestieLib:Euclid(playerX or 0, playerY or 0, x, y); if (closestStarter[questId].distance > distance) then closestStarter[questId].distance = distance; closestStarter[questId].x = x; closestStarter[questId].y = y; closestStarter[questId].zone = uiMapId closestStarter[questId].type = "GameObject - " .. obj.name; end end end spIndex = spIndex + 1 end end end end stIndex = stIndex + 1 end elseif (starterType == "NPC") then local stIndex = 1 while starters and starters[stIndex] do local NPCID = starters[stIndex] local NPC = QuestieDB:GetNPC(NPCID) if (NPC ~= nil and NPC.spawns ~= nil and NPC.friendly) then for Zone, Spawns in next, NPC.spawns do if (Zone ~= nil and Spawns ~= nil) then local spIndex = 1 while Spawns[spIndex] do local coords = Spawns[spIndex] if (coords[1] == -1 or coords[2] == -1) then local dungeonLocation = ZoneDB:GetDungeonLocation(Zone) if dungeonLocation ~= nil then local dlIndex = 1 while dungeonLocation[dlIndex] do local value = dungeonLocation[dlIndex] if (value[1] and value[2]) then local uiMapId = ZoneDB:GetUiMapIdByAreaId(value[3]) local x, y, _ = HBD:GetWorldCoordinatesFromZone(value[1] / 100, value[2] / 100, uiMapId) if (x and y) then local distance = QuestieLib:Euclid(playerX or 0, playerY or 0, x, y); if (closestStarter[questId].distance > distance) then closestStarter[questId].distance = distance; closestStarter[questId].x = x; closestStarter[questId].y = y; closestStarter[questId].zone = ZoneDB:GetUiMapIdByAreaId(Zone); closestStarter[questId].type = "NPC - " .. NPC.name; end end end dlIndex = dlIndex + 1 end end elseif (coords[1] and coords[2]) then local uiMapId = ZoneDB:GetUiMapIdByAreaId(Zone) local x, y, _ = HBD:GetWorldCoordinatesFromZone(coords[1] / 100, coords[2] / 100, uiMapId) if (x and y) then local distance = QuestieLib:Euclid(playerX or 0, playerY or 0, x, y); if (closestStarter[questId].distance > distance) then closestStarter[questId].distance = distance; closestStarter[questId].x = x; closestStarter[questId].y = y; closestStarter[questId].zone = ZoneDB:GetUiMapIdByAreaId(Zone); closestStarter[questId].type = "NPC - " .. NPC.name; end end end spIndex = spIndex + 1 end end end end stIndex = stIndex + 1 end end end if (closestStarter[questId].x == -1) then closestStarter[questId].distance = 0; closestStarter[questId].x = playerX; closestStarter[questId].y = playerY; closestStarter[questId].zone = playerZone; closestStarter[questId].type = "player"; end end end end return closestStarter; end function QuestieMap:GetNearestSpawn(objective) if not objective then return nil end local playerX, playerY, playerI = HBD:GetPlayerWorldPosition() local bestDistance = 999999999 local bestSpawn, bestSpawnZone, bestSpawnId, bestSpawnType, bestSpawnName -- TODO: This is just a temporary workaround - We have to find out why "objective.spawnList" can be nil if objective and objective.spawnList and next(objective.spawnList) then for id, spawnData in next, objective.spawnList do for zone, spawns in next, spawnData.Spawns do for _, spawn in next, spawns do local uiMapId = ZoneDB:GetUiMapIdByAreaId(zone) local dX, dY, dInstance = HBD:GetWorldCoordinatesFromZone(spawn[1] / 100.0, spawn[2] / 100.0, uiMapId) local dist = HBD:GetWorldDistance(dInstance, playerX, playerY, dX, dY) if dist then if dInstance ~= playerI then dist = 500000 + dist * 100 -- hack end if dist < bestDistance then bestDistance = dist bestSpawn = spawn bestSpawnZone = zone bestSpawnId = id bestSpawnType = spawnData.Type bestSpawnName = spawnData.Name end end end end end end return bestSpawn, bestSpawnZone, bestSpawnName, bestSpawnId, bestSpawnType, bestDistance end ---@param quest Quest function QuestieMap:GetNearestQuestSpawn(quest) if not quest then return nil end if quest:IsComplete() == 1 then local finisherSpawns local finisherName if quest.Finisher ~= nil then if quest.Finisher.Type == "monster" then --finisher = QuestieDB:GetNPC(quest.Finisher.Id) finisherSpawns, finisherName = QuestieDB.QueryNPCSingle(quest.Finisher.Id, "spawns"), QuestieDB.QueryNPCSingle(quest.Finisher.Id, "name") elseif quest.Finisher.Type == "object" then --finisher = QuestieDB:GetObject(quest.Finisher.Id) finisherSpawns, finisherName = QuestieDB.QueryObjectSingle(quest.Finisher.Id, "spawns"), QuestieDB.QueryObjectSingle(quest.Finisher.Id, "name") end end if finisherSpawns then -- redundant code local bestDistance = 999999999 local playerX, playerY, playerI = HBD:GetPlayerWorldPosition() local bestSpawn, bestSpawnZone, bestSpawnType, bestSpawnName for zone, spawns in next, finisherSpawns do for _, spawn in next, spawns do local uiMapId = ZoneDB:GetUiMapIdByAreaId(zone) local dX, dY, dInstance = HBD:GetWorldCoordinatesFromZone(spawn[1] / 100.0, spawn[2] / 100.0, uiMapId) local dist = HBD:GetWorldDistance(dInstance, playerX, playerY, dX, dY) if dist then if dInstance ~= playerI then dist = 500000 + dist * 100 -- hack end if dist < bestDistance then bestDistance = dist bestSpawn = spawn bestSpawnZone = zone bestSpawnType = quest.Finisher.Type bestSpawnName = finisherName end end end end return bestSpawn, bestSpawnZone, bestSpawnName, bestSpawnType, bestDistance end return nil end local bestDistance = 999999999 local bestSpawn, bestSpawnZone, bestSpawnId, bestSpawnType, bestSpawnName for _, objective in next, quest.Objectives do local spawn, zone, Name, id, Type, dist = QuestieMap:GetNearestSpawn(objective) if spawn and dist < bestDistance and ((not objective.Needed) or objective.Needed ~= objective.Collected) then bestDistance = dist bestSpawn = spawn bestSpawnZone = zone bestSpawnId = id bestSpawnType = Type bestSpawnName = Name end end for _, objective in next, quest.SpecialObjectives do local spawn, zone, Name, id, Type, dist = QuestieMap:GetNearestSpawn(objective) if spawn and dist < bestDistance and ((not objective.Needed) or objective.Needed ~= objective.Collected) then bestDistance = dist bestSpawn = spawn bestSpawnZone = zone bestSpawnId = id bestSpawnType = Type bestSpawnName = Name end end return bestSpawn, bestSpawnZone, bestSpawnName, bestSpawnId, bestSpawnType, bestDistance end QuestieMap.zoneWaypointColorOverrides = { -- this is used when the default orange color doesn't work well in specific zones. Not needed after 769ea832ff772b10c57351eb199348393625a99b -- [14] = {0,0.1,0.9,0.7}, -- durotar -- [38] = {0,0.1,0.9,0.7} -- loch modan } QuestieMap.zoneWaypointHoverColorOverrides = { -- [14] = {0,0.6,1,1}, -- durotar -- [38] = {0,0.6,1,1} -- loch modan } function QuestieMap:DrawWaypoints(icon, waypoints, zone, color) if waypoints and waypoints[1] and waypoints[1][1] and waypoints[1][1][1] then -- check that waypoint data actually exists local lineFrames = QuestieFramePool:CreateWaypoints(icon, waypoints, nil, color or QuestieMap.zoneWaypointColorOverrides[zone], zone) local lIndex = 1 while lineFrames[lIndex] do local lineFrame = lineFrames[lIndex] QuestieMap:DrawLineIcon(lineFrame, zone, waypoints[1][1][1], waypoints[1][1][2]) lIndex = lIndex + 1 end end end