fix: rewrite QuestRouteOptimizer module

This commit is contained in:
Xurkon
2026-03-19 21:48:02 -05:00
parent 7857340c37
commit 156a184da4
2 changed files with 160 additions and 189 deletions
+28
View File
@@ -21,6 +21,8 @@ local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
local l10n = QuestieLoader:ImportModule("l10n")
---@type QuestieCombatQueue
local QuestieCombatQueue = QuestieLoader:ImportModule("QuestieCombatQueue")
---@type QuestieRouteOptimizer
local QuestieRouteOptimizer = QuestieLoader:ImportModule("QuestieRouteOptimizer")
function QuestieSlash.RegisterSlashCommands()
@@ -64,6 +66,7 @@ function QuestieSlash.HandleCommands(input)
print(Questie:Colorize("/questie doable [questID] - " .. l10n("Prints whether you are eligibile to do a quest"), "yellow"));
print(Questie:Colorize("/questie version - " .. l10n("Prints Questie and client version info"), "yellow"));
print(Questie:Colorize("/questie learn [toggle/stats/clear/export] - " .. l10n("Self-learning database: toggle on/off, view stats, clear data, or export"), "yellow"));
print(Questie:Colorize("/questie route [off/single/all/tsp] - " .. l10n("Toggle quest route optimization. off=disable, single=single quest, all=all tracked, tsp=TSP approximation"), "yellow"));
return;
end
@@ -218,5 +221,30 @@ function QuestieSlash.HandleCommands(input)
return
end
-- /questie route [off/single/all/tsp]
if mainCommand == "route" then
local ROUTE_MODE_OFF = 1
local ROUTE_MODE_SINGLE_QUEST = 2
local ROUTE_MODE_ALL_TRACKED = 3
local ROUTE_MODE_TSP_APPROXIMATION = 4
if subCommand == "off" or not subCommand then
QuestieRouteOptimizer:SetMode(ROUTE_MODE_OFF)
Questie:Print("Quest route " .. l10n("disabled"))
elseif subCommand == "single" then
QuestieRouteOptimizer:SetMode(ROUTE_MODE_SINGLE_QUEST)
Questie:Print("Quest route " .. l10n("set to single quest mode"))
elseif subCommand == "all" then
QuestieRouteOptimizer:SetMode(ROUTE_MODE_ALL_TRACKED)
Questie:Print("Quest route " .. l10n("set to all tracked quests mode"))
elseif subCommand == "tsp" then
QuestieRouteOptimizer:SetMode(ROUTE_MODE_TSP_APPROXIMATION)
Questie:Print("Quest route " .. l10n("set to TSP approximation mode"))
else
Questie:Print("Usage: /questie route [off/single/all/tsp]")
end
return
end
print(Questie:Colorize("[Questie] ", "yellow") .. l10n("Invalid command. For a list of options please type: ") .. Questie:Colorize("/questie help", "yellow"));
end
+126 -183
View File
@@ -10,25 +10,21 @@ local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
local QuestieMap = QuestieLoader:ImportModule("QuestieMap")
---@type QuestieTracker
local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker")
---@type TrackerUtils
local TrackerUtils = QuestieLoader:ImportModule("TrackerUtils")
---@type QuestieCompat
local QuestieCompat = QuestieLoader:ImportModule("QuestieCompat")
---@type ZoneDB
local ZoneDB = QuestieLoader:ImportModule("ZoneDB")
---@type QuestieLib
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
---@type QuestieFramePool
local QuestieFramePool = QuestieLoader:ImportModule("QuestieFramePool")
---@type QuestieLib
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
---@type l10n
local l10n = QuestieLoader:ImportModule("l10n")
-------------------------
--Compat
-------------------------
local GetTime = GetTime
local pairs = pairs
local ipairs = ipairs
local tinsert = table.insert
local type = type
-------------------------
--Route optimization modes
@@ -41,8 +37,14 @@ local ROUTE_MODE_TSP_APPROXIMATION = 4
-------------------------
--State
-------------------------
local routeFrames = {}
local currentRouteMode = ROUTE_MODE_OFF
local routeIcons = {}
local routeLines = {}
-------------------------
--Constants
-------------------------
local ROUTE_COLOR = {0.2, 0.8, 1, 0.8}
local WAYPOINT_ICON = "Interface\\WorldMap\\WorldMapPartyIcon"
--- Calculate distance between two points
---@param x1 number
@@ -54,9 +56,21 @@ local function _GetDistance(x1, y1, x2, y2)
return math.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
end
--- Get player position on current map
---@return number? x
---@return number? y
local function _GetPlayerPosition()
local mapID = GetCurrentMapAreaID()
local x, y = GetPlayerMapPosition(mapID)
if x == 0 and y == 0 then
x, y = GetPlayerMapPosition("player")
end
return x, y
end
--- Nearest neighbor TSP approximation
---@param coordinates table<number, {x: number, y: number, data: any}>
---@return table<number, {x: number, y: number, data: any}>
---@param coordinates table<number, {x: number, y: number, zone: number}>
---@return table<number, {x: number, y: number, zone: number}>
local function _NearestNeighborTSP(coordinates)
if #coordinates <= 1 then
return coordinates
@@ -96,26 +110,29 @@ local function _NearestNeighborTSP(coordinates)
return route
end
--- Get spawn coordinates for a single quest
--- Get spawn coordinates for objectives in a quest
---@param questId number
---@return table<number, {x: number, y: number, data: any}>?
local function _GetQuestSpawnCoordinates(questId)
---@return table<number, {x: number, y: number, zone: number}>?
local function _GetQuestObjectiveCoords(questId)
local quest = QuestieDB.GetQuest(questId)
if not quest then return nil end
local coordinates = {}
local currentMapId = GetCurrentMapAreaID()
---@param spawnData table
---@param spawns table?
---@param zoneId number
local function AddSpawns(spawnData, zoneId)
if spawnData and spawnData.Spawns then
for _, spawn in pairs(spawnData.Spawns) do
local function AddCoords(spawns, zoneId)
if not spawns then return end
for _, spawn in pairs(spawns) do
if type(spawn) == "table" then
for _, coord in pairs(spawn) do
if coord[1] > 0 and coord[2] > 0 then
if type(coord) == "table" and coord[1] and coord[2] then
local x, y = coord[1], coord[2]
if x > 0 and x <= 100 and y > 0 and y <= 100 then
tinsert(coordinates, {
x = coord[1] / 100,
y = coord[2] / 100,
data = spawnData,
x = x / 100,
y = y / 100,
zone = zoneId
})
end
@@ -123,39 +140,13 @@ local function _GetQuestSpawnCoordinates(questId)
end
end
end
end
if quest.Objectives then
for _, objective in pairs(quest.Objectives) do
if objective.Spawns then
for zoneId, spawnData in pairs(objective.Spawns) do
AddSpawns(spawnData, zoneId)
end
end
if objective.KillCredit and objective.KillCredit > 0 then
local spawns = QuestieDB.QueryNPCSingle(objective.KillCredit, "spawns")
if spawns then
for _, spawn in pairs(spawns) do
for _, coord in pairs(spawn) do
if coord[1] > 0 and coord[2] > 0 then
tinsert(coordinates, {
x = coord[1] / 100,
y = coord[2] / 100,
data = { Id = objective.KillCredit, Name = "Kill Credit" },
zone = zoneId
})
end
end
end
end
end
end
end
if quest.Finishers then
for _, finisher in pairs(quest.Finishers) do
if finisher.Spawns then
for zoneId, spawnData in pairs(finisher.Spawns) do
AddSpawns(spawnData, zoneId)
AddCoords(spawnData, zoneId)
end
end
end
@@ -165,15 +156,15 @@ local function _GetQuestSpawnCoordinates(questId)
end
--- Get spawn coordinates for all tracked quests
---@return table<number, {x: number, y: number, data: any}>?
local function _GetAllTrackedQuestsCoordinates()
---@return table<number, {x: number, y: number, zone: number}>?
local function _GetAllTrackedQuestCoords()
local coordinates = {}
local trackedQuests = Questie.db.char.TrackedQuests
if not trackedQuests then return nil end
for questId in pairs(trackedQuests) do
local questCoords = _GetQuestSpawnCoordinates(questId)
local questCoords = _GetQuestObjectiveCoords(questId)
if questCoords then
for _, coord in pairs(questCoords) do
tinsert(coordinates, coord)
@@ -184,145 +175,78 @@ local function _GetAllTrackedQuestsCoordinates()
return coordinates
end
--- Clear all route frames
--- Clear all route visuals
function QuestieRouteOptimizer:ClearRoutes()
for _, frame in pairs(routeFrames) do
if frame and frame:Hide then
frame:Hide()
for _, icon in pairs(routeIcons) do
if icon and icon.Hide then
icon:Hide()
end
end
routeFrames = {}
end
routeIcons = {}
--- Draw an optimized route for a single quest
---@param questId number
function QuestieRouteOptimizer:DrawQuestRoute(questId)
self:ClearRoutes()
local coordinates = _GetQuestSpawnCoordinates(questId)
if not coordinates or #coordinates < 2 then
return
end
local optimized = _NearestNeighborTSP(coordinates)
local lastZone = nil
local zoneRoute = {}
for i, coord in ipairs(optimized) do
if coord.zone == lastZone or not lastZone then
tinsert(zoneRoute, {coord.x, coord.y})
lastZone = coord.zone
else
self:_DrawZoneRoute(zoneRoute, lastZone)
zoneRoute = {{coord.x, coord.y}}
lastZone = coord.zone
for _, line in pairs(routeLines) do
if line and line.Hide then
line:Hide()
end
end
if #zoneRoute > 0 and lastZone then
self:_DrawZoneRoute(zoneRoute, lastZone)
end
routeLines = {}
end
--- Draw route for all tracked quests
function QuestieRouteOptimizer:DrawAllTrackedRoutes()
self:ClearRoutes()
local coordinates = _GetAllTrackedQuestsCoordinates()
if not coordinates or #coordinates < 2 then
return
end
local optimized = _NearestNeighborTSP(coordinates)
local lastZone = nil
local zoneRoute = {}
for i, coord in ipairs(optimized) do
if coord.zone == lastZone or not lastZone then
tinsert(zoneRoute, {coord.x, coord.y})
lastZone = coord.zone
else
self:_DrawZoneRoute(zoneRoute, lastZone)
zoneRoute = {{coord.x, coord.y}}
lastZone = coord.zone
end
end
if #zoneRoute > 0 and lastZone then
self:_DrawZoneRoute(zoneRoute, lastZone)
end
end
--- Draw a TSP approximation route connecting all objectives
function QuestieRouteOptimizer:DrawTSPRoute()
self:ClearRoutes()
local coordinates = {}
for questId in pairs(Questie.db.char.TrackedQuests or {}) do
local questCoords = _GetQuestSpawnCoordinates(questId)
if questCoords then
for _, coord in pairs(questCoords) do
tinsert(coordinates, coord)
end
end
end
if #coordinates < 2 then
return
end
local optimized = _NearestNeighborTSP(coordinates)
local lastZone = nil
local zoneRoute = {}
for i, coord in ipairs(optimized) do
if coord.zone == lastZone or not lastZone then
tinsert(zoneRoute, {coord.x, coord.y})
lastZone = coord.zone
else
self:_DrawZoneRoute(zoneRoute, lastZone)
zoneRoute = {{coord.x, coord.y}}
lastZone = coord.zone
end
end
if #zoneRoute > 0 and lastZone then
self:_DrawZoneRoute(zoneRoute, lastZone)
end
end
---@param waypoints table
--- Draw a route line between waypoints
---@param waypoints table<number, {x: number, y: number}>
---@param zoneId number
function QuestieRouteOptimizer:_DrawZoneRoute(waypoints, zoneId)
local function _DrawRouteLine(waypoints, zoneId)
if #waypoints < 2 then return end
local uiMapId = ZoneDB:GetUiMapIdByAreaId(zoneId)
if not uiMapId then return end
local routeData = {
Title = "Quest Route",
IconScale = 1.0,
Type = "route",
UiMapID = uiMapId,
x = waypoints[1][1],
y = waypoints[1][2],
}
local icon = QuestieMap:DrawWorldIcon(routeData, zoneId, waypoints[1][1], waypoints[1][2])
local lineFrames = QuestieFramePool:CreateWaypoints(icon, waypoints, nil, {0.2, 0.8, 1, 0.7}, zoneId)
tinsert(routeFrames, icon)
local icon = CreateFrame("Button", "QuestieRouteIcon" .. #routeIcons, UIParent)
icon:SetWidth(1)
icon:SetHeight(1)
icon:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
icon:SetFrameLevel(1)
icon:Show()
tinsert(routeIcons, icon)
local lineFrames = QuestieFramePool:CreateWaypoints(icon, waypoints, 2, ROUTE_COLOR, zoneId)
for _, lineFrame in ipairs(lineFrames) do
tinsert(routeFrames, lineFrame)
lineFrame:Show()
tinsert(routeLines, lineFrame)
end
end
--- Update route display based on current mode
--- Draw optimized route for given coordinates
---@param coordinates table<number, {x: number, y: number, zone: number}>
function QuestieRouteOptimizer:DrawRoute(coordinates)
self:ClearRoutes()
if not coordinates or #coordinates < 2 then
return
end
local optimized = _NearestNeighborTSP(coordinates)
local currentMapId = GetCurrentMapAreaID()
local mapWaypoints = {}
local currentZone = nil
for _, coord in ipairs(optimized) do
if coord.zone == currentZone or not currentZone then
tinsert(mapWaypoints, {coord.x, coord.y})
currentZone = coord.zone
else
if #mapWaypoints >= 2 then
_DrawRouteLine(mapWaypoints, currentZone)
end
mapWaypoints = {{coord.x, coord.y}}
currentZone = coord.zone
end
end
if #mapWaypoints >= 2 then
_DrawRouteLine(mapWaypoints, currentZone)
end
end
--- Update route based on current mode
function QuestieRouteOptimizer:Update()
local mode = Questie.db.profile.routeMode or ROUTE_MODE_OFF
@@ -331,18 +255,37 @@ function QuestieRouteOptimizer:Update()
elseif mode == ROUTE_MODE_SINGLE_QUEST then
local questId = QuestieTracker:GetSelectedQuest()
if questId then
self:DrawQuestRoute(questId)
local coords = _GetQuestObjectiveCoords(questId)
if coords and #coords >= 2 then
self:DrawRoute(coords)
else
self:ClearRoutes()
end
else
self:ClearRoutes()
end
elseif mode == ROUTE_MODE_ALL_TRACKED or mode == ROUTE_MODE_TSP_APPROXIMATION then
local coords = _GetAllTrackedQuestCoords()
if coords and #coords >= 2 then
self:DrawRoute(coords)
else
self:ClearRoutes()
end
elseif mode == ROUTE_MODE_ALL_TRACKED then
self:DrawAllTrackedRoutes()
elseif mode == ROUTE_MODE_TSP_APPROXIMATION then
self:DrawTSPRoute()
end
end
--- Get route mode from settings
--- Toggle route visibility
function QuestieRouteOptimizer:Toggle()
if routeIcons and #routeIcons > 0 then
self:ClearRoutes()
Questie.db.profile.routeMode = ROUTE_MODE_OFF
else
self:Update()
end
end
--- Get current route mode
---@return number mode
function QuestieRouteOptimizer:GetMode()
return Questie.db.profile.routeMode or ROUTE_MODE_OFF
end