feat: add Use Quest Item keybind + fix custom zone names + nil check
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user