1c0a891efd
The cutoff hid icons beyond its yard value before checking the minimap's visible radius, so the default 100 hid icons that were clearly on the minimap (view radius is 133-466 yd by zoom). Now it only clips icons outside the visible circle: HBD's pin renderer gates the cutoff on dist > 1, and QuestieMap FadeLogic raises the effective cutoff to at least the current minimap view radius via new HBDPins:GetMinimapRadius().
1155 lines
50 KiB
Lua
1155 lines
50 KiB
Lua
---@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";
|
|
|
|
-- List of frames sorted by quest ID (automatic notes)
|
|
-- E.g. {[questId] = {[frameName] = frame, ...}, ...}
|
|
QuestieMap.questIdFrames = {}
|
|
-- List of frames sorted by NPC/object ID (manual notes)
|
|
-- id > 0: NPC
|
|
-- id < 0: object
|
|
-- E.g. {[-objectId] = {[frameName] = frame, ...}, ...}
|
|
QuestieMap.manualFrames = {}
|
|
|
|
--Used in my fadelogic.
|
|
local fadeOverDistance = 10;
|
|
local normalizedValue = 1 / fadeOverDistance;
|
|
|
|
local HBD = QuestieCompat.HBD or LibStub("HereBeDragonsQuestie-2.0")
|
|
local HBDPins = QuestieCompat.HBDPins or LibStub("HereBeDragonsQuestie-Pins-2.0")
|
|
|
|
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 function _ResolveMapUiMapId(uiMapId, x, y)
|
|
-- Ghost map 946 has no real coordinate data; redirect to Eversong (1941).
|
|
if uiMapId == 946 then
|
|
return 1941
|
|
end
|
|
-- Map 1241 (Sunstrider Isle) must keep its native render target so the
|
|
-- actual Sunstrider map can show pins too. Visibility is handled separately
|
|
-- through ResolveZone()/isSameZoneSpace logic, which keeps 1241 and 1941
|
|
-- linked without forcing the render target to the parent map.
|
|
return uiMapId
|
|
end
|
|
|
|
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 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
|
|
|
|
-- Unload only the frames belonging to a single objective of a quest.
|
|
-- Frames carry data.ObjectiveIndex (set in _DetermineIconsToDraw), so we can remove a
|
|
-- completed objective's pins deterministically off the map's own frame registry instead
|
|
-- of relying on objective.AlreadySpawned, which desyncs from on-screen frames after
|
|
-- learner spawn-list invalidation or complete->abandon->reaccept cycles.
|
|
function QuestieMap:UnloadQuestFramesForObjective(questId, objectiveIndex)
|
|
-- objectiveIndex 0 is the SpecialObjectives sentinel (shared by all special objectives),
|
|
-- so unloading by it would cross-remove sibling specials mid-pass. Only act on real
|
|
-- positive standard-objective indices; specials keep using AlreadySpawned.
|
|
if (not objectiveIndex) or objectiveIndex <= 0 or (not QuestieMap.questIdFrames[questId]) then
|
|
return
|
|
end
|
|
for name, frame in pairs(QuestieMap:GetFramesForQuest(questId)) do
|
|
if frame and frame.data and frame.data.ObjectiveIndex == objectiveIndex then
|
|
frame:Unload()
|
|
QuestieMap.questIdFrames[questId][name] = nil
|
|
_G[name] = nil
|
|
end
|
|
end
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap] Unloading objective frames for questid:", questId, "objIdx:", objectiveIndex)
|
|
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
|
|
|
|
function QuestieMap:UnloadQuestFramesByDataType(questId, dataType)
|
|
if QuestieMap.questIdFrames[questId] then
|
|
for name, frame in pairs(QuestieMap:GetFramesForQuest(questId)) do
|
|
if frame and frame.data and frame.data.Type == dataType then
|
|
frame:Unload()
|
|
-- Also drop the registry/global reference. Without this the frame name
|
|
-- lingered in questIdFrames and _G, so the available '!' could stay on the
|
|
-- minimap after a quest was accepted until a full /reload rebuilt frames. (#9)
|
|
QuestieMap.questIdFrames[questId][name] = nil
|
|
_G[name] = nil
|
|
end
|
|
end
|
|
|
|
if not next(QuestieMap.questIdFrames[questId]) then
|
|
QuestieMap.questIdFrames[questId] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieMap:UnloadQuestFramesExceptDataType(questId, dataType)
|
|
if QuestieMap.questIdFrames[questId] then
|
|
for _, frame in pairs(QuestieMap:GetFramesForQuest(questId)) do
|
|
if frame and ((not frame.data) or frame.data.Type ~= dataType) then
|
|
frame:Unload()
|
|
end
|
|
end
|
|
|
|
if not next(QuestieMap.questIdFrames[questId]) then
|
|
QuestieMap.questIdFrames[questId] = nil
|
|
end
|
|
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 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"
|
|
if QuestieMap.manualFrames[typ] then
|
|
for id in pairs(QuestieMap.manualFrames[typ]) do
|
|
QuestieMap:UnloadManualFrames(id, typ)
|
|
end
|
|
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
|
|
|
|
--- Called at startup (Stage 3) and on PLAYER_ENTERING_WORLD to reset the draw queue.
|
|
function QuestieMap:InitializeQueue()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap] Starting draw queue timer!")
|
|
local isInInstance, instanceType = IsInInstance()
|
|
|
|
if (not isInInstance) or instanceType ~= "raid" then
|
|
isDrawQueueDisabled = false
|
|
if not drawTimer then
|
|
drawTimer = C_Timer.NewTicker(0.2, QuestieMap.ProcessQueue)
|
|
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
|
|
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()
|
|
if not HBDPins or not HBDPins.worldmapProvider then return 1 end
|
|
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 then
|
|
if (mapInfo.mapType == 0) then
|
|
scaling = 0.85
|
|
elseif (mapInfo.mapType == 1) then
|
|
scaling = 0.85
|
|
elseif (mapInfo.mapType == 2) then
|
|
scaling = 0.9
|
|
end
|
|
end
|
|
end
|
|
return scaling
|
|
end
|
|
|
|
function QuestieMap:ProcessShownMinimapIcons()
|
|
local getTime, cYield, getWorldPos = GetTime, coroutine.yield, HBD.GetPlayerWorldPosition
|
|
|
|
local maxCount = 50
|
|
local doEdgeUpdate = true
|
|
local playerX, playerY
|
|
local count
|
|
local lastUpdate = getTime()
|
|
local xd, yd
|
|
local totalDistance = 0
|
|
|
|
while true do
|
|
count = 0
|
|
playerX, playerY = getWorldPos()
|
|
|
|
xd = (playerX or 0) - (QuestieMap.playerX or 0)
|
|
yd = (playerY or 0) - (QuestieMap.playerY or 0)
|
|
totalDistance = totalDistance + (xd * xd + yd * yd)
|
|
QuestieMap.playerX = playerX
|
|
QuestieMap.playerY = playerY
|
|
|
|
if totalDistance > 3 or getTime() - lastUpdate >= 1 then
|
|
doEdgeUpdate = true
|
|
lastUpdate = getTime()
|
|
totalDistance = 0
|
|
end
|
|
|
|
if HBDPins and HBDPins.activeMinimapPins then
|
|
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
|
|
|
|
if count > maxCount then
|
|
cYield()
|
|
if not HBDPins.activeMinimapPins[minimapFrame] then
|
|
totalDistance = 9000
|
|
break
|
|
end
|
|
count = 0
|
|
else
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
cYield()
|
|
doEdgeUpdate = false
|
|
end
|
|
end
|
|
|
|
function QuestieMap:RefreshMinimapIconVisibility()
|
|
if not HBDPins or not HBDPins.activeMinimapPins then
|
|
return
|
|
end
|
|
|
|
local minimapVisibilityCutoff = Questie.db.profile.minimapIconRangeCutoff or 100
|
|
if HBDPins.SetMinimapVisibilityCutoff then
|
|
HBDPins:SetMinimapVisibilityCutoff(minimapVisibilityCutoff)
|
|
end
|
|
if HBDPins.RefreshMinimap then
|
|
HBDPins:RefreshMinimap()
|
|
end
|
|
|
|
for minimapFrame, data in pairs(HBDPins.activeMinimapPins) do
|
|
if minimapFrame and minimapFrame.miniMapIcon and minimapFrame.FadeLogic then
|
|
minimapFrame.minimapVisibilityCutoff = minimapVisibilityCutoff
|
|
minimapFrame:FadeLogic()
|
|
if minimapFrame.GlowUpdate then
|
|
minimapFrame:GlowUpdate()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieMap:QueueDraw(drawType, ...)
|
|
if (not isDrawQueueDisabled) then
|
|
if _G.QuestieDebugPins then
|
|
local args = { ... }
|
|
local frame = args[2]
|
|
local data = frame and frame.data
|
|
local label = data and (data.Name or data.name or data.Title)
|
|
local questId = data and (data.Id or data.id)
|
|
if label == "Mana Wyrm" or questId == 8325 then
|
|
print(string.format(
|
|
"[QD] QueueDraw type=%s frame=%s label=%s quest=%s mini=%s ui=%s area=%s",
|
|
tostring(drawType),
|
|
tostring(frame and frame:GetName()),
|
|
tostring(label),
|
|
tostring(questId),
|
|
tostring(frame and frame.miniMapIcon),
|
|
tostring(frame and frame.UiMapID),
|
|
tostring(frame and frame.AreaID)
|
|
))
|
|
end
|
|
end
|
|
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
|
|
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));
|
|
|
|
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));
|
|
if _G.QuestieDebugPins then
|
|
local data = frame and frame.data
|
|
local label = data and (data.Name or data.name or data.Title)
|
|
local questId = data and (data.Id or data.id)
|
|
if label == "Mana Wyrm" or questId == 8325 then
|
|
local point1, rel1, point2, xOff, yOff = frame:GetPoint()
|
|
print(string.format(
|
|
"[QD] ProcessQueue minimap frame=%s label=%s quest=%s shown=%s hidden=%s parent=%s point=(%s,%s,%s,%s,%s)",
|
|
tostring(frame:GetName()),
|
|
tostring(label),
|
|
tostring(questId),
|
|
tostring(frame:IsShown()),
|
|
tostring(frame.hidden),
|
|
tostring(frame:GetParent() and frame:GetParent():GetName() or frame:GetParent()),
|
|
tostring(point1),
|
|
tostring(rel1 and rel1.GetName and rel1:GetName() or rel1),
|
|
tostring(point2),
|
|
tostring(xOff),
|
|
tostring(yOff)
|
|
))
|
|
end
|
|
end
|
|
QuestieMap.utils:SetDrawOrder(frame);
|
|
end
|
|
|
|
if mapDrawCall then
|
|
mapDrawCall[2]._loaded = true
|
|
if mapDrawCall[2]._needsUnload then
|
|
mapDrawCall[2]:Unload()
|
|
end
|
|
end
|
|
|
|
if minimapDrawCall then
|
|
minimapDrawCall[2]._loaded = true
|
|
if minimapDrawCall[2]._needsUnload then
|
|
minimapDrawCall[2]:Unload()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Show NPC on map
|
|
---@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 <number>")
|
|
return
|
|
end
|
|
local npc = QuestieDB:GetNPC(npcID)
|
|
if (not npc) or (not npc.spawns) then return end
|
|
|
|
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 and WeaponMasterSkills.AppendSkillsToTitle(baseTitle, data.id) or baseTitle
|
|
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 = {}
|
|
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
|
|
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
|
|
else
|
|
manualIcons[zone] = QuestieMap:DrawManualIcon(data, zone, coords[1], coords[2], typ)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
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
|
|
---@param objectID number
|
|
function QuestieMap:ShowObject(objectID, icon, scale, title, body, disableShiftToRemove, typ)
|
|
if type(objectID) ~= "number" then return end
|
|
local object = QuestieDB:GetObject(objectID)
|
|
if not object or not object.spawns then return end
|
|
|
|
local data = {}
|
|
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
|
|
|
|
for zone, spawns in pairs(object.spawns) do
|
|
if (zone ~= nil and spawns ~= nil) then
|
|
for _, coords in ipairs(spawns) do
|
|
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
|
|
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
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawLineIcon] 'AreaID', 'x' and 'y' must be numbers:", areaID, x, y)
|
|
return nil, nil
|
|
end
|
|
|
|
local uiMapId = ZoneDB:GetUiMapIdByAreaId(areaID)
|
|
if not uiMapId then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap:DrawLineIcon] No UiMapID for areaId:", areaID)
|
|
return nil, nil
|
|
end
|
|
|
|
HBDPins:AddWorldMapIconMap(Questie, lineFrame, uiMapId, x, y, HBD_PINS_WORLDMAP_SHOW_CURRENT)
|
|
end
|
|
|
|
-- Draw manually added NPC/object notes
|
|
function QuestieMap:DrawManualIcon(data, areaID, x, y, typ)
|
|
if type(data) ~= "table" then
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawManualIcon] must have some data")
|
|
return nil, nil
|
|
end
|
|
if type(areaID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawManualIcon] 'AreaID', 'x' and 'y' must be numbers:", areaID, x, y)
|
|
return nil, nil
|
|
end
|
|
if type(data.id) ~= "number" then
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawManualIcon] Data.id must be set to the NPC or object ID!")
|
|
return nil, nil
|
|
end
|
|
|
|
data.Id = data.id
|
|
|
|
-- Manual notes are placed by the Townsfolk / menu system. Tag them so the source
|
|
-- tooltip line can report it (only if a caller hasn't already set a more specific source).
|
|
if data.DataSource == nil then
|
|
data.DataSource = "Townsfolk"
|
|
end
|
|
|
|
local uiMapId = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(areaID), x, y)
|
|
if (not uiMapId) then
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawManualIcon] No UiMapID for areaId:", areaID, tostring(data.Name))
|
|
return nil, nil
|
|
end
|
|
|
|
local texture = data.Icon or "Interface\\WorldMap\\WorldMapPartyIcon"
|
|
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
|
|
|
|
local icon = QuestieFramePool:GetFrame()
|
|
icon.data = data
|
|
icon.x = x
|
|
icon.y = y
|
|
icon.AreaID = areaID
|
|
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))
|
|
|
|
QuestieMap:QueueDraw(QuestieMap.ICON_MAP_TYPE, Questie, icon, icon.UiMapID, x / 100, y / 100, 3)
|
|
tinsert(QuestieMap.manualFrames[typ][data.id], icon:GetName())
|
|
|
|
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
|
|
iconMinimap.UiMapID = uiMapId
|
|
iconMinimap.texture:SetTexture(texture)
|
|
iconMinimap.texture:SetVertexColor(colorsMinimap[1], colorsMinimap[2], colorsMinimap[3], 1);
|
|
iconMinimap.miniMapIcon = true;
|
|
iconMinimap.minimapVisibilityCutoff = Questie.db.profile.minimapIconRangeCutoff or 100
|
|
|
|
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(colorsMinimap[1], colorsMinimap[2], colorsMinimap[3], 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 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
|
|
local xd = QuestieMap.playerX - x
|
|
local yd = QuestieMap.playerY - y
|
|
local distance = math.sqrt(xd * xd + yd * yd)
|
|
local minimapVisibilityCutoff = self.minimapVisibilityCutoff or profile.minimapIconRangeCutoff or 100
|
|
|
|
-- Only let the range cutoff hide icons that fall OUTSIDE the minimap's
|
|
-- visible radius. Icons within the visible minimap circle must always show,
|
|
-- otherwise a cutoff smaller than the current view radius (133-466 yd by
|
|
-- zoom) hides quest icons that are clearly on the minimap (#17). The cutoff
|
|
-- then only limits how far edge-floating icons reach.
|
|
local minimapRadius = (HBDPins and HBDPins.GetMinimapRadius and HBDPins:GetMinimapRadius()) or 0
|
|
local effectiveCutoff = minimapVisibilityCutoff
|
|
if minimapRadius and minimapRadius > effectiveCutoff then
|
|
effectiveCutoff = minimapRadius
|
|
end
|
|
|
|
if (distance > effectiveCutoff) then
|
|
self:FakeHide()
|
|
return
|
|
elseif self.hidden then
|
|
-- Only re-show icons that aren't filtered out (quest-type options
|
|
-- like showDungeonQuests, enableMiniMapIcons, hideUntracked, etc.).
|
|
-- Previously FadeLogic re-showed ANY FakeHidden minimap icon once in
|
|
-- range, so the world-map quest-type filters never applied to the
|
|
-- minimap. ShouldBeHidden mirrors the world-map draw-time check.
|
|
if not self:ShouldBeHidden() then
|
|
self:FakeShow()
|
|
end
|
|
elseif (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
|
|
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
|
|
end
|
|
|
|
QuestieMap:QueueDraw(QuestieMap.ICON_MINIMAP_TYPE, Questie, iconMinimap, iconMinimap.UiMapID, x / 100, y / 100, true, true);
|
|
tinsert(QuestieMap.manualFrames[typ][data.id], iconMinimap:GetName())
|
|
|
|
if (not Questie.db.profile.enabled) then
|
|
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
|
|
|
|
if QuestieMap.utils and QuestieMap.utils.RescaleIcon then
|
|
QuestieMap.utils:RescaleIcon(icon)
|
|
end
|
|
|
|
return icon, iconMinimap;
|
|
end
|
|
|
|
--A layer to keep the area conversion away from the other parts of the code
|
|
--coordinates need to be 0-1 instead of 0-100
|
|
---@return IconFrame, IconFrame
|
|
function QuestieMap:DrawWorldIcon(data, areaID, x, y, showFlag)
|
|
if type(data) ~= "table" then
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieMap:DrawWorldIcon] must have some data")
|
|
return nil, nil
|
|
end
|
|
|
|
-- Tag data-source provenance for the source tooltip line. Objective pins set this
|
|
-- explicitly (per-spawn). Available/finisher pins are quest-scoped, so derive from the
|
|
-- quest. Leave nil for anything else so the tooltip shows no (potentially wrong) source.
|
|
if data.DataSource == nil and (data.Type == "available" or data.Type == "complete") then
|
|
data.DataSource = QuestieDB.GetPinDataSource("QUEST", data.Id)
|
|
end
|
|
|
|
local uiMapId = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(areaID), x, y)
|
|
if (not uiMapId) then
|
|
local parentMapId
|
|
local mapInfo = C_Map and C_Map.GetMapInfo and C_Map.GetMapInfo(areaID)
|
|
if mapInfo then
|
|
parentMapId = mapInfo.parentMapID
|
|
else
|
|
parentMapId = ZoneDB:GetParentZoneId(areaID)
|
|
end
|
|
|
|
if (not parentMapId) then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap:DrawWorldIcon] No UiMapID or fitting parentAreaId for areaId:", areaID, tostring(data.Name))
|
|
return nil, nil
|
|
else
|
|
areaID = parentMapId
|
|
uiMapId = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(areaID), x, y)
|
|
end
|
|
end
|
|
|
|
if (not showFlag) then
|
|
showFlag = HBD_PINS_WORLDMAP_SHOW_WORLD
|
|
end
|
|
|
|
if not uiMapId then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap:DrawWorldIcon] 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
|
|
iconMinimap.miniMapIcon = true;
|
|
iconMinimap.minimapVisibilityCutoff = Questie.db.profile.minimapIconRangeCutoff or 100
|
|
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 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
|
|
local xd = QuestieMap.playerX - x
|
|
local yd = QuestieMap.playerY - y
|
|
local distance = math.sqrt(xd * xd + yd * yd);
|
|
local minimapVisibilityCutoff = self.minimapVisibilityCutoff or profile.minimapIconRangeCutoff or 100;
|
|
|
|
-- Hard stop: keep minimap pins from remaining visible beyond
|
|
-- the configured minimap range cutoff.
|
|
if (distance > minimapVisibilityCutoff) then
|
|
self:FakeHide()
|
|
return
|
|
elseif self.hidden then
|
|
-- Only re-show icons that aren't filtered out (quest-type options
|
|
-- like showDungeonQuests, enableMiniMapIcons, hideUntracked, etc.).
|
|
-- Previously FadeLogic re-showed ANY FakeHidden minimap icon once in
|
|
-- range, so the world-map quest-type filters never applied to the
|
|
-- minimap. ShouldBeHidden mirrors the world-map draw-time check.
|
|
if not self:ShouldBeHidden() then
|
|
self:FakeShow()
|
|
end
|
|
elseif (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
|
|
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
|
|
|
|
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<QuestId, {x:X, y:Y}>
|
|
local closestStarter = {}
|
|
function QuestieMap:FindClosestStarter()
|
|
local playerX, playerY, _ = HBD:GetPlayerWorldPosition();
|
|
local playerZone = QuestiePlayer:GetCurrentUiMapId();
|
|
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
|
|
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, _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(value[3]), value[1], value[2]))
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(Zone), value[1], value[2])
|
|
closestStarter[questId].type = "GameObject - " .. obj.name;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
local uiMapId = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(Zone), coords[1], coords[2])
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(value[3]), value[1], value[2])
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(Zone), value[1], value[2])
|
|
closestStarter[questId].type = "NPC - " .. NPC.name;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif (coords[1] and coords[2]) then
|
|
local uiMapId = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(Zone), coords[1], coords[2])
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(Zone), coords[1], coords[2])
|
|
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
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(zone), spawn[1], spawn[2])
|
|
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
|
|
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
|
|
finisherSpawns, finisherName = QuestieDB.QueryNPCSingle(quest.Finisher.Id, "spawns"), QuestieDB.QueryNPCSingle(quest.Finisher.Id, "name")
|
|
elseif quest.Finisher.Type == "object" then
|
|
finisherSpawns, finisherName = QuestieDB.QueryObjectSingle(quest.Finisher.Id, "spawns"), QuestieDB.QueryObjectSingle(quest.Finisher.Id, "name")
|
|
end
|
|
end
|
|
if finisherSpawns then
|
|
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 = _ResolveMapUiMapId(ZoneDB:GetUiMapIdByAreaId(zone), spawn[1], spawn[2])
|
|
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
|
|
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 = {
|
|
-- [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
|
|
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
|
|
|
|
return QuestieMap
|