---@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 pairs = pairs; local ipairs = ipairs; 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 pairs(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 for _, frame in pairs(QuestieMap:GetFramesForQuest(questId)) do frame:Unload(); end QuestieMap.questIdFrames[questId] = nil; else for name, frame in pairs(QuestieMap:GetFramesForQuest(questId)) 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 pairs(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 for _, frame in ipairs(QuestieMap:GetManualFrames(id, typ)) do frame:Unload(); end QuestieMap.manualFrames[typ][id] = nil; end end function QuestieMap:ResetManualFrames(typ) typ = typ or "any" for id, _ in pairs(QuestieMap.manualFrames[typ]) do QuestieMap:UnloadManualFrames(id, typ) end end -- Rescale all the icons function QuestieMap:RescaleIcons() local mapScale = QuestieMap.GetScaleValue() for _, framelist in pairs(QuestieMap.questIdFrames) do for _, frameName in pairs(framelist) do QuestieMap.utils:RescaleIcon(frameName, mapScale) end end for _, frameTypeList in pairs(QuestieMap.manualFrames) do for _, framelist in pairs(frameTypeList) do for _, frameName in ipairs(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 pairs(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(#mapDrawQueue, #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 pairs(npc.spawns) do if (zone ~= nil and spawns ~= nil) and ((not excludeDungeon) or (not ZoneDB.IsDungeonZone(zone))) then for _, coords in ipairs(spawns) do -- instance spawn, draw entrance on map local dungeonLocation = ZoneDB:GetDungeonLocation(zone) if dungeonLocation ~= nil then for _, value in ipairs(dungeonLocation) do QuestieMap:DrawManualIcon(data, value[1], value[2], value[3], typ) end -- world spawn else manualIcons[zone] = QuestieMap:DrawManualIcon(data, zone, coords[1], coords[2], typ) end end end end -- draw waypoints if npc.waypoints then for zone, waypoints in pairs(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 pairs(object.spawns) do if (zone ~= nil and spawns ~= nil) then for _, coords in ipairs(spawns) do -- instance spawn, draw entrance on map local dungeonLocation = ZoneDB:GetDungeonLocation(zone) if dungeonLocation ~= nil then for _, value in ipairs(dungeonLocation) do QuestieMap:DrawManualIcon(data, value[1], value[2], value[3], typ) end -- world spawn else QuestieMap:DrawManualIcon(data, zone, coords[1], coords[2], typ) end 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() QuestieDBMIntegration:RegisterHudQuestIcon(tostring(iconMap), data.Icon, uiMapId, x, y, r, g, b) 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 pairs(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 pairs(quest.Starts) do if (starterType == "GameObject") then for _, ObjectID in ipairs(starters or {}) do local obj = QuestieDB:GetObject(ObjectID) if (obj ~= nil and obj.spawns ~= nil) then for Zone, Spawns in pairs(obj.spawns) do if (Zone ~= nil and Spawns ~= nil) then for _, coords in ipairs(Spawns) do if (coords[1] == -1 or coords[2] == -1) then -- instace locations local dungeonLocation = ZoneDB:GetDungeonLocation(Zone) if dungeonLocation ~= nil then for _, value in ipairs(dungeonLocation) do 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 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 end end end end end elseif (starterType == "NPC") then for _, NPCID in ipairs(starters or {}) do local NPC = QuestieDB:GetNPC(NPCID) if (NPC ~= nil and NPC.spawns ~= nil and NPC.friendly) then for Zone, Spawns in pairs(NPC.spawns) do if (Zone ~= nil and Spawns ~= nil) then for _, coords in ipairs(Spawns) do if (coords[1] == -1 or coords[2] == -1) then local dungeonLocation = ZoneDB:GetDungeonLocation(Zone) if dungeonLocation ~= nil then for _, value in ipairs(dungeonLocation) do 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 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 end end end end 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 pairs(objective.spawnList) do for zone, spawns in pairs(spawnData.Spawns) do for _, spawn in pairs(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 pairs(finisherSpawns) do for _, spawn in pairs(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 pairs(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 pairs(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) for _, lineFrame in ipairs(lineFrames) do QuestieMap:DrawLineIcon(lineFrame, zone, waypoints[1][1][1], waypoints[1][1][2]) end end end