v9.7.2: Ebonhold Database integration and core logic refinements
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
---@class HBDHooks
|
||||
local HBDHooks = QuestieLoader:CreateModule("HBDHooks");
|
||||
local _HBDHooks = {}
|
||||
|
||||
---@type QuestieMap
|
||||
local QuestieMap = QuestieLoader:ImportModule("QuestieMap");
|
||||
|
||||
local HBDPins = LibStub("HereBeDragonsQuestie-Pins-2.0")
|
||||
|
||||
|
||||
function HBDHooks:Init()
|
||||
--Override OnMapChanged from MapCanvasDataProviderMixin
|
||||
-- (https://www.townlong-yak.com/framexml/27101/Blizzard_MapCanvas/MapCanvas_DataProviderBase.lua#74)
|
||||
--This could in theory be skipped by instead using our own MapCanvasDataProviderMixin
|
||||
--The reason i don't is because i want the scaling to happen AFTER HBD has processed all the icons.
|
||||
_HBDHooks.ORG_OnMapChanged = HBDPins.worldmapProvider.OnMapChanged;
|
||||
HBDPins.worldmapProvider.OnMapChanged = _HBDHooks.OnMapChanged
|
||||
end
|
||||
|
||||
function _HBDHooks:OnMapChanged()
|
||||
--Call original one : https://www.townlong-yak.com/framexml/27101/Blizzard_MapCanvas/MapCanvas_DataProviderBase.lua#74
|
||||
_HBDHooks.ORG_OnMapChanged(HBDPins.worldmapProvider)
|
||||
|
||||
local mapScale = QuestieMap.GetScaleValue()
|
||||
for pin in HBDPins.worldmapProvider:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplateQuestie") do
|
||||
QuestieMap.utils:RescaleIcon(pin.icon, mapScale)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,973 @@
|
||||
---@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 <number>")
|
||||
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<QuestId, {x:X, y:Y}>
|
||||
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
|
||||
@@ -0,0 +1,210 @@
|
||||
---@type QuestieMap
|
||||
local QuestieMap = QuestieLoader:ImportModule("QuestieMap");
|
||||
---@class QuestieMapUtils
|
||||
QuestieMap.utils = QuestieMap.utils or {}
|
||||
|
||||
---@type QuestieLib
|
||||
local QuestieLib = QuestieLoader:ImportModule("QuestieLib");
|
||||
|
||||
local HBD = QuestieCompat.HBD or LibStub("HereBeDragonsQuestie-2.0")
|
||||
|
||||
local ZOOM_MODIFIER = 1;
|
||||
|
||||
-- All the speed we can get is worth it.
|
||||
local tinsert = table.insert
|
||||
local pairs = pairs
|
||||
|
||||
function QuestieMap.utils:SetDrawOrder(frame)
|
||||
-- This is all fixes to always be on top of HandyNotes notes Let the frame level wars begin.
|
||||
-- HandyNotes uses GetFrameLevel + 6, so we use +7
|
||||
if frame.miniMapIcon then
|
||||
local frameLevel = Minimap:GetFrameLevel() + 7
|
||||
local frameStrata = Minimap:GetFrameStrata()
|
||||
frame:SetParent(Minimap)
|
||||
frame:SetFrameStrata(frameStrata)
|
||||
frame:SetFrameLevel(frameLevel)
|
||||
else
|
||||
-- If Magnify-WotLK is loaded, parent to WorldMapButton instead of WorldMapFrame
|
||||
local parent = WorldMapFrame
|
||||
if IsAddOnLoaded and IsAddOnLoaded("Magnify-WotLK") and _G.WorldMapButton then
|
||||
parent = WorldMapButton
|
||||
end
|
||||
local frameLevel = WorldMapFrame:GetFrameLevel() + 7
|
||||
local frameStrata = WorldMapFrame:GetFrameStrata()
|
||||
frame:SetParent(parent)
|
||||
frame:SetFrameStrata(frameStrata)
|
||||
frame:SetFrameLevel(frameLevel)
|
||||
end
|
||||
|
||||
-- Draw layer is between -8 and 7, please leave some number above so we don't paint ourselves into a corner...
|
||||
-- These are sorted by order of most common occurrence to reduce if checks; it's less readable but more performant with so many icons
|
||||
if frame.data then
|
||||
if frame.data.Icon == Questie.ICON_TYPE_AVAILABLE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 5)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_REPEATABLE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 4)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_EVENTQUEST then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 4)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_PVPQUEST then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 4)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_COMPLETE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 6)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_REPEATABLE_COMPLETE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 6)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_EVENTQUEST_COMPLETE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 6)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_PVPQUEST_COMPLETE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 6)
|
||||
elseif frame.data.Icon == Questie.ICON_TYPE_SODRUNE then
|
||||
frame.texture:SetDrawLayer("OVERLAY", 6)
|
||||
else
|
||||
frame.texture:SetDrawLayer("OVERLAY", 0)
|
||||
end
|
||||
else
|
||||
frame.texture:SetDrawLayer("OVERLAY", 0)
|
||||
end
|
||||
end
|
||||
|
||||
---@param points table<number, Point> @{{x=0, y=0}, ...}
|
||||
---@return number x, number y @Center coordinates
|
||||
function QuestieMap.utils.CenterPoint(points)
|
||||
local x, y = 0, 0
|
||||
local count = #points
|
||||
for i=1, count do
|
||||
local point = points[i]
|
||||
x = x + point.x
|
||||
y = y + point.y
|
||||
end
|
||||
x = x / count
|
||||
y = y / count
|
||||
return x, y
|
||||
end
|
||||
|
||||
---@param points table<number, Point> @A pointlist with {worldX=0, worldY=0, UiMapID=0, distance=0}
|
||||
---@param rangeR number @Range of the hotzones.
|
||||
---@param count number @Optional, used to allow more notes if far away from the quest giver.
|
||||
---@return table<number, table<number, Point>> @A table of hotzones
|
||||
function QuestieMap.utils:CalcHotzones(points, rangeR, count)
|
||||
-- if(points == nil) then return nil; end
|
||||
|
||||
local hotzones = {}
|
||||
local pointsCount = #points
|
||||
|
||||
if pointsCount == 1 then
|
||||
-- This is execution shortcut to skip loop in case table size == 1
|
||||
|
||||
hotzones = { { points[1] } }
|
||||
return hotzones
|
||||
end
|
||||
|
||||
--If count isn't set we want to distance clustering to still work,
|
||||
--to simplify the logic we just use a big number.
|
||||
if not count then
|
||||
count = 99999;
|
||||
end
|
||||
|
||||
local useMovingRange = (count > 100)
|
||||
|
||||
local range = rangeR or 100;
|
||||
|
||||
for j=1, pointsCount do
|
||||
local point = points[j]
|
||||
if not point.touched then
|
||||
point.touched = true
|
||||
local notes = { point }
|
||||
|
||||
--We want things further away to be clustered more
|
||||
local movingRange = range
|
||||
if useMovingRange and (point.distance > 1000) then
|
||||
movingRange = movingRange * (point.distance/1000);
|
||||
end
|
||||
|
||||
local aX, aY, aUiMapID = point.worldX, point.worldY, point.UiMapID
|
||||
|
||||
for i=j+1, pointsCount do
|
||||
local point2 = points[i]
|
||||
--We only want to cluster icons that are on the same map.
|
||||
if (not point2.touched) and (aUiMapID == point2.UiMapID)
|
||||
-- Do not cluster icons if they have no coordinates
|
||||
and aX ~= 0 and aY ~= 0 and point2.worldX ~= 0 and point2.worldY ~= 0 then
|
||||
local distance = QuestieLib:Euclid(aX, aY, point2.worldX, point2.worldY)
|
||||
if (distance < movingRange) then
|
||||
point2.touched = true
|
||||
notes[#notes+1] = point2
|
||||
end
|
||||
end
|
||||
end
|
||||
hotzones[#hotzones+1] = notes
|
||||
end
|
||||
end
|
||||
return hotzones
|
||||
end
|
||||
|
||||
function QuestieMap.utils:IsExplored(uiMapId, x, y)
|
||||
local IsExplored = false
|
||||
if uiMapId then
|
||||
local exploredAreaIDs =
|
||||
C_MapExplorationInfo.GetExploredAreaIDsAtPosition(uiMapId,
|
||||
CreateVector2D(
|
||||
x / 100,
|
||||
y / 100))
|
||||
if exploredAreaIDs then
|
||||
IsExplored = true -- Explored
|
||||
elseif (uiMapId == 1453) then
|
||||
IsExplored = true -- Stormwind
|
||||
elseif (uiMapId == 1455) then
|
||||
IsExplored = true -- Ironforge
|
||||
elseif (uiMapId == 1457) then
|
||||
IsExplored = true -- Darnassus
|
||||
elseif (uiMapId == 1458) then
|
||||
IsExplored = true -- Undercity
|
||||
elseif (uiMapId == 1454) then
|
||||
IsExplored = true -- Orgrimmar
|
||||
elseif (uiMapId == 1456) then
|
||||
IsExplored = true -- Thunder Bluff
|
||||
end
|
||||
end
|
||||
return IsExplored
|
||||
end
|
||||
|
||||
function QuestieMap.utils:MapExplorationUpdate()
|
||||
for _, frameList in pairs(QuestieMap.questIdFrames) do
|
||||
for _, frameName in pairs(frameList) do
|
||||
local frame = _G[frameName]
|
||||
if (frame and frame.x and frame.y and frame.UiMapID and frame.hidden) then
|
||||
if (QuestieMap.utils:IsExplored(frame.UiMapID, frame.x, frame.y)) then
|
||||
frame:FakeShow()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Rescale a single icon
|
||||
---@param frameRef string|IconFrame @The global name/iconRef of the icon frame, e.g. "QuestieFrame1"
|
||||
---@param mapScale number? @Scale value for the final size of the Icon
|
||||
function QuestieMap.utils:RescaleIcon(frameRef, mapScale)
|
||||
local frame = frameRef;
|
||||
local iconScale = mapScale or 1
|
||||
if type(frameRef) == "string" then
|
||||
frame = _G[frameRef];
|
||||
end
|
||||
if frame and frame.data then
|
||||
if frame.data.GetIconScale then
|
||||
frame.data.IconScale = frame.data:GetIconScale();
|
||||
local scale
|
||||
if frame.miniMapIcon then
|
||||
scale = 16 * (frame.data.IconScale or 1) * (Questie.db.profile.globalMiniMapScale or 0.7);
|
||||
else
|
||||
--? If you ever chanage this logic, make sure you change the logic in QuestieMap:ProcessQueue() too!
|
||||
scale = (16 * (frame.data.IconScale or 1) * (Questie.db.profile.globalScale or 0.7)) * iconScale;
|
||||
end
|
||||
|
||||
if scale > 1 then
|
||||
frame:SetSize(scale * ZOOM_MODIFIER, scale * ZOOM_MODIFIER);
|
||||
end
|
||||
else
|
||||
Questie:Error("A frame is lacking the GetIconScale function for resizing!", frame.data.Id);
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
---@class WeaponMasterSkills
|
||||
local WeaponMasterSkills = QuestieLoader:CreateModule("WeaponMasterSkills")
|
||||
|
||||
---@type l10n
|
||||
local l10n = QuestieLoader:ImportModule("l10n")
|
||||
|
||||
WeaponMasterSkills.data = {
|
||||
-- Alliance Weapon Trainers
|
||||
[11865] = {"Fist Weapons", "Guns", "One-Handed Axes", "One-Handed Maces", "Two-Handed Axes", "Two-Handed Maces"},
|
||||
[11866] = {"Bows", "Daggers", "Fist Weapons", "Staves", "Thrown"},
|
||||
[11867] = {"Crossbows", "Daggers", "One-Handed Swords", "Polearms", "Staves", "Two-Handed Swords"},
|
||||
[13084] = {"Crossbows", "Daggers", "Thrown"},
|
||||
-- Horde Weapon Trainers
|
||||
[2704] = {"Bows", "One-Handed Axes", "Staves", "Thrown", "Two-Handed Axes"},
|
||||
[11868] = {"Bows", "Daggers", "Fist Weapons", "One-Handed Axes", "Thrown", "Two-Handed Axes"},
|
||||
[11869] = {"Guns", "One-Handed Maces", "Staves", "Two-Handed Maces"},
|
||||
[11870] = {"Crossbows", "Daggers", "One-Handed Swords", "Polearms", "Two-Handed Swords"},
|
||||
}
|
||||
|
||||
if Questie.IsTBC or Questie.IsWotlk then
|
||||
-- Blood Elf Starting Area Weapon Trainers
|
||||
WeaponMasterSkills.data[16621] = {"Bows", "Daggers", "One-Handed Swords", "Polearms", "Thrown", "Two-Handed Swords"}
|
||||
WeaponMasterSkills.data[17005] = {"Bows", "Daggers", "One-Handed Swords", "Polearms", "Thrown", "Two-Handed Swords"}
|
||||
-- Draenei Starting Area Weapon Trainers
|
||||
WeaponMasterSkills.data[16773] = {"Crossbows", "Daggers", "One-Handed Maces", "One-Handed Swords", "Two-Handed Maces", "Two-Handed Swords"}
|
||||
end
|
||||
|
||||
function WeaponMasterSkills.AppendSkillsToTitle(title, npcId)
|
||||
local skills = WeaponMasterSkills.data[npcId]
|
||||
if (not skills) then
|
||||
return title
|
||||
end
|
||||
|
||||
for _, skill in ipairs(skills) do
|
||||
title = title .. "\n - " .. l10n(skill)
|
||||
end
|
||||
return title
|
||||
end
|
||||
Reference in New Issue
Block a user