bf6ecaedbe
Code changes (13 files, +916/-171): - ZoneDB integration: GetZoneId() converts uiMapId→areaId via ZoneDB reverse lookup - MIN_CONFIDENCE_PINS reduced to 1 for Ascension (incomplete NPC DBs) - QuestieLearner icon preservation: objective Icon passed to RegisterObjectiveTooltip - InjectLearnedData zone migration: converts old uiMapId spawn keys to areaId - areaId passed through all LearnNPC call sites (OnMouseoverUnit, OnQuestDetail, OnQuestComplete, OnQuestAccepted, OnQuestTurnedIn, OnGossipShow) - Compat/Compat.lua: C_Map.GetPlayerMapPosition UiMapData support - Compat/HBD.lua: Sunstrider mapData aliases and fallback loading - Sunstrider arrow fix (QuestieArrow.lua), resolved pin rendering (QuestieMap.lua) - _MergeOverride helper for string/numeric key compatibility - Northshire Valley UiMapData registration Testing infrastructure: - .busted config pointing to tests/ directory - Tests/wow_api_mock.lua: WoW API mocks (ZoneDB, C_Map, QuestLogCache, etc.) - Tests/QuestieLearner_spec.lua: 12 tests covering coordinate scaling, spell cast learning, zone migration (NPC/objects), icon preservation, settings defaults, and LearnNPC spawn zone tracking - selene.toml + wow_classic.yml: linter configuration Documentation: - Makefile with test/lint/ci targets - sunstrider-coordinate-collection.md: coordinate data reference - sunstrider-pin-fix.md: root cause analysis and fix documentation
612 lines
28 KiB
Lua
612 lines
28 KiB
Lua
---@diagnostic disable: undefined-global, return-type-mismatch, undefined-field
|
|
---@class QuestieCompat
|
|
---@type table|_G
|
|
QuestieCompat = QuestieLoader:CreateModule("QuestieCompat")
|
|
setmetatable(QuestieCompat, { __index = _G })
|
|
QuestieCompat.addonName = QuestieLoader.addonName
|
|
|
|
|
|
------------------------------------------
|
|
-- Lua 5.0 / 5.1 / 5.2 compatibility shims
|
|
-- NOTE: string.match, select(), and math.mod shims live in QuestieLoader.lua
|
|
-- which is always loaded first. Do NOT duplicate them here.
|
|
------------------------------------------
|
|
|
|
if not math.mod and math.fmod then
|
|
math.mod = math.fmod
|
|
end
|
|
|
|
-- Polyfill for xpcall variadic arguments (missing in standard Lua 5.0/5.1 WoW clients).
|
|
-- Modern Ace3 uses xpcall(func, err, ...) which drops arguments on legacy clients,
|
|
-- leading to 'self' being nil in addon callbacks.
|
|
local _xpcall = xpcall
|
|
local xpcall_supported = false
|
|
pcall(function()
|
|
_xpcall(function(a) if a == 1 then xpcall_supported = true end end, function() end, 1)
|
|
end)
|
|
|
|
if not xpcall_supported then
|
|
QuestieCompat.xpcall = function(func, err, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25)
|
|
-- To avoid the GC overhead of building {...} on every event fire, we pre-check argument counts.
|
|
-- We support up to 25 arguments just like our select() polyfill.
|
|
if arg25 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25) end, err) end
|
|
if arg24 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24) end, err) end
|
|
if arg23 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23) end, err) end
|
|
if arg22 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22) end, err) end
|
|
if arg21 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21) end, err) end
|
|
if arg20 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20) end, err) end
|
|
if arg19 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19) end, err) end
|
|
if arg18 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18) end, err) end
|
|
if arg17 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17) end, err) end
|
|
if arg16 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) end, err) end
|
|
if arg15 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) end, err) end
|
|
if arg14 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) end, err) end
|
|
if arg13 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) end, err) end
|
|
if arg12 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) end, err) end
|
|
if arg11 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) end, err) end
|
|
if arg10 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) end, err) end
|
|
if arg9 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) end, err) end
|
|
if arg8 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) end, err) end
|
|
if arg7 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) end, err) end
|
|
if arg6 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6) end, err) end
|
|
if arg5 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5) end, err) end
|
|
if arg4 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4) end, err) end
|
|
if arg3 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3) end, err) end
|
|
if arg2 ~= nil then return _xpcall(function() return func(arg1, arg2) end, err) end
|
|
if arg1 ~= nil then return _xpcall(function() return func(arg1) end, err) end
|
|
|
|
-- No extra args provided
|
|
return _xpcall(func, err)
|
|
end
|
|
-- Crucial: Expose to the global environment so that unmodified Ace3 libraries (like AceGUI-3.0)
|
|
-- will pick it up instead of using the broken native version which drops arguments causing crashes.
|
|
_G.xpcall = QuestieCompat.xpcall
|
|
else
|
|
-- Native xpcall works fine, expose it
|
|
QuestieCompat.xpcall = _xpcall
|
|
end
|
|
|
|
------------------------------------------
|
|
-- GetCurrentRegion polyfill (WotLK/Classic)
|
|
------------------------------------------
|
|
|
|
-- GetCurrentRegion and GetCurrentRegionName are modern API functions that don't exist in WotLK.
|
|
-- AceDB-3.0 uses these for realm identification. Provide fallbacks based on locale.
|
|
if not GetCurrentRegion then
|
|
local regionByLocale = {
|
|
["enUS"] = 1, ["enGB"] = 1, ["koKR"] = 2, ["frFR"] = 3, ["deDE"] = 3,
|
|
["zhCN"] = 5, ["zhTW"] = 4, ["esES"] = 3, ["esMX"] = 1, ["ruRU"] = 3,
|
|
["ptBR"] = 1, ["itIT"] = 3,
|
|
}
|
|
GetCurrentRegion = function()
|
|
return regionByLocale[GetLocale()] or 1
|
|
end
|
|
end
|
|
|
|
if not GetCurrentRegionName then
|
|
local regionNames = { "US", "KR", "EU", "TW", "CN" }
|
|
GetCurrentRegionName = function()
|
|
return regionNames[GetCurrentRegion()] or "US"
|
|
end
|
|
end
|
|
|
|
-- Addon is running on a client without WOW_PROJECT_ID defined (e.g. 3.3.5 WotLK)
|
|
do
|
|
local _, _, _, build = GetBuildInfo()
|
|
QuestieCompat.Is335 = (build == 30300)
|
|
|
|
-- Polyfill WOW_PROJECT_ID and constants if missing
|
|
if WOW_PROJECT_ID == nil then
|
|
_G.WOW_PROJECT_CLASSIC = _G.WOW_PROJECT_CLASSIC or 2
|
|
_G.WOW_PROJECT_BURNING_CRUSADE_CLASSIC = _G.WOW_PROJECT_BURNING_CRUSADE_CLASSIC or 5
|
|
_G.WOW_PROJECT_WRATH_CLASSIC = _G.WOW_PROJECT_WRATH_CLASSIC or 11
|
|
|
|
if build >= 30000 and build < 40000 then
|
|
_G.WOW_PROJECT_ID = _G.WOW_PROJECT_WRATH_CLASSIC
|
|
elseif build >= 20000 and build < 30000 then
|
|
_G.WOW_PROJECT_ID = _G.WOW_PROJECT_BURNING_CRUSADE_CLASSIC
|
|
elseif build < 20000 then
|
|
_G.WOW_PROJECT_ID = _G.WOW_PROJECT_CLASSIC
|
|
end
|
|
end
|
|
end
|
|
|
|
local errorMsg = "Questie tried to call a blizzard API function that does not exist..."
|
|
|
|
local sqrt = math.sqrt
|
|
|
|
local CALIBRATED_MAP_GROUPS = {
|
|
sunstrider = {
|
|
-- Use the parent Eversong map as the primary lookup surface. On this realm,
|
|
-- explicit child-map (1241) position queries can still return parent/ghost-map
|
|
-- normalized coordinates (~0.60/0.44), which recreates the 433-yard/backwards-arrow bug.
|
|
primaryUiMapId = 1941,
|
|
uiMapIds = {
|
|
[1241] = true,
|
|
[946] = true,
|
|
[1941] = true,
|
|
},
|
|
zoneIds = {
|
|
[3430] = true,
|
|
},
|
|
worldUiMapId = 1941,
|
|
normalizedToPseudoWorldScale = 1353,
|
|
},
|
|
}
|
|
|
|
local function _GetCalibratedMapGroup(uiMapId, zoneId)
|
|
for _, group in pairs(CALIBRATED_MAP_GROUPS) do
|
|
if (uiMapId and group.uiMapIds and group.uiMapIds[uiMapId]) or (zoneId and group.zoneIds and group.zoneIds[zoneId]) then
|
|
return group
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieCompat.GetCalibratedMapGroup(uiMapId, zoneId)
|
|
return _GetCalibratedMapGroup(uiMapId, zoneId)
|
|
end
|
|
|
|
function QuestieCompat.IsCalibratedMap(uiMapId, zoneId)
|
|
return _GetCalibratedMapGroup(uiMapId, zoneId) ~= nil
|
|
end
|
|
|
|
function QuestieCompat.GetCalibratedWorldCoordinatesFromZone(x, y, uiMapId, zoneId)
|
|
local group = _GetCalibratedMapGroup(uiMapId, zoneId)
|
|
if group and x and y then
|
|
local scale = group.normalizedToPseudoWorldScale or 1000
|
|
return x * scale, y * scale, 0, group
|
|
end
|
|
|
|
if QuestieCompat.HBD and QuestieCompat.HBD.GetWorldCoordinatesFromZone and uiMapId then
|
|
local worldX, worldY, instanceId = QuestieCompat.HBD:GetWorldCoordinatesFromZone(x, y, uiMapId)
|
|
return worldX, worldY, instanceId, nil
|
|
end
|
|
end
|
|
|
|
function QuestieCompat.GetCalibratedPlayerPosition(uiMapId, zoneId, unitToken)
|
|
local group = _GetCalibratedMapGroup(uiMapId, zoneId)
|
|
if group then
|
|
local mapPos = QuestieCompat.C_Map and QuestieCompat.C_Map.GetPlayerMapPosition and QuestieCompat.C_Map.GetPlayerMapPosition(group.primaryUiMapId, unitToken or "player")
|
|
if type(mapPos) == "table" and mapPos.x and mapPos.y and mapPos.x > 0 and mapPos.y > 0 then
|
|
local worldX, worldY = QuestieCompat.GetCalibratedWorldCoordinatesFromZone(mapPos.x, mapPos.y, group.primaryUiMapId, zoneId)
|
|
return worldX, worldY, 0, mapPos.x, mapPos.y, group
|
|
end
|
|
end
|
|
|
|
if QuestieCompat.HBD and QuestieCompat.HBD.GetPlayerWorldPosition then
|
|
local worldX, worldY, instanceId = QuestieCompat.HBD:GetPlayerWorldPosition()
|
|
return worldX, worldY, instanceId or 0, nil, nil, group
|
|
end
|
|
end
|
|
|
|
function QuestieCompat.GetCalibratedDistanceScale(uiMapId, zoneId)
|
|
local group = _GetCalibratedMapGroup(uiMapId, zoneId)
|
|
if group and group.normalizedToPseudoWorldScale then
|
|
return group.normalizedToPseudoWorldScale / 100
|
|
end
|
|
end
|
|
|
|
------------------------------------------
|
|
-- Older client compatibility (pre 1.14.1)
|
|
------------------------------------------
|
|
|
|
-- Add missing Seasons object, if not available (e.g. 1.14.0 and below is missing it)
|
|
-- Fix #3: Do NOT write to bare _G["C_Seasons"] — store in QuestieCompat namespace only.
|
|
-- All callers should use QuestieCompat.C_Seasons.
|
|
QuestieCompat.C_Seasons = C_Seasons or {
|
|
---[C_Seasons.HasActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.HasActiveSeason)
|
|
---Returns true if the player is on a seasonal realm.
|
|
HasActiveSeason = function()
|
|
return false
|
|
end,
|
|
---[C_Seasons.GetActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.GetActiveSeason)
|
|
---Returns the ID of the season that is active on the current realm.
|
|
GetActiveSeason = function()
|
|
return 0
|
|
end
|
|
}
|
|
|
|
-- Specific subclass of this mixin was added in a minor version and is missing in earlier patches, functionality this makes next to no visual difference
|
|
if not TooltipBackdropTemplateMixin then
|
|
TooltipBackdropTemplateMixin = BackdropTemplateMixin
|
|
end
|
|
|
|
-------------------------------------------
|
|
-- AceComm/AceSerializer compatibility (WotLK)
|
|
-------------------------------------------
|
|
|
|
-- Ambiguate is used to disambiguate realm names but doesn't exist in WotLK.
|
|
-- On WotLK, realm names are already unique in the format, so we can just return the name.
|
|
if not Ambiguate then
|
|
Ambiguate = function(name, kind)
|
|
return name
|
|
end
|
|
end
|
|
|
|
-- RegisterAddonMessagePrefix may not exist in all WotLK versions.
|
|
if not RegisterAddonMessagePrefix then
|
|
RegisterAddonMessagePrefix = function(prefix)
|
|
-- No-op on versions that don't support it
|
|
end
|
|
end
|
|
|
|
-------------------------------------------
|
|
-- API difference compatibility (Era/Wotlk)
|
|
-------------------------------------------
|
|
|
|
-- Fix #2/#13: The old hooksecurefunc polyfill made raw _G table assignments which
|
|
-- directly cause taint on all modern WoW clients. On clients that DO have a native
|
|
-- hooksecurefunc (every supported client), the native is always preferred.
|
|
-- We never write to bare _G here. If hooksecurefunc is truly missing (extremely
|
|
-- old Lua 5.0 host with no secure-call protection), addon code can still call it
|
|
-- but it will simply be a no-op that prints a warning rather than injecting taint.
|
|
if not hooksecurefunc then
|
|
-- Lua 5.0 hosts (Turtle WoW pre-2.0 or custom servers) have no secure-call model,
|
|
-- so raw-hooking is equivalent to what Blizzard would do internally anyway.
|
|
-- Use a local to avoid polluting _G unnecessarily.
|
|
local function _rawHook(arg1, arg2, arg3)
|
|
local t, name, func
|
|
if type(arg1) == "string" then
|
|
t = _G
|
|
name = arg1
|
|
func = arg2
|
|
elseif type(arg1) == "table" then
|
|
t = arg1
|
|
name = arg2
|
|
func = arg3
|
|
end
|
|
if not (t and name and func) then return end
|
|
local original = t[name]
|
|
t[name] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
|
local ret1, ret2, ret3, ret4
|
|
if original then
|
|
ret1, ret2, ret3, ret4 = original(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
|
end
|
|
func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
|
return ret1, ret2, ret3, ret4
|
|
end
|
|
end
|
|
-- Only expose under QuestieCompat, never pollute _G with a replacement.
|
|
QuestieCompat.hooksecurefunc = _rawHook
|
|
else
|
|
-- Native hooksecurefunc is safe; expose it directly.
|
|
QuestieCompat.hooksecurefunc = hooksecurefunc
|
|
end
|
|
|
|
-- Fix #3: Never write C_Timer to bare _G. Store polyfill in QuestieCompat.C_Timer only.
|
|
-- All callers already use `local C_Timer = QuestieCompat.C_Timer` at top of each file.
|
|
if C_Timer then
|
|
QuestieCompat.C_Timer = C_Timer
|
|
else
|
|
-- C_Timer polyfill for Lua 5.0/5.1 clients that don't have it (e.g. Turtle WoW pre-2.0).
|
|
-- Only stored in QuestieCompat namespace, NOT in bare _G.
|
|
local TickerFrame = CreateFrame("Frame")
|
|
local tickers = {}
|
|
|
|
TickerFrame:SetScript("OnUpdate", function(self, elapsed)
|
|
local i = table.getn(tickers)
|
|
while i >= 1 do
|
|
local ticker = tickers[i]
|
|
if not ticker._cancelled then
|
|
ticker._elapsed = ticker._elapsed + elapsed
|
|
if ticker._elapsed >= ticker._duration then
|
|
ticker._elapsed = ticker._elapsed - ticker._duration
|
|
ticker._callback()
|
|
if ticker._iterations then
|
|
ticker._iterations = ticker._iterations - 1
|
|
if ticker._iterations <= 0 then
|
|
ticker._cancelled = true
|
|
table.remove(tickers, i)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
table.remove(tickers, i)
|
|
end
|
|
i = i - 1
|
|
end
|
|
end)
|
|
|
|
QuestieCompat.C_Timer = {
|
|
After = function(duration, callback)
|
|
table.insert(tickers, {
|
|
_duration = duration,
|
|
_elapsed = 0,
|
|
_callback = callback,
|
|
_iterations = 1,
|
|
_cancelled = false
|
|
})
|
|
end,
|
|
NewTicker = function(duration, callback, iterations)
|
|
local ticker = {
|
|
_duration = duration,
|
|
_elapsed = 0,
|
|
_callback = callback,
|
|
_iterations = iterations,
|
|
_cancelled = false,
|
|
Cancel = function(self)
|
|
self._cancelled = true
|
|
end
|
|
}
|
|
table.insert(tickers, ticker)
|
|
return ticker
|
|
end
|
|
}
|
|
_G.C_Timer = QuestieCompat.C_Timer
|
|
end
|
|
|
|
---[SetMinResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMinResize)
|
|
---[SetMaxResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMaxResize)
|
|
---[SetResizeBounds Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMinResize)
|
|
---Specifies the minimum [and maximum] width and height that the object can be resized to.
|
|
---@param frame frame
|
|
---@param minWidth number The minimum width the object can be resized to.
|
|
---@param minHeight number The minimum height the object can be resized to.
|
|
---@param maxWidth number The maximum width the object can be resized to.
|
|
---@param maxHeight number The maximum height the object can be resized to.
|
|
function QuestieCompat.SetResizeBounds(frame, minWidth, minHeight, maxWidth, maxHeight)
|
|
if frame.SetResizeBounds then
|
|
frame:SetResizeBounds(minWidth, minHeight, maxWidth, maxHeight)
|
|
return
|
|
else
|
|
if frame.SetMinResize and frame.SetMaxResize then
|
|
if minWidth and minWidth ~= 0 then
|
|
frame:SetMinResize(minWidth, minHeight)
|
|
end
|
|
if maxWidth and maxWidth ~= 0 then
|
|
frame:SetMaxResize(maxWidth, maxHeight)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_C_GossipInfo.GetAvailableQuests)
|
|
---Returns the available quests at a quest giver.
|
|
---@return GossipQuestUIInfo[] info
|
|
function QuestieCompat.GetAvailableQuests()
|
|
if C_GossipInfo and C_GossipInfo.GetAvailableQuests then
|
|
local info = C_GossipInfo.GetAvailableQuests()
|
|
local availableQuests = {}
|
|
local index = 1
|
|
for _, availableQuest in pairs(info) do
|
|
availableQuests[index] = availableQuest.title
|
|
availableQuests[index + 1] = availableQuest.questLevel
|
|
availableQuests[index + 2] = availableQuest.isTrivial
|
|
availableQuests[index + 3] = availableQuest.frequency
|
|
availableQuests[index + 4] = availableQuest.repeatable
|
|
availableQuests[index + 5] = availableQuest.isLegendary
|
|
availableQuests[index + 6] = availableQuest.isIgnored
|
|
index = index + 7
|
|
end
|
|
return unpack(availableQuests)
|
|
elseif GetGossipAvailableQuests then
|
|
return GetGossipAvailableQuests() -- https://wowpedia.fandom.com/wiki/API_GetGossipAvailableQuests
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_C_GossipInfo.GetActiveQuests)
|
|
---Returns the quests which can be turned in at a quest giver.
|
|
---@return GossipQuestUIInfo[] info
|
|
function QuestieCompat.GetActiveQuests()
|
|
if C_GossipInfo and C_GossipInfo.GetActiveQuests then
|
|
-- QuestieDB needs to be loaded locally, otherwise it will be an empty module
|
|
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
|
|
local info = C_GossipInfo.GetActiveQuests()
|
|
local activeQuests = {}
|
|
local index = 1
|
|
for _, activeQuest in pairs(info) do
|
|
activeQuests[index] = activeQuest.title
|
|
activeQuests[index + 1] = activeQuest.questLevel
|
|
activeQuests[index + 2] = activeQuest.isTrivial
|
|
activeQuests[index + 3] = activeQuest.isComplete or QuestieDB.IsComplete(activeQuest.questID) == 1
|
|
activeQuests[index + 4] = activeQuest.isLegendary
|
|
activeQuests[index + 5] = activeQuest.isIgnored
|
|
index = index + 6
|
|
end
|
|
return unpack(activeQuests)
|
|
elseif GetGossipActiveQuests then
|
|
return GetGossipActiveQuests() -- https://wowpedia.fandom.com/wiki/API_GetGossipActiveQuests
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_C_GossipInfo.SelectAvailableQuest)
|
|
---Selects an available quest from the gossip window.
|
|
---@param index number Index of the quest to select (I think questId might work here too...)
|
|
function QuestieCompat.SelectAvailableQuest(index)
|
|
if C_GossipInfo and C_GossipInfo.SelectAvailableQuest then
|
|
local questId = C_GossipInfo.GetAvailableQuests()[index].questID
|
|
return C_GossipInfo.SelectAvailableQuest(questId)
|
|
elseif SelectGossipAvailableQuest then
|
|
return SelectGossipAvailableQuest(index)
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_C_GossipInfo.SelectActiveQuest)
|
|
---Selects an active quest from the gossip window.
|
|
---@param index number|QuestId Index of the active quest to select, from 1 to GetNumGossipActiveQuests(); order corresponds to the order of return values from GetGossipActiveQuests().
|
|
function QuestieCompat.SelectActiveQuest(index)
|
|
if C_GossipInfo and C_GossipInfo.SelectActiveQuest then
|
|
local questId = C_GossipInfo.GetActiveQuests()[index].questID
|
|
return C_GossipInfo.SelectActiveQuest(questId)
|
|
elseif SelectGossipActiveQuest then
|
|
return SelectGossipActiveQuest(index)
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_GetContainerNumSlots)
|
|
---Returns the total number of slots in the bag specified by the index.
|
|
---@param bagID number the slot containing the bag, e.g. 0 for backpack, etc.
|
|
---@return number numberOfSlots the number of slots in the specified bag, or 0 if there is no bag in the given slot.
|
|
function QuestieCompat.GetContainerNumSlots(bagID)
|
|
if C_Container and C_Container.GetContainerNumSlots then
|
|
return C_Container.GetContainerNumSlots(bagID)
|
|
elseif GetContainerNumSlots then
|
|
return GetContainerNumSlots(bagID)
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_GetContainerItemInfo)
|
|
---Returns info for an item in a container slot.
|
|
---@param bagID number BagID of the bag the item is in, e.g. 0 for your backpack.
|
|
---@param slot number index of the slot inside the bag to look up.
|
|
---@return number texture The icon texture (FileID) for the item in the specified bag slot.
|
|
---@return number itemCount The number of items in the specified bag slot.
|
|
---@return boolean locked True if the item is locked by the server, false otherwise.
|
|
---@return number quality The Quality of the item.
|
|
---@return boolean readable True if the item can be "read" (as in a book), false otherwise.
|
|
---@return boolean lootable True if the item is a temporary container containing items that can be looted, false otherwise.
|
|
---@return string itemLink The itemLink of the item in the specified bag slot.
|
|
---@return boolean isFiltered True if the item is grayed-out during the current inventory search, false otherwise.
|
|
---@return boolean noValue True if the item has no gold value, false otherwise.
|
|
---@return number itemID The unique ID for the item in the specified bag slot.
|
|
---@return boolean isBound True if the item is bound to the current character, false otherwise.
|
|
function QuestieCompat.GetContainerItemInfo(bagID, slot)
|
|
if C_Container and C_Container.GetContainerItemInfo then
|
|
local containerInfo = C_Container.GetContainerItemInfo(bagID, slot)
|
|
if containerInfo then
|
|
return containerInfo.iconFileID,
|
|
containerInfo.stackCount,
|
|
containerInfo.isLocked,
|
|
containerInfo.quality,
|
|
containerInfo.isReadable,
|
|
containerInfo.hasLoot,
|
|
containerInfo.hyperlink,
|
|
containerInfo.isFiltered,
|
|
containerInfo.hasNoValue,
|
|
containerInfo.itemID,
|
|
containerInfo.isBound
|
|
else
|
|
return nil
|
|
end
|
|
elseif GetContainerItemInfo then
|
|
return GetContainerItemInfo(bagID, slot)
|
|
end
|
|
error(errorMsg, 2)
|
|
end
|
|
|
|
---[Documentation](https://wowpedia.fandom.com/wiki/API_GetItemCooldown)
|
|
---Returns info about the cooldown state and time of an item.
|
|
---@param itemID number The item ID.
|
|
---@return number startTime The time when the cooldown started (as returned by GetTime()) or zero if no cooldown.
|
|
---@return number duration The number of seconds the cooldown will last, or zero if no cooldown.
|
|
---@return number enable 1 if the item is ready or on cooldown, 0 if the item is used, but the cooldown didn't start yet (e.g. potion in combat).
|
|
function QuestieCompat.GetItemCooldown(itemID)
|
|
if C_Container and C_Container.GetItemCooldown then
|
|
return C_Container.GetItemCooldown(itemID)
|
|
else
|
|
return GetItemCooldown(itemID)
|
|
end
|
|
end
|
|
|
|
--- C_QuestLog Shim
|
|
if not rawget(QuestieCompat, "C_QuestLog") then
|
|
QuestieCompat.C_QuestLog = {}
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetNumQuestLogEntries()
|
|
return GetNumQuestLogEntries()
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetQuestLogTitle(questIndex)
|
|
return GetQuestLogTitle(questIndex)
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetQuestLogSelection()
|
|
return GetQuestLogSelection()
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetQuestInfo(questID)
|
|
return QuestieCompat.GetQuestInfo(questID)
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetAllQuestIDs()
|
|
local questIDs = {}
|
|
local numEntries = GetNumQuestLogEntries()
|
|
for i = 1, numEntries do
|
|
local questTitle, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID, startEvent, displayQuestID = GetQuestLogTitle(i)
|
|
if questID and questID ~= 0 then
|
|
table.insert(questIDs, questID)
|
|
end
|
|
end
|
|
return questIDs
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.IsQuestFlaggedCompleted(questID)
|
|
|
|
return IsQuestFlaggedCompleted(questID)
|
|
end
|
|
|
|
function QuestieCompat.C_QuestLog.GetQuestPlayerQuestLink(questID)
|
|
return GetQuestLink(questID)
|
|
end
|
|
|
|
--- C_Map Shim
|
|
QuestieCompat.C_Map = QuestieCompat.C_Map or {}
|
|
|
|
function QuestieCompat.C_Map.GetPlayerMapPosition(uiMapID, unitToken)
|
|
unitToken = unitToken or "player"
|
|
|
|
if uiMapID and QuestieCompat.UiMapData and QuestieCompat.UiMapData[uiMapID] then
|
|
local originalMapAreaID = GetCurrentMapAreaID()
|
|
local originalDungeonLevel = GetCurrentMapDungeonLevel and GetCurrentMapDungeonLevel() or 0
|
|
local mapID = QuestieCompat.UiMapData[uiMapID].mapID
|
|
local dungeonLevel = QuestieCompat.Round(math.mod(mapID, 1) * 10)
|
|
|
|
SetMapByID(math.floor(mapID) - 1)
|
|
if dungeonLevel > 0 and SetDungeonMapLevel then
|
|
SetDungeonMapLevel(dungeonLevel)
|
|
end
|
|
|
|
local x, y = GetPlayerMapPosition(unitToken)
|
|
|
|
if originalMapAreaID and originalMapAreaID > 0 then
|
|
SetMapByID(originalMapAreaID - 1)
|
|
if originalDungeonLevel and originalDungeonLevel > 0 and SetDungeonMapLevel then
|
|
SetDungeonMapLevel(originalDungeonLevel)
|
|
end
|
|
else
|
|
SetMapToCurrentZone()
|
|
end
|
|
|
|
return { uiMapID = uiMapID, x = x, y = y }, uiMapID
|
|
end
|
|
|
|
local playerPos, resolvedUiMapID = QuestieCompat.GetPlayerMapPosition()
|
|
return playerPos, resolvedUiMapID
|
|
end
|
|
|
|
function QuestieCompat.C_Map.GetBestMapForUnit(unit)
|
|
if unit == "player" then
|
|
return GetCurrentMapAreaID()
|
|
end
|
|
return GetCurrentMapAreaID()
|
|
end
|
|
|
|
function QuestieCompat.C_Map.GetMapInfo(uiMapID)
|
|
return GetMapInfo(uiMapID)
|
|
end
|
|
|
|
--- GetTrackedAchievements Shim
|
|
QuestieCompat.GetTrackedAchievements = QuestieCompat.GetTrackedAchievements or function()
|
|
return {}
|
|
end
|
|
|
|
--- IsAchievementCompleted Shim
|
|
QuestieCompat.IsAchievementCompleted = QuestieCompat.IsAchievementCompleted or function(achievementID)
|
|
local completed = select(4, GetAchievementInfo(achievementID))
|
|
return completed or false
|
|
end
|
|
|
|
--- LibUIDropDownMenu Shim
|
|
QuestieCompat.LibUIDropDownMenu = QuestieCompat.LibUIDropDownMenu or {}
|
|
QuestieCompat.LibUIDropDownMenu.UIDropDownMenu_Menu_NewSize = function()
|
|
end
|
|
|