feat: add Use Quest Item keybind + fix custom zone names + nil check

This commit is contained in:
Xurkon
2026-03-28 22:33:54 -05:00
parent 69c0f8045c
commit e6c27ddf77
5 changed files with 187 additions and 38 deletions
+1
View File
@@ -7,6 +7,7 @@
- **[Fix — Killcredit NPC Linking]** Enhanced `killcredit` objective handling to properly link NPCs when database entries have missing or invalid IDs. Added two-pass approach: first tries ID-based lookup, then falls back to name-based search using the objective description text. Also fixed `monster` function to gracefully handle `npcId <= 0` instead of erroring.
- **[Feature — Existing Quest Scanning]** Added `ScanExistingQuestLog` function to proactively map objectives for quests already in the quest log on addon initialization. Previously, objective mapping only occurred for newly accepted quests.
- **[Fix — AceComm-3.0]** Added nil check for `RegisterAddonMessagePrefix` on private servers where this API may not be available.
- **[Feature — Use Quest Item Keybind]** Added configurable keyboard hotkey to automatically use the quest item for the nearest incomplete quest objective. When pressed, Questie scans active quests for usable quest items (items with spells), checks which ones are in the player's bags, calculates proximity to quest objectives, and uses the nearest one. Configurable via Tracker options tab.
## v1.5.0 (2026-03-28)
@@ -100,6 +100,7 @@ function QuestieOptionsDefaults:Load()
trackerbindSetTomTom = 'ctrlleft',
trackerbindOpenQuestLog = 'left',
trackerbindUntrack = "shiftleft",
useQuestItemKeybind = '',
trackerSetpoint = "TOPLEFT",
trackerFontSizeHeader = 12,
trackerFontHeader = 'Friz Quadrata TT',
@@ -573,6 +573,20 @@ function QuestieOptions.tabs.tracker:Initialize()
Questie.db.profile.trackerbindSetTomTom = key
end
},
useQuestItemKeybind = {
type = "keybinding",
order = 10.5,
name = function() return l10n('Use Quest Item (Nearest)') end,
desc = function() return l10n('Press this keybind to automatically use a quest item for the nearest incomplete quest objective. The item must be in your bags and be a usable quest item (trigger a spell when used).') end,
disabled = function() return not Questie.db.profile.trackerEnabled end,
get = function() return Questie.db.profile.useQuestItemKeybind end,
set = function(_, key)
Questie.db.profile.useQuestItemKeybind = key
if QuestieTracker_UpdateQuestItemKeybind then
QuestieTracker_UpdateQuestItemKeybind()
end
end
},
trackerSetpoint = {
type = "select",
order = 11,
+49 -1
View File
@@ -170,6 +170,51 @@ function QuestieTracker:PruneGhostQuests()
return removedAny
end
local questItemKeybindFrame = nil
questItemUseFrame = nil
function QuestieTracker_UpdateQuestItemKeybind()
if not questItemKeybindFrame then
return
end
local keybind = Questie and Questie.db and Questie.db.profile and Questie.db.profile.useQuestItemKeybind
ClearOverrideBindings(questItemKeybindFrame)
if keybind and keybind ~= "" then
local upperKeybind = string.upper(keybind)
SetOverrideBinding(questItemKeybindFrame, true, upperKeybind, "CLICK Questie_QuestItemUseBtn:LeftButton")
end
end
local function _InstallQuestItemKeybindHandler()
if questItemKeybindFrame then
return
end
questItemKeybindFrame = CreateFrame("Frame", "Questie_QuestItemKeybindFrame")
questItemUseFrame = CreateFrame("Button", "Questie_QuestItemUseBtn", questItemKeybindFrame, "SecureActionButtonTemplate")
questItemUseFrame:SetAttribute("type", "item")
questItemUseFrame:Hide()
questItemKeybindFrame:SetScript("OnEvent", function(self, event)
if event == "PLAYER_LOGIN" then
C_Timer.After(0.5, QuestieTracker_UpdateQuestItemKeybind)
elseif event == "PLAYER_REGEN_ENABLED" then
ClearOverrideBindings(questItemKeybindFrame)
end
end)
questItemKeybindFrame:RegisterEvent("PLAYER_LOGIN")
questItemKeybindFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
if IsLoggedIn() then
C_Timer.After(0.5, QuestieTracker_UpdateQuestItemKeybind)
end
end
local function _InstallQuestLogUpdateListener()
if QuestieTracker._questLogUpdateListenerInstalled then return end
@@ -256,6 +301,9 @@ function QuestieTracker.Initialize()
-- Note: _InstallMissingQuestLogWarningFilter was removed (fix #1).
-- Ghost quest warnings are now prevented upstream in PruneGhostQuests.
-- Initialize keyboard handler for Use Quest Item keybind
_InstallQuestItemKeybindHandler()
TrackerFadeTicker.Initialize(trackerBaseFrame, trackerHeaderFrame)
QuestieTracker.started = true
@@ -1230,7 +1278,7 @@ function QuestieTracker:Update()
-- Add incomplete Quest Objectives
if complete == 0 and quest.isComplete ~= true then
for _, objective in pairs(quest.Objectives) do
if objective and (not Questie.db.profile.hideCompletedQuestObjectives or (Questie.db.profile.hideCompletedQuestObjectives and objective.Needed ~= objective.Collected)) then
if objective and objective.Description and (not Questie.db.profile.hideCompletedQuestObjectives or (Questie.db.profile.hideCompletedQuestObjectives and objective.Needed ~= objective.Collected)) then
-- Get next line in linePool
line = TrackerLinePool.GetNextLine()
+122 -37
View File
@@ -656,41 +656,7 @@ local function _GetContinent(uiMapId)
end
end
local function _GetZoneName(zoneOrSort, questId)
if not zoneOrSort then return "Unknown Zone" end
local zoneName
local sortObj = Questie.db.profile.trackerSortObjectives
if sortObj == "byZone" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
if (zoneOrSort) > 0 then
-- Valid ZoneID
zoneName = TrackerUtils:GetZoneNameByID(zoneOrSort)
elseif (zoneOrSort) < 0 then
-- Valid CategoryID
zoneName = TrackerUtils:GetCategoryNameByID(zoneOrSort)
else
-- The quest has no explicit zone or category. Fallback to "Unknown Zone"
zoneName = "Unknown Zone"
Questie:Debug(Questie.DEBUG_CRITICAL, "[TrackerUtils:_GetZoneName] zoneOrSort", zoneOrSort, "of quest",
questId, "is not in the Database!")
end
else
-- Let's create custom Zones based on Sorting type.
if sortObj == "byComplete" then
zoneName = "Quests (By %% Complete)"
elseif sortObj == "byCompleteReversed" then
zoneName = "Quests (By %% Complete Reversed)"
elseif sortObj == "byLevel" then
zoneName = "Quests (By Level)"
elseif sortObj == "byLevelReversed" then
zoneName = "Quests (By Level Reversed)"
elseif sortObj == "byProximity" then
zoneName = "Quests (By Proximity)"
elseif sortObj == "byProximityReversed" then
zoneName = "Quests (By Proximity Reversed)"
end
end
return zoneName
end
---@return table sortedQuestIds Table with sorted Quest ID's by Sort Type
---@return table questDetails Table with raw quest table from QuestiePlayer.currentQuestLog, percentage completed value per quest, and a "translated" zoneName
@@ -727,6 +693,47 @@ local function GetQuestLogZoneName(questId)
return nil
end
local function _GetZoneName(zoneOrSort, questId, zoneNameOverride)
if zoneNameOverride and zoneNameOverride ~= "" then
return zoneNameOverride
end
if not zoneOrSort then return "Unknown Zone" end
local zoneName
local sortObj = Questie.db.profile.trackerSortObjectives
if sortObj == "byZone" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
if (zoneOrSort) > 0 then
zoneName = TrackerUtils:GetZoneNameByID(zoneOrSort)
if not zoneName or zoneName == "Unknown Zone" then
local logZone = GetQuestLogZoneName(questId)
if logZone then
zoneName = logZone
end
end
elseif (zoneOrSort) < 0 then
zoneName = TrackerUtils:GetCategoryNameByID(zoneOrSort)
else
zoneName = "Unknown Zone"
Questie:Debug(Questie.DEBUG_CRITICAL, "[TrackerUtils:_GetZoneName] zoneOrSort", zoneOrSort, "of quest",
questId, "is not in the Database!")
end
else
if sortObj == "byComplete" then
zoneName = "Quests (By %% Complete)"
elseif sortObj == "byCompleteReversed" then
zoneName = "Quests (By %% Complete Reversed)"
elseif sortObj == "byLevel" then
zoneName = "Quests (By Level)"
elseif sortObj == "byLevelReversed" then
zoneName = "Quests (By Level Reversed)"
elseif sortObj == "byProximity" then
zoneName = "Quests (By Proximity)"
elseif sortObj == "byProximityReversed" then
zoneName = "Quests (By Proximity Reversed)"
end
end
return zoneName
end
-- Returns nil if the quest is not currently in the quest log.
function TrackerUtils:BuildFallbackQuest(questId)
for i = 1, GetNumQuestLogEntries() do
@@ -763,6 +770,10 @@ function TrackerUtils:BuildFallbackQuest(questId)
end
end
local zoneId = (zoneText and GetAreaIdByZoneName(zoneText)) or 0
local zoneNameOverride = nil
if zoneText and (not zoneId or zoneId == 0) then
zoneNameOverride = zoneText
end
local quest = {
Id = questId,
@@ -770,6 +781,7 @@ function TrackerUtils:BuildFallbackQuest(questId)
level = level or 0,
zoneOrSort = zoneId,
zoneName = zoneText,
zoneNameOverride = zoneNameOverride,
Objectives = objectives,
SpecialObjectives = {},
isFallback = true,
@@ -829,7 +841,13 @@ function TrackerUtils:GetSortedQuestIds()
local logZone = GetQuestLogZoneName(capturedId)
if logZone then
quest.zoneName = logZone
quest.zoneOrSort = GetAreaIdByZoneName(logZone) or 0
local areaId = GetAreaIdByZoneName(logZone)
if areaId and areaId > 0 then
quest.zoneOrSort = areaId
else
quest.zoneOrSort = 0
quest.zoneNameOverride = logZone
end
end
end
QuestiePlayer.currentQuestlog[qid] = quest
@@ -860,7 +878,7 @@ function TrackerUtils:GetSortedQuestIds()
-- Create questDetails table keys and insert values
questDetails[qid] = {}
questDetails[qid].quest = quest
questDetails[qid].zoneName = quest.zoneName or _GetZoneName(quest.zoneOrSort, qid)
questDetails[qid].zoneName = _GetZoneName(quest.zoneOrSort, qid, quest.zoneNameOverride)
if quest:IsComplete() == 1 or (not next(quest.Objectives)) then
questDetails[qid].questCompletePercent = 1
@@ -1279,3 +1297,70 @@ function TrackerUtils:UpdateVoiceOverPlayButtons()
end
end
end
function TrackerUtils:UseNearestQuestItem()
if InCombatLockdown() then
Questie:Debug(Questie.DEBUG_INFO, "[TrackerUtils:UseNearestQuestItem] Cannot use items while in combat")
return
end
local playerPosition = _GetWorldPlayerPosition()
if not playerPosition then
Questie:Debug(Questie.DEBUG_INFO, "[TrackerUtils:UseNearestQuestItem] Could not get player position")
return
end
local bestDistance = math.huge
local bestQuestIndex = nil
local bestItemName = nil
local numEntries, numQuests = GetNumQuestLogEntries()
for i = 1, numEntries do
local title, level, questTag, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(i)
if not isHeader and questId then
local itemInfo = GetQuestLogSpecialItemInfo(i)
if itemInfo then
local itemName
if type(itemInfo) == "string" then
itemName = itemInfo
else
itemName = tostring(itemInfo)
end
local quest = QuestieDB.GetQuest(questId)
if quest and quest:IsComplete() ~= 1 then
local spawn, zone = QuestieMap:GetNearestQuestSpawn(quest)
if spawn and zone then
local uiMapId = ZoneDB:GetUiMapIdByAreaId(zone)
if uiMapId then
local _, worldPosition = C_Map.GetWorldPosFromMapPos(uiMapId, {
x = spawn[1] / 100,
y = spawn[2] / 100
})
if worldPosition then
local distance = _GetDistance(playerPosition.x, playerPosition.y, worldPosition.x, worldPosition.y)
if distance < bestDistance then
bestDistance = distance
bestQuestIndex = i
bestItemName = itemName
end
end
end
end
end
end
end
end
if bestQuestIndex and bestItemName then
Questie:Debug(Questie.DEBUG_INFO, "[TrackerUtils:UseNearestQuestItem] Using item:", bestItemName, "for quest index:", bestQuestIndex, "distance:", bestDistance)
UseItemByName(bestItemName)
else
Questie:Debug(Questie.DEBUG_INFO, "[TrackerUtils:UseNearestQuestItem] No usable quest item found")
end
end