Files
Questie-Octo/Database/Database.lua
T
2026-08-16 20:28:28 -05:00

359 lines
9.7 KiB
Lua

-- Questie-Octo canonical database facade.
--
-- IMPORTANT:
-- pfDB is not exposed beyond Database/. The rest of Questie-Octo talks only
-- through this API.
QuestieOcto.DatabaseAPI = QuestieOcto.DatabaseAPI or {}
local DB = QuestieOcto.DatabaseAPI
DB.ready = false
DB.questIDs = DB.questIDs or nil
DB.questTitleCache = DB.questTitleCache or {}
DB.questSearchTitleCache = DB.questSearchTitleCache or {}
local function RawQuest(id)
return QuestieOcto.Database:GetRawQuest(id)
end
local function QuestLocale(id)
return QuestieOcto.Database:GetQuestLocale(id)
end
function DB:IsReady()
return self.ready and true or false
end
function DB:GetQuestCount()
-- questIDs is built incrementally before DATABASE_API_READY is published, so
-- counting it is O(1) and avoids walking the entire pfDB quest table again.
return self.questIDs and table.getn(self.questIDs) or 0
end
function DB:BuildQuestIDCache()
local result={}
if not pfDB or not pfDB.quests or not pfDB.quests.data then
self.questIDs=result
return result
end
for id in pairs(pfDB.quests.data) do table.insert(result,id) end
self.questIDs=result
return result
end
function DB:GetQuestIDs()
if not self.ready then return {} end
return self.questIDs or self:BuildQuestIDCache()
end
function DB:GetQuestRaw(id)
if not self.ready then return nil end
return RawQuest(id)
end
function DB:GetQuestTitle(id)
if not self.ready then return nil end
local cached=self.questTitleCache and self.questTitleCache[id]
if cached then return cached end
local loc=QuestLocale(id)
local title=nil
if type(loc)=="table" then title=loc["T"] or loc[1]
elseif type(loc)=="string" then title=loc end
title=title or ("Quest "..tostring(id))
self.questTitleCache[id]=title
self.questSearchTitleCache[id]=string.lower(title)
return title
end
function DB:GetQuestSearchTitle(id)
if not self.ready then return nil end
local cached=self.questSearchTitleCache and self.questSearchTitleCache[id]
if cached then return cached end
local title=self:GetQuestTitle(id)
return title and string.lower(title) or nil
end
function DB:GetQuestSearchIndex()
if not self.ready then return {} end
return self.questSearchTitleCache or {}
end
function DB:GetQuestRewards(id)
if not self.ready then return nil end
return QuestieOcto.QuestRewardsData and QuestieOcto.QuestRewardsData[tonumber(id)] or nil
end
-- Questie Journey presents the quest's authored objective/description text
-- rather than reconstructing every objective from database entity rows.
-- pfDB's localized quest table stores that objective sentence in field "O".
function DB:GetQuestObjectiveText(id)
if not self.ready then return nil end
local loc=QuestLocale(id)
if type(loc)=="table" then
local text=loc["O"]
if type(text)=="string" and text~="" then return text end
end
return nil
end
-- pfDB's localized quest table stores the main quest-offer description in
-- field "D". Keep it separate from "O" (the objective summary), matching the
-- way the Vanilla quest dialog presents description and objectives.
function DB:GetQuestDescriptionText(id)
if not self.ready then return nil end
local loc=QuestLocale(id)
if type(loc)=="table" then
local text=loc["D"]
if type(text)=="string" and text~="" then return text end
end
return nil
end
function DB:GetItemRaw(id)
if not self.ready then return nil end
return QuestieOcto.Database:GetRawItem(id)
end
function DB:GetCreatureRaw(id)
if not self.ready then return nil end
return QuestieOcto.Database:GetRawUnit(id)
end
function DB:GetObjectRaw(id)
if not self.ready then return nil end
return QuestieOcto.Database:GetRawObject(id)
end
function DB:GetItemName(id)
if pfDB and pfDB.items and pfDB.items.loc then
local loc=pfDB.items.loc[id]
if type(loc)=="string" and loc~="" then return loc end
end
if C_Item and C_Item.GetItemNameByID then
local name=C_Item.GetItemNameByID(id)
if name then return name end
end
if GetItemInfo then
local name=GetItemInfo(id)
if name then return name end
end
return "Item "..tostring(id)
end
function DB:GetCreatureName(id)
if pfDB and pfDB.units and pfDB.units.loc then
local loc=pfDB.units.loc[id]
if type(loc)=="string" then return loc end
end
return "Creature "..tostring(id)
end
function DB:GetObjectName(id)
if pfDB and pfDB.objects and pfDB.objects.loc then
local loc=pfDB.objects.loc[id]
if type(loc)=="string" then return loc end
end
return "Object "..tostring(id)
end
function DB:GetZoneName(id)
if not self.ready then return nil end
id=tonumber(id)
if not id then return nil end
if pfDB and pfDB.zones and pfDB.zones.loc then
local loc=pfDB.zones.loc[id]
if type(loc)=="string" and loc~="" then return loc end
end
return nil
end
function DB:GetItemSources(id)
local raw=self:GetItemRaw(id)
if not raw then return nil end
return {
Creature=raw["U"],
GameObject=raw["O"],
Reference=raw["R"],
Vendor=raw["V"],
}
end
function DB:GetReferenceLootRaw(id)
if not self.ready then return nil end
if not pfDB or not pfDB.refloot or not pfDB.refloot.data then return nil end
return pfDB.refloot.data[id]
end
function DB:GetCreatureCoords(id)
local raw=self:GetCreatureRaw(id)
return raw and raw["coords"] or nil
end
function DB:GetObjectCoords(id)
local raw=self:GetObjectRaw(id)
return raw and raw["coords"] or nil
end
function DB:OnLegacyReady()
-- Build the quest-ID index incrementally as well. Iterating the entire quest
-- database in the DATABASE_READY callback creates an avoidable single-frame
-- spike on Vanilla clients. No consumer is released until the cache is done.
self.ready=false
self.questIDs={}
self.questTitleCache={}
self.questSearchTitleCache={}
local data=pfDB and pfDB.quests and pfDB.quests.data or {}
local cursor=nil
local function Slice()
local count=0
while count<200 do
local key=next(data,cursor)
if key==nil then
DB.ready=true
QuestieOcto:SendMessage("DATABASE_API_READY")
return
end
cursor=key
table.insert(DB.questIDs,key)
local loc=QuestLocale(key)
local title=nil
if type(loc)=="table" then title=loc["T"] or loc[1]
elseif type(loc)=="string" then title=loc end
title=title or ("Quest "..tostring(key))
DB.questTitleCache[key]=title
DB.questSearchTitleCache[key]=string.lower(title)
count=count+1
end
QuestieOcto.Scheduler:Enqueue(Slice,"database-quest-id-cache")
end
QuestieOcto.Scheduler:Enqueue(Slice,"database-quest-id-cache")
end
QuestieOcto:RegisterMessage("DATABASE_READY",DB,"OnLegacyReady")
function DB:GetQuestItemRequirementRaw(id)
if not self.ready then return nil end
if not pfDB or not pfDB["quests-itemreq"] or not pfDB["quests-itemreq"].data then return nil end
return pfDB["quests-itemreq"].data[id]
end
function DB:GetCreatureType(id)
local raw=self:GetCreatureRaw(id)
return raw and raw["type"] or nil
end
function DB:GetCreatureRank(id)
local raw=self:GetCreatureRaw(id)
return raw and (raw["rnk"] or raw["rank"]) or nil
end
function DB:GetCreatureFaction(id)
local raw=self:GetCreatureRaw(id)
return raw and (raw["fac"] or raw["faction"]) or nil
end
function DB:GetObjectFaction(id)
local raw=self:GetObjectRaw(id)
return raw and (raw["fac"] or raw["faction"]) or nil
end
function DB:GetMapIDByName(name)
if not self.ready or not name then return nil end
if not pfDB or not pfDB.zones or not pfDB.zones.loc then return nil end
for id,zoneName in pairs(pfDB.zones.loc) do
if zoneName==name then return id end
end
-- The active localized table uses English through __index for missing direct
-- lookups. pairs() does not enumerate __index entries on Vanilla Lua, so
-- search the English fallback explicitly for untranslated Turtle zones.
if pfDB.zones.enUS and pfDB.zones.loc~=pfDB.zones.enUS then
for id,zoneName in pairs(pfDB.zones.enUS) do
if pfDB.zones.loc[id]==nil and zoneName==name then return id end
end
end
return nil
end
function DB:GetCreatureLevel(id)
local raw=self:GetCreatureRaw(id)
return raw and raw["lvl"] or nil
end
function DB:GetCreatureRespawnSeconds(id)
local coords=self:GetCreatureCoords(id)
if not coords then return nil end
local best=nil
for _,coord in pairs(coords) do
if type(coord)=="table" then
local seconds=tonumber(coord[4])
if seconds and seconds>0 then
if not best or seconds>best then best=seconds end
end
end
end
return best
end
function DB:GetCreatureMapIDs(id)
local result={}
local seen={}
local coords=self:GetCreatureCoords(id)
if coords then
for _,coord in pairs(coords) do
if type(coord)=="table" and tonumber(coord[3]) then
local mapID=tonumber(coord[3])
if not seen[mapID] then
seen[mapID]=true
table.insert(result,mapID)
end
end
end
end
table.sort(result)
return result
end
function DB:GetProfessionName(id)
if not self.ready then return nil end
id=tonumber(id)
if not id then return nil end
local loc=pfDB and pfDB.professions and (pfDB.professions.loc or pfDB.professions.enUS)
return loc and loc[id] or nil
end
function DB:GetTrackingMeta(metaKey)
if not self.ready or not metaKey or not pfDB then return nil end
local turtle=pfDB["meta-turtle"]
if turtle and turtle[metaKey] then return turtle[metaKey] end
return pfDB.meta and pfDB.meta[metaKey] or nil
end
function DB:GetMinimapSize(mapID)
mapID=tonumber(mapID)
if not self.ready or not mapID then return nil,nil end
if not pfDB or not pfDB["minimap"] then return nil,nil end
local size=pfDB["minimap"][mapID]
if type(size)~="table" then return nil,nil end
return tonumber(size[1]),tonumber(size[2])
end