1671 lines
59 KiB
Lua
1671 lines
59 KiB
Lua
---@class DBCompiler
|
|
local QuestieDBCompiler = QuestieLoader:CreateModule("DBCompiler")
|
|
|
|
---@type QuestieStreamLib
|
|
local QuestieStream = QuestieLoader:ImportModule("QuestieStreamLib"):GetStream("raw")
|
|
---@type QuestieDB
|
|
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
|
|
---@type QuestieLib
|
|
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
|
|
---@type l10n
|
|
local l10n = QuestieLoader:ImportModule("l10n")
|
|
|
|
--- COMPATIBILITY ---
|
|
local WOW_PROJECT_ID = QuestieCompat.WOW_PROJECT_ID
|
|
|
|
local type = type
|
|
local abs, min, floor = math.abs, math.min, math.floor
|
|
local lshift = bit.lshift
|
|
|
|
|
|
-- how fast to run operations (lower = slower but less lag)
|
|
local TICKS_PER_YIELD = 48
|
|
local TICKS_PER_YIELD_DEBUG = TICKS_PER_YIELD * 3
|
|
|
|
local function safeYield()
|
|
if coroutine.running() then
|
|
coroutine.yield()
|
|
end
|
|
end
|
|
|
|
---@alias CompilerTypes
|
|
---| "u8"
|
|
---| "u16"
|
|
---| "s16"
|
|
---| "u24"
|
|
---| "u32"
|
|
---| "u12pair"
|
|
---| "u24pair"
|
|
---| "s24pair"
|
|
---| "u8string"
|
|
---| "u16string"
|
|
---| "u8u16array"
|
|
---| "u8s16pairs"
|
|
---| "u16u16array"
|
|
---| "u8s24pairs"
|
|
---| "u8u24array"
|
|
---| "u16u24array"
|
|
---| "u8u16stringarray"
|
|
---| "faction"
|
|
---| "spawnlist"
|
|
---| "trigger"
|
|
---| "questgivers"
|
|
---| "objective"
|
|
---| "spellobjective"
|
|
---| "objectives"
|
|
---| "reflist"
|
|
---| "extraobjective
|
|
|
|
|
|
QuestieDBCompiler.supportedTypes = {
|
|
["table"] = {
|
|
["u12pair"] = true,
|
|
["u24pair"] = true,
|
|
["s24pair"] = true,
|
|
["u8u16array"] = true,
|
|
["u16u16array"] = true,
|
|
["u8s16pairs"] = true,
|
|
["u8s24pairs"] = true,
|
|
["spawnlist"] = true,
|
|
["trigger"] = true,
|
|
["questgivers"] = true,
|
|
["objective"] = true,
|
|
["spellobjective"] = true,
|
|
["objectives"] = true,
|
|
["waypointlist"] = true,
|
|
["u8u16stringarray"] = true,
|
|
["u8u24array"] = true,
|
|
["u16u24array"] = true,
|
|
["extraobjectives"] = true,
|
|
["reflist"] = true
|
|
},
|
|
["number"] = {
|
|
["s8"] = true,
|
|
["u8"] = true,
|
|
["u16"] = true,
|
|
["s16"] = true,
|
|
["u24"] = true,
|
|
["s24"] = true,
|
|
["u32"] = true
|
|
},
|
|
["string"] = {
|
|
["u8string"] = true,
|
|
["u16string"] = true,
|
|
["faction"] = true
|
|
}
|
|
}
|
|
|
|
local refTypes = {
|
|
"monster",
|
|
"item",
|
|
"object"
|
|
}
|
|
QuestieDBCompiler.refTypes = refTypes
|
|
|
|
QuestieDBCompiler.refTypesReversed = {
|
|
monster = 1,
|
|
item = 2,
|
|
object = 3
|
|
}
|
|
|
|
local readers = {}
|
|
local skippers = {}
|
|
|
|
readers["s8"] = function(stream)
|
|
return stream:ReadByte() - 127
|
|
end
|
|
readers["u8"] = QuestieStream.ReadByte
|
|
readers["u16"] = QuestieStream.ReadShort
|
|
readers["s16"] = function(stream)
|
|
return stream:ReadShort() - 32767
|
|
end
|
|
readers["u24"] = QuestieStream.ReadInt24
|
|
readers["s24"] = function(stream)
|
|
return stream:ReadInt24() - 8388607
|
|
end
|
|
readers["u32"] = QuestieStream.ReadInt
|
|
readers["u12pair"] = function(stream)
|
|
local a,b = stream:ReadInt12Pair()
|
|
-- bit of a hack
|
|
if a == 0 and b == 0 then
|
|
return nil
|
|
end
|
|
return {a, b}
|
|
end
|
|
readers["u24pair"] = function(stream)
|
|
local a,b = stream:ReadInt24(), stream:ReadInt24()
|
|
-- bit of a hack
|
|
if a == 0 and b == 0 then
|
|
return nil
|
|
end
|
|
|
|
return {a, b}
|
|
end
|
|
readers["s24pair"] = function(stream)
|
|
local a,b = stream:ReadInt24()-8388608, stream:ReadInt24()-8388608
|
|
-- bit of a hack
|
|
if a == 0 and b == 0 then
|
|
return nil
|
|
end
|
|
|
|
return {a, b}
|
|
end
|
|
readers["u8string"] = function(stream)
|
|
local ret = stream:ReadTinyString()
|
|
if ret == "nil" then-- I hate this but we need to support both nil strings and empty strings
|
|
return nil
|
|
else
|
|
return ret
|
|
end
|
|
end
|
|
readers["u16string"] = function(stream)
|
|
local ret = stream:ReadShortString()
|
|
if ret == "nil" then-- I hate this but we need to support both nil strings and empty strings
|
|
return nil
|
|
else
|
|
return ret
|
|
end
|
|
end
|
|
readers["u8u16array"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
|
|
for i = 1, count do
|
|
list[i] = stream:ReadShort()
|
|
end
|
|
return list
|
|
end
|
|
readers["u8s16pairs"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = {stream:ReadShort() - 32767, stream:ReadShort() - 32767}
|
|
end
|
|
return list
|
|
end
|
|
readers["u16u16array"] = function(stream)
|
|
local count = stream:ReadShort()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = stream:ReadShort()
|
|
end
|
|
return list
|
|
end
|
|
readers["u8s24pairs"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = {stream:ReadInt24()-8388608, stream:ReadInt24()-8388608}
|
|
end
|
|
return list
|
|
end
|
|
readers["u8u24array"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = stream:ReadInt24()
|
|
end
|
|
return list
|
|
end
|
|
readers["u16u24array"] = function(stream)
|
|
local count = stream:ReadShort()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = stream:ReadInt24()
|
|
end
|
|
return list
|
|
end
|
|
readers["u8u16stringarray"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local list = {}
|
|
for i = 1, count do
|
|
list[i] = stream:ReadShortString()
|
|
end
|
|
return list
|
|
end
|
|
readers["faction"] = function(stream)
|
|
local val = stream:ReadByte()
|
|
if val == 3 then
|
|
return nil
|
|
elseif val == 2 then
|
|
return "AH"
|
|
elseif val == 1 then
|
|
return "H"
|
|
else
|
|
return "A"
|
|
end
|
|
end
|
|
readers["spawnlist"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local spawnlist = {}
|
|
for _ = 1, count do
|
|
local zone = stream:ReadShort()
|
|
local spawnCount = stream:ReadShort()
|
|
local list = {}
|
|
for i = 1, spawnCount do
|
|
local x, y = stream:ReadInt12Pair()
|
|
if x == 0 and y == 0 then
|
|
list[i] = {-1, -1}
|
|
else
|
|
list[i] = {x / 40.90, y / 40.90}
|
|
end
|
|
end
|
|
spawnlist[zone] = list
|
|
end
|
|
return spawnlist
|
|
end
|
|
readers["trigger"] = function(stream)
|
|
if stream:ReadShort() == 0 then
|
|
return nil
|
|
else
|
|
stream._pointer = stream._pointer - 2
|
|
end
|
|
return {stream:ReadTinyStringNil(), readers["spawnlist"](stream)}
|
|
end
|
|
local u8u24arrayReader = readers["u8u24array"]
|
|
readers["questgivers"] = function(stream)
|
|
return {
|
|
u8u24arrayReader(stream),
|
|
u8u24arrayReader(stream),
|
|
u8u24arrayReader(stream)
|
|
}
|
|
end
|
|
readers["objective"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local ret = {}
|
|
for i = 1, count do
|
|
ret[i] = {stream:ReadInt24(), stream:ReadTinyStringNil()}
|
|
end
|
|
return ret
|
|
end
|
|
readers["spellobjective"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local ret = {}
|
|
for i = 1, count do
|
|
ret[i] = {stream:ReadInt24(), stream:ReadTinyStringNil(), stream:ReadInt24()}
|
|
end
|
|
return ret
|
|
end
|
|
|
|
readers["objectives"] = function(stream)
|
|
local ret = {
|
|
readers["objective"](stream),
|
|
readers["objective"](stream),
|
|
readers["objective"](stream),
|
|
readers["u24pair"](stream),
|
|
}
|
|
|
|
local count = stream:ReadByte()
|
|
if count == 0 then
|
|
ret[5] = nil
|
|
else
|
|
local killobjectives = {}
|
|
for i=1, count do
|
|
local creditCount = stream:ReadByte()
|
|
local creditList = {}
|
|
for j=1, creditCount do
|
|
creditList[j] = stream:ReadInt24()
|
|
end
|
|
killobjectives[i] = {creditList, stream:ReadInt24(), stream:ReadTinyStringNil()}
|
|
end
|
|
ret[5] = killobjectives
|
|
end
|
|
|
|
ret[6] = readers["spellobjective"](stream)
|
|
|
|
return ret
|
|
end
|
|
readers["reflist"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count > 0 then
|
|
local ret = {}
|
|
for i=1,count do
|
|
ret[i] = {refTypes[stream:ReadByte()], stream:ReadInt24()}
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
readers["extraobjectives"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count > 0 then
|
|
local ret = {}
|
|
for i=1,count do
|
|
local entry = {
|
|
readers["spawnlist"](stream),
|
|
stream:ReadInt24(),
|
|
stream:ReadShortString(),
|
|
stream:ReadInt24(),
|
|
readers["reflist"](stream)
|
|
}
|
|
local condCount = stream:ReadByte()
|
|
if condCount > 0 then
|
|
local conds = {}
|
|
for _=1,condCount do
|
|
local k = stream:ReadShortString()
|
|
conds[k] = stream:ReadInt24()
|
|
end
|
|
entry[6] = conds
|
|
end
|
|
ret[i] = entry
|
|
end
|
|
return ret
|
|
end
|
|
return nil
|
|
end
|
|
readers["waypointlist"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
if count == 0 then return nil end
|
|
|
|
local waypointlist = {}
|
|
for _ = 1, count do
|
|
local lists = {}
|
|
local zone = stream:ReadShort()
|
|
local listCount = stream:ReadByte()
|
|
for j = 1, listCount do
|
|
local spawnCount = stream:ReadShort()
|
|
local list = {}
|
|
for i = 1, spawnCount do
|
|
local x, y = stream:ReadInt12Pair()
|
|
if x == 0 and y == 0 then
|
|
list[i] = {-1, -1}
|
|
else
|
|
list[i] = {x / 40.90, y / 40.90}
|
|
end
|
|
end
|
|
lists[j] = list
|
|
end
|
|
waypointlist[zone] = lists
|
|
end
|
|
return waypointlist
|
|
end
|
|
|
|
QuestieDBCompiler.readers = readers
|
|
|
|
QuestieDBCompiler.writers = {
|
|
["s8"] = function(stream, value)
|
|
stream:WriteByte((value or 0)+127)
|
|
end,
|
|
["u8"] = function(stream, value)
|
|
stream:WriteByte(value or 0)
|
|
end,
|
|
["u16"] = function(stream, value)
|
|
stream:WriteShort(value or 0)
|
|
end,
|
|
["s16"] = function(stream, value)
|
|
stream:WriteShort(32767 + (value or 0))
|
|
end,
|
|
["u24"] = function(stream, value)
|
|
stream:WriteInt24(value or 0)
|
|
end,
|
|
["s24"] = function(stream, value)
|
|
stream:WriteInt24(8388607 + (value or 0))
|
|
end,
|
|
["u32"] = function(stream, value)
|
|
stream:WriteInt(value or 0)
|
|
end,
|
|
["u12pair"] = function(stream, value)
|
|
if value then
|
|
stream:WriteInt12Pair(value[1] or 0, value[2] or 0)
|
|
else
|
|
stream:WriteInt24(0)
|
|
end
|
|
end,
|
|
["u24pair"] = function(stream, value)
|
|
if value then
|
|
stream:WriteInt24(value[1] or 0)
|
|
stream:WriteInt24(value[2] or 0)
|
|
else
|
|
stream:WriteInt24(0)
|
|
stream:WriteInt24(0)
|
|
end
|
|
end,
|
|
["s24pair"] = function(stream, value)
|
|
if value then
|
|
stream:WriteInt24((value[1] or 0) + 8388608)
|
|
stream:WriteInt24((value[2] or 0) + 8388608)
|
|
else
|
|
stream:WriteInt24(8388608)
|
|
stream:WriteInt24(8388608)
|
|
end
|
|
end,
|
|
["u8string"] = function(stream, value)
|
|
|
|
stream:WriteTinyString(value or "nil") -- I hate this but we need to support both nil strings and empty strings
|
|
|
|
--if value then
|
|
-- stream:WriteTinyString(value)
|
|
--else
|
|
-- stream:WriteByte(0)
|
|
--end
|
|
end,
|
|
["u16string"] = function(stream, value)
|
|
--if value then
|
|
-- stream:WriteShortString(value)
|
|
--else
|
|
-- stream:WriteShort(0)
|
|
--end
|
|
stream:WriteShortString(value or "nil")
|
|
end,
|
|
["u8u16array"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteShort(v)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["u8s16pairs"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteShort((v[1] or 0) + 32767)
|
|
stream:WriteShort((v[2] or 0) + 32767)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["u16u16array"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteShort(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteShort(v)
|
|
end
|
|
else
|
|
stream:WriteShort(0)
|
|
end
|
|
end,
|
|
["u8s24pairs"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteInt24((v[1] or 0) + 8388608)
|
|
stream:WriteInt24((v[2] or 0) + 8388608)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["u8u24array"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteInt24(v)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["u16u24array"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteShort(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteInt24(v)
|
|
end
|
|
else
|
|
stream:WriteShort(0)
|
|
end
|
|
end,
|
|
["u8u16stringarray"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _,v in pairs(value) do
|
|
stream:WriteShortString(v or "nil")
|
|
end
|
|
else
|
|
--print("Missing u8u16stringarray for " .. QuestieDBCompiler.currentEntry)
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["faction"] = function(stream, value)
|
|
if value == nil then
|
|
stream:WriteByte(3)
|
|
elseif "A" == value then
|
|
stream:WriteByte(0)
|
|
elseif "H" == value then
|
|
stream:WriteByte(1)
|
|
else
|
|
stream:WriteByte(2)
|
|
end
|
|
end,
|
|
["spawnlist"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for zone, spawnlist in pairs(value) do
|
|
count = 0 for _ in pairs(spawnlist) do count = count + 1 end
|
|
stream:WriteShort(zone)
|
|
stream:WriteShort(count)
|
|
for _, spawn in pairs(spawnlist) do
|
|
if spawn[1] == -1 and spawn[2] == -1 then -- instance spawn
|
|
stream:WriteInt24(0) -- 0 instead
|
|
else
|
|
stream:WriteInt12Pair(floor(spawn[1] * 40.90), floor(spawn[2] * 40.90))
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--print("Missing spawnlist for " .. QuestieDBCompiler.currentEntry)
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["trigger"] = function(stream, value)
|
|
if value then
|
|
stream:WriteTinyString(value[1])
|
|
QuestieDBCompiler.writers["spawnlist"](stream, value[2])
|
|
else
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["questgivers"] = function(stream, value)
|
|
if value then
|
|
QuestieDBCompiler.writers["u8u24array"](stream, value[1])
|
|
QuestieDBCompiler.writers["u8u24array"](stream, value[2])
|
|
QuestieDBCompiler.writers["u8u24array"](stream, value[3])
|
|
else
|
|
--print("Missing questgivers for " .. QuestieDBCompiler.currentEntry) TODO: Reintroduce this check in any form
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["objective"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _, pair in pairs(value) do
|
|
stream:WriteInt24(pair[1])
|
|
stream:WriteTinyString(pair[2] or "")
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["spellobjective"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _, data in pairs(value) do
|
|
stream:WriteInt24(data[1])
|
|
stream:WriteTinyString(data[2] or "")
|
|
stream:WriteInt24(data[3] or 0)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["objectives"] = function(stream, value)
|
|
if value then
|
|
QuestieDBCompiler.writers["objective"](stream, value[1])
|
|
QuestieDBCompiler.writers["objective"](stream, value[2])
|
|
QuestieDBCompiler.writers["objective"](stream, value[3])
|
|
QuestieDBCompiler.writers["u24pair"](stream, value[4])
|
|
|
|
local killobjectives = value[5]
|
|
if type(killobjectives) == "table" and #killobjectives > 0 then
|
|
stream:WriteByte(#killobjectives)
|
|
for i=1, #killobjectives do -- iterate over all killobjectives
|
|
local killobjective = killobjectives[i]
|
|
local npcIds = killobjective[1]
|
|
assert(type(npcIds) == "table", "killobjective's npcids is not a table.")
|
|
assert(#npcIds > 0, "killOojective has 0 npcIDs.")
|
|
stream:WriteByte(#npcIds) -- write count of creatureIDs
|
|
for j=1, #npcIds do
|
|
stream:WriteInt24(npcIds[j]) -- write creatureID
|
|
end
|
|
stream:WriteInt24(killobjective[2]) -- write baseCreatureID
|
|
stream:WriteTinyString(killobjective[3] or "") -- write baseCreatureText
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
|
|
QuestieDBCompiler.writers["spellobjective"](stream, value[6])
|
|
else
|
|
--print("Missing objective table for " .. QuestieDBCompiler.currentEntry)
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
stream:WriteInt24(0)
|
|
stream:WriteInt24(0)
|
|
stream:WriteByte(0)
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["reflist"] = function(stream, value)
|
|
stream:WriteByte(#value)
|
|
for _, v in pairs(value) do
|
|
stream:WriteByte(QuestieDBCompiler.refTypesReversed[v[1]])
|
|
stream:WriteInt24(v[2])
|
|
end
|
|
end,
|
|
["extraobjectives"] = function(stream, value)
|
|
if value then
|
|
stream:WriteByte(#value)
|
|
for _, data in pairs(value) do
|
|
QuestieDBCompiler.writers["spawnlist"](stream, data[1])
|
|
stream:WriteInt24(data[2]) -- icon
|
|
stream:WriteShortString(data[3]) -- description
|
|
stream:WriteInt24(data[4] or 0) -- objective index (or 0)
|
|
QuestieDBCompiler.writers["reflist"](stream, data[5] or {})
|
|
local conds = data[6]
|
|
if type(conds) == "table" then
|
|
local n = 0
|
|
for _ in pairs(conds) do n = n + 1 end
|
|
stream:WriteByte(n)
|
|
for k, v in pairs(conds) do
|
|
stream:WriteShortString(k)
|
|
stream:WriteInt24(v)
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end
|
|
else
|
|
stream:WriteByte(0)
|
|
end
|
|
end,
|
|
["waypointlist"] = function(stream, value)
|
|
if value then
|
|
local count = 0 for _ in pairs(value) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for zone, spawnlists in pairs(value) do
|
|
stream:WriteShort(zone)
|
|
count = 0 for _ in pairs(spawnlists) do count = count + 1 end
|
|
stream:WriteByte(count)
|
|
for _, spawnlist in pairs(spawnlists) do
|
|
count = 0 for _ in pairs(spawnlist) do count = count + 1 end
|
|
stream:WriteShort(count)
|
|
for _, spawn in pairs(spawnlist) do
|
|
if spawn[1] == -1 and spawn[2] == -1 then -- instance spawn
|
|
stream:WriteInt24(0) -- 0 instead
|
|
else
|
|
stream:WriteInt12Pair(floor(spawn[1] * 40.90), floor(spawn[2] * 40.90))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--print("Missing spawnlist for " .. QuestieDBCompiler.currentEntry)
|
|
stream:WriteByte(0)
|
|
end
|
|
end
|
|
}
|
|
|
|
skippers["s8"] = function(stream) stream._pointer = stream._pointer + 1 end
|
|
skippers["u8"] = function(stream) stream._pointer = stream._pointer + 1 end
|
|
skippers["u16"] = function(stream) stream._pointer = stream._pointer + 2 end
|
|
skippers["s16"] = function(stream) stream._pointer = stream._pointer + 2 end
|
|
skippers["u24"] = function(stream) stream._pointer = stream._pointer + 3 end
|
|
skippers["s24"] = function(stream) stream._pointer = stream._pointer + 3 end
|
|
skippers["u32"] = function(stream) stream._pointer = stream._pointer + 4 end
|
|
skippers["u12pair"] = function(stream) stream._pointer = stream._pointer + 3 end
|
|
skippers["u24pair"] = function(stream) stream._pointer = stream._pointer + 6 end
|
|
skippers["s24pair"] = function(stream) stream._pointer = stream._pointer + 6 end
|
|
skippers["u8string"] = function(stream) stream._pointer = stream:ReadByte() + stream._pointer end
|
|
skippers["u16string"] = function(stream) stream._pointer = stream:ReadShort() + stream._pointer end
|
|
skippers["u8u16array"] = function(stream) stream._pointer = stream:ReadByte() * 2 + stream._pointer end
|
|
skippers["u8s16pairs"] = function(stream) stream._pointer = stream:ReadByte() * 4 + stream._pointer end
|
|
skippers["u16u16array"] = function(stream) stream._pointer = stream:ReadShort() * 2 + stream._pointer end
|
|
skippers["u8s24pairs"] = function(stream) stream._pointer = stream:ReadByte() * 6 + stream._pointer end
|
|
skippers["u8u24array"] = function(stream) stream._pointer = stream:ReadByte() * 3 + stream._pointer end
|
|
skippers["u16u24array"] = function(stream) stream._pointer = stream:ReadShort() * 3 + stream._pointer end
|
|
skippers["waypointlist"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _ = 1, count do
|
|
stream._pointer = stream._pointer + 2
|
|
local listCount = stream:ReadByte()
|
|
for _ = 1, listCount do
|
|
stream._pointer = stream:ReadShort() * 3 + stream._pointer
|
|
end
|
|
end
|
|
end
|
|
skippers["u8u16stringarray"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _=1,count do
|
|
stream._pointer = stream:ReadShort() + stream._pointer
|
|
end
|
|
end
|
|
skippers["faction"] = function(stream) stream._pointer = stream._pointer + 1 end
|
|
skippers["spawnlist"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _ = 1, count do
|
|
stream._pointer = stream._pointer + 2
|
|
stream._pointer = stream:ReadShort() * 3 + stream._pointer
|
|
end
|
|
end
|
|
local spawnlistSkipper = skippers["spawnlist"]
|
|
skippers["trigger"] = function(stream)
|
|
stream._pointer = stream:ReadByte() + stream._pointer
|
|
spawnlistSkipper(stream)
|
|
end
|
|
local u8u24arraySkipper = skippers["u8u24array"]
|
|
skippers["questgivers"] = function(stream)
|
|
u8u24arraySkipper(stream)
|
|
u8u24arraySkipper(stream)
|
|
u8u24arraySkipper(stream)
|
|
end
|
|
skippers["objective"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _=1,count do
|
|
stream._pointer = stream._pointer + 3
|
|
stream._pointer = stream:ReadByte() + stream._pointer
|
|
end
|
|
end
|
|
skippers["spellobjective"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _=1,count do
|
|
stream._pointer = stream._pointer + 3
|
|
stream._pointer = stream:ReadByte() + stream._pointer
|
|
stream._pointer = stream._pointer + 3
|
|
end
|
|
end
|
|
local objectiveSkipper = skippers["objective"]
|
|
local spellObjectiveSkipper = skippers["spellobjective"]
|
|
local u24pairSkipper = skippers["u24pair"]
|
|
skippers["objectives"] = function(stream)
|
|
objectiveSkipper(stream)
|
|
objectiveSkipper(stream)
|
|
objectiveSkipper(stream)
|
|
u24pairSkipper(stream)
|
|
local count = stream:ReadByte() -- killobjectives
|
|
if count > 0 then
|
|
for _=1, count do
|
|
stream._pointer = stream:ReadByte() * 3 + 3 + stream._pointer
|
|
stream._pointer = stream:ReadByte() + stream._pointer
|
|
end
|
|
end
|
|
spellObjectiveSkipper(stream)
|
|
end
|
|
skippers["reflist"] = function(stream)
|
|
stream._pointer = stream:ReadByte() * 4 + stream._pointer
|
|
end
|
|
local reflistSkipper = skippers["reflist"]
|
|
skippers["extraobjectives"] = function(stream)
|
|
local count = stream:ReadByte()
|
|
for _=1,count do
|
|
spawnlistSkipper(stream)
|
|
stream._pointer = stream._pointer + 3
|
|
stream._pointer = stream:ReadShort() + stream._pointer
|
|
stream._pointer = stream._pointer + 3
|
|
reflistSkipper(stream)
|
|
local condCount = stream:ReadByte()
|
|
for _=1,condCount do
|
|
stream._pointer = stream:ReadShort() + stream._pointer -- skip ShortString key
|
|
stream._pointer = stream._pointer + 3 -- skip Int24 value
|
|
end
|
|
end
|
|
end
|
|
|
|
QuestieDBCompiler.skippers = skippers
|
|
|
|
QuestieDBCompiler.dynamics = {
|
|
["u8string"] = true,
|
|
["u16string"] = true,
|
|
["u8u16array"] = true,
|
|
["u8s16pairs"] = true,
|
|
["u16u16array"] = true,
|
|
["u8s24pairs"] = true,
|
|
["u8u24array"] = true,
|
|
["u16u24array"] = true,
|
|
["u8u16stringarray"] = true,
|
|
["spawnlist"] = true,
|
|
["trigger"] = true,
|
|
["objective"] = true,
|
|
["objectives"] = true,
|
|
["questgivers"] = true,
|
|
["waypointlist"] = true,
|
|
["extraobjectives"] = true,
|
|
["reflist"] = true
|
|
}
|
|
|
|
QuestieDBCompiler.statics = {
|
|
["u8"] = 1,
|
|
["s8"] = 1,
|
|
["u16"] = 2,
|
|
["s16"] = 2,
|
|
["u24"] = 3,
|
|
["s24"] = 3,
|
|
["u32"] = 4,
|
|
["faction"] = 1,
|
|
["u12pair"] = 3,
|
|
["u24pair"] = 6,
|
|
["s24pair"] = 6,
|
|
}
|
|
|
|
function QuestieDBCompiler:CompileNPCs()
|
|
QuestieDBCompiler:CompileTableCoroutine(QuestieDB.npcData, QuestieDB.npcCompilerTypes, QuestieDB.npcCompilerOrder, QuestieDB.npcKeys, "npc", "NPC")
|
|
end
|
|
|
|
function QuestieDBCompiler:CompileObjects()
|
|
QuestieDBCompiler:CompileTableCoroutine(QuestieDB.objectData, QuestieDB.objectCompilerTypes, QuestieDB.objectCompilerOrder, QuestieDB.objectKeys, "obj", "Object")
|
|
end
|
|
|
|
function QuestieDBCompiler:CompileQuests()
|
|
QuestieDBCompiler:CompileTableCoroutine(QuestieDB.questData, QuestieDB.questCompilerTypes, QuestieDB.questCompilerOrder, QuestieDB.questKeys, "quest", "Quest", 28)
|
|
end
|
|
|
|
function QuestieDBCompiler:CompileItems()
|
|
QuestieDBCompiler:CompileTableCoroutine(QuestieDB.itemData, QuestieDB.itemCompilerTypes, QuestieDB.itemCompilerOrder, QuestieDB.itemKeys, "item", "Item", 128)
|
|
end
|
|
|
|
local function equals(a, b)
|
|
if a == nil and b == nil then return true end
|
|
if a == nil or b == nil then return false end
|
|
local ta = type(a)
|
|
local tb = type(b)
|
|
if ta ~= tb then return false end
|
|
|
|
if ta == "number" then
|
|
return abs(a-b) < 0.2 -- 100/4096 is the precision of the data (From Aero)
|
|
elseif ta == "table" then
|
|
for k,v in pairs(a) do
|
|
if not equals(b[k], v) then
|
|
return false
|
|
end
|
|
end
|
|
for k,v in pairs(b) do
|
|
if not equals(a[k], v) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
else
|
|
return a == b
|
|
end
|
|
|
|
end
|
|
|
|
function QuestieDBCompiler:EncodePointerMap(stream, pointerMap)
|
|
stream:reset()
|
|
stream:WriteShort(0) -- placeholder
|
|
local count = 0
|
|
for id, ptrs in pairs(pointerMap) do
|
|
stream:WriteInt24(id)
|
|
stream:WriteInt24(ptrs)
|
|
count = count + 1
|
|
end
|
|
stream._pointer = 1
|
|
stream:WriteShort(count)
|
|
local result = stream:Save()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[EncodePointerMap] Encoded", count, "entries, result length:", #result)
|
|
return result
|
|
end
|
|
|
|
function QuestieDBCompiler:DecodePointerMap(stream)
|
|
-- Defensive check: ensure the stream has data before reading
|
|
local bin = stream._bin
|
|
if not bin or #bin < 2 then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DecodePointerMap] Warning: empty or corrupted pointer map, returning empty table")
|
|
return {}
|
|
end
|
|
local count = stream:ReadShort()
|
|
local ret = {}
|
|
local i = 0
|
|
while i < count do
|
|
for _ = 1, min(768, count-i) do -- steps per yield
|
|
ret[stream:ReadInt24()] = stream:ReadInt24()
|
|
end
|
|
i = i + 768
|
|
safeYield()
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function QuestieDBCompiler:CompileTable(tbl, types, order, lookup)
|
|
local pointerMap = {}
|
|
local stream = QuestieStream:GetStream("raw")
|
|
for id, entry in pairs(tbl) do
|
|
pointerMap[id] = stream._pointer
|
|
for _, key in pairs(order) do
|
|
QuestieDBCompiler.writers[types[key]](stream, entry[lookup[key]])
|
|
end
|
|
end
|
|
return stream:Save(), QuestieDBCompiler:EncodePointerMap(stream, pointerMap)
|
|
end
|
|
|
|
function QuestieDBCompiler:CompileTableCoroutine(tbl, types, order, lookup, databaseKey, kind, entriesPerTick)
|
|
local count = 0
|
|
local indexLookup = {};
|
|
|
|
local max_id = 0
|
|
for id in pairs(tbl) do
|
|
assert(type(id) == "number", "CompileTableCoroutine: tbl id is not a number")
|
|
if id > max_id then
|
|
max_id = id
|
|
end
|
|
end
|
|
-- iterate table tbl in numerical order to get ids in order to indexLoopup list. iterating over pairs(tbl) gives ids in non determined order
|
|
for id=0,max_id do
|
|
if tbl[id] then
|
|
count = count + 1
|
|
indexLookup[count] = id
|
|
end
|
|
end
|
|
count = count + 1
|
|
|
|
local index = 0
|
|
|
|
local pointerMap = {}
|
|
local stream = Questie.db.profile.debugEnabled and QuestieStream:GetStream("raw_assert") or QuestieStream:GetStream("raw")
|
|
|
|
-- Localize functions
|
|
local pcall, type = pcall, type
|
|
local writers = QuestieDBCompiler.writers
|
|
local supportedTypes = QuestieDBCompiler.supportedTypes
|
|
|
|
while true do
|
|
safeYield()
|
|
for _=0,Questie.db.profile.debugEnabled and TICKS_PER_YIELD_DEBUG or (entriesPerTick or TICKS_PER_YIELD) do
|
|
index = index + 1
|
|
if index == count then
|
|
local binaryBucket = Questie.dbCache and Questie.dbCache.global or Questie.db.global
|
|
if Questie.IsSoD then
|
|
binaryBucket.sod[databaseKey.."Bin"] = stream:Save()
|
|
binaryBucket.sod[databaseKey.."Ptrs"] = QuestieDBCompiler:EncodePointerMap(stream, pointerMap)
|
|
else
|
|
binaryBucket[databaseKey.."Bin"] = stream:Save()
|
|
binaryBucket[databaseKey.."Ptrs"] = QuestieDBCompiler:EncodePointerMap(stream, pointerMap)
|
|
end
|
|
stream:finished() -- relief memory pressure
|
|
return
|
|
end
|
|
local id = indexLookup[index]
|
|
|
|
-- QuestieDBCompiler.currentEntry = id -- This was only ever used for debugging.
|
|
local entry = tbl[id]
|
|
|
|
pointerMap[id] = stream._pointer--pointerStart
|
|
for i=1, #order do
|
|
local key = order[i]
|
|
local v = entry[lookup[key]]
|
|
local t = types[key]
|
|
|
|
if v and not supportedTypes[type(v)][t] then
|
|
Questie:Error("|cFFFF0000Invalid datatype!|r " .. kind .. "s[" .. tostring(id) .. "]."..key..": \"" .. type(v) .. "\" is not compatible with type \"" .. t .."\"")
|
|
return
|
|
end
|
|
if not writers[t] then
|
|
Questie:Error("Invalid datatype: " .. key .. " " .. tostring(t))
|
|
end
|
|
--print(key .. "s[" .. tostring(id) .. "]."..key..": \"" .. type(v) .. "\"")
|
|
local result, errorMessage = pcall(writers[t], stream, v)
|
|
if not result then
|
|
Questie:Error("There was an error when compiling data for "..kind.." " .. tostring(id) .. " \""..tostring(key).."\":")
|
|
Questie:Error(errorMessage)
|
|
error(errorMessage)
|
|
end
|
|
end
|
|
-- tbl[id] = nil -- quicker gabage collection later
|
|
end
|
|
end
|
|
--I do not know if these assignments are actually needed but I am too scared to remove them
|
|
-- QuestieDBCompiler.pointerMap = pointerMap
|
|
-- QuestieDBCompiler.stream = stream
|
|
-- QuestieDBCompiler.index = index
|
|
|
|
end
|
|
|
|
function QuestieDBCompiler:BuildSkipMap(types, order) -- skip map is used for random access, to read specific fields in an entry without reading the whole entry
|
|
local skipmap = {}
|
|
local indexToKey = {}
|
|
local keyToIndex = {}
|
|
local ptr = 0
|
|
local haveDynamic = false
|
|
local lastIndex
|
|
for index = 1, #order do
|
|
local key = order[index]
|
|
local typ = types[key]
|
|
indexToKey[index] = key
|
|
keyToIndex[key] = index
|
|
if not haveDynamic then
|
|
skipmap[key] = ptr
|
|
end
|
|
if QuestieDBCompiler.dynamics[typ] then
|
|
if not haveDynamic then
|
|
lastIndex = index
|
|
end
|
|
haveDynamic = true
|
|
else
|
|
--print("static: " .. typ)
|
|
ptr = ptr + QuestieDBCompiler.statics[typ]
|
|
end
|
|
end
|
|
skipmap = {skipmap, lastIndex, ptr, types, order, indexToKey, keyToIndex}
|
|
return skipmap
|
|
end
|
|
|
|
function QuestieDBCompiler:Compile()
|
|
if QuestieDBCompiler._isCompiling then
|
|
return
|
|
end
|
|
|
|
local hasData = type(QuestieDB.questData) == "string"
|
|
or type(QuestieDB.questData) == "table"
|
|
or (type(QuestieDB.questDataOverrides) == "table" and next(QuestieDB.questDataOverrides) ~= nil)
|
|
if not hasData then
|
|
Questie:Print("|cFFFF4444[Questie-X]|r No database plugin loaded. Install a DB plugin for your server — see Options ⌂ Database.")
|
|
return
|
|
end
|
|
|
|
QuestieDBCompiler._isCompiling = true -- some unknown addon that is popular in china causes player_logged_in event to fire many times which triggers db compile multiple times
|
|
|
|
QuestieDBCompiler.startTime = GetTime()
|
|
QuestieDBCompiler.totalSize = 0
|
|
|
|
print("\124cFF4DDBFF [6/9] " .. l10n("Updating NPCs") .. "...")
|
|
QuestieDBCompiler:CompileNPCs()
|
|
print("\124cFF4DDBFF [7/9] " .. l10n("Updating objects") .. "...")
|
|
QuestieDBCompiler:CompileObjects()
|
|
print("\124cFF4DDBFF [8/9] " .. l10n("Updating quests") .. "...")
|
|
QuestieDBCompiler:CompileQuests()
|
|
print("\124cFF4DDBFF [9/9] " .. l10n("Updating items") .. "...")
|
|
QuestieDBCompiler:CompileItems()
|
|
print("\124cFFAAEEFF"..l10n("Questie DB update complete!"))
|
|
|
|
Questie.db.global.dbCompiledExpansion = WOW_PROJECT_ID
|
|
|
|
if Questie.IsSoD then
|
|
Questie.db.global.sod.dbCompiledOnVersion = QuestieLib:GetAddonVersionString()
|
|
Questie.db.global.sod.dbCompiledLang = l10n:GetUILocale()
|
|
Questie.db.global.sod.dbIsCompiled = true
|
|
Questie.db.global.sod.dbCompiledCount = (Questie.db.global.sod.dbCompiledCount or 0) + 1
|
|
else
|
|
Questie.db.global.dbCompiledOnVersion = QuestieLib:GetAddonVersionString()
|
|
Questie.db.global.dbCompiledLang = l10n:GetUILocale()
|
|
Questie.db.global.dbIsCompiled = true
|
|
Questie.db.global.dbCompiledCount = (Questie.db.global.dbCompiledCount or 0) + 1
|
|
end
|
|
end
|
|
|
|
function QuestieDBCompiler:ValidateNPCs()
|
|
local npcBin, npcPtrs
|
|
local binaryBucket = Questie.dbCache and Questie.dbCache.global or Questie.db.global
|
|
if Questie.IsSoD then
|
|
binaryBucket = binaryBucket.sod or {}
|
|
npcBin = binaryBucket.npcBin
|
|
npcPtrs = binaryBucket.npcPtrs
|
|
else
|
|
npcBin = binaryBucket.npcBin
|
|
npcPtrs = binaryBucket.npcPtrs
|
|
end
|
|
local validator = QuestieDBCompiler:GetDBHandle(npcBin, npcPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.npcCompilerTypes, QuestieDB.npcCompilerOrder))
|
|
|
|
local count = 0
|
|
for npcId, nonCompiledData in pairs(QuestieDB.npcData) do
|
|
local compiledData = validator.QueryValidator(npcId, QuestieDB.npcCompilerOrder)
|
|
|
|
for id, key in pairs(QuestieDB.npcCompilerOrder) do
|
|
local a = compiledData[id]
|
|
local b = nonCompiledData[QuestieDB.npcKeys[key]]
|
|
|
|
if type(a) == "number" and abs(a-(b or 0)) > 0.2 then
|
|
Questie:Warning("Nonmatching number at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. npcId)
|
|
return
|
|
elseif type(a) == "string" and a ~= (b or "") then
|
|
Questie:Warning("Nonmatching string at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. npcId)
|
|
return
|
|
elseif type(a) == "table" then
|
|
if not equals(a, (b or {})) then
|
|
Questie:Warning("Nonmatching table at " .. key .. " " .. id .. " for ID: ".. npcId)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if count == TICKS_PER_YIELD_DEBUG then
|
|
count = 0
|
|
safeYield()
|
|
end
|
|
count = count + 1
|
|
end
|
|
|
|
validator.stream:finished()
|
|
Questie:Debug(Questie.DEBUG_INFO, "Finished NPCs validation without issues!")
|
|
end
|
|
|
|
function QuestieDBCompiler:ValidateObjects()
|
|
local objBin, objPtrs
|
|
if Questie.IsSoD then
|
|
objBin = Questie.db.global.sod.objBin
|
|
objPtrs = Questie.db.global.sod.objPtrs
|
|
else
|
|
objBin = Questie.db.global.objBin
|
|
objPtrs = Questie.db.global.objPtrs
|
|
end
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[ValidateObjects] objBin type:", type(objBin), "objPtrs type:", type(objPtrs), "objPtrs length:", objPtrs and #objPtrs or "nil")
|
|
-- Skip validation if compiled data is missing (DB was cached without proper compilation)
|
|
if not objBin or not objPtrs or #objPtrs < 2 then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[ValidateObjects] Skipping - compiled object data not available (likely cached from incomplete previous session)")
|
|
return
|
|
end
|
|
local validator = QuestieDBCompiler:GetDBHandle(objBin, objPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.objectCompilerTypes, QuestieDB.objectCompilerOrder))
|
|
|
|
local count = 0
|
|
for objectId, nonCompiledData in pairs(QuestieDB.objectData) do
|
|
local compiledData = validator.QueryValidator(objectId, QuestieDB.objectCompilerOrder)
|
|
|
|
for id, key in pairs(QuestieDB.objectCompilerOrder) do
|
|
local a = compiledData[id]
|
|
local b = nonCompiledData[QuestieDB.objectKeys[key]]
|
|
|
|
if type(a) == "number" and abs(a-(b or 0)) > 0.2 then
|
|
Questie:Warning("Nonmatching number at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. objectId)
|
|
return
|
|
elseif type(a) == "string" and a ~= (b or "") then
|
|
Questie:Warning("Nonmatching string at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. objectId)
|
|
return
|
|
elseif type(a) == "table" then
|
|
if not equals(a, (b or {})) then
|
|
Questie:Warning("Nonmatching table at " .. key .. " " .. id .. " for ID: ".. objectId)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if count == TICKS_PER_YIELD_DEBUG then
|
|
count = 0
|
|
safeYield()
|
|
end
|
|
count = count + 1
|
|
end
|
|
|
|
validator.stream:finished()
|
|
Questie:Debug(Questie.DEBUG_INFO, "Finished objects validation without issues!")
|
|
end
|
|
|
|
|
|
function QuestieDBCompiler:ValidateItems()
|
|
local itemBin, objBin, npcBin, objPtrs, itemPtrs, npcPtrs
|
|
local binaryBucket = Questie.dbCache and Questie.dbCache.global or Questie.db.global
|
|
if Questie.IsSoD then
|
|
binaryBucket = binaryBucket.sod or {}
|
|
itemBin = binaryBucket.itemBin
|
|
itemPtrs = binaryBucket.itemPtrs
|
|
objBin = binaryBucket.objBin
|
|
objPtrs = binaryBucket.objPtrs
|
|
npcBin = binaryBucket.npcBin
|
|
npcPtrs = binaryBucket.npcPtrs
|
|
else
|
|
itemBin = binaryBucket.itemBin
|
|
itemPtrs = binaryBucket.itemPtrs
|
|
objBin = binaryBucket.objBin
|
|
objPtrs = binaryBucket.objPtrs
|
|
npcBin = binaryBucket.npcBin
|
|
npcPtrs = binaryBucket.npcPtrs
|
|
end
|
|
|
|
local validator = QuestieDBCompiler:GetDBHandle(itemBin, itemPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.itemCompilerTypes, QuestieDB.itemCompilerOrder))
|
|
local obj = QuestieDBCompiler:GetDBHandle(objBin, objPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.objectCompilerTypes, QuestieDB.objectCompilerOrder))
|
|
local npc = QuestieDBCompiler:GetDBHandle(npcBin, npcPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.npcCompilerTypes, QuestieDB.npcCompilerOrder))
|
|
|
|
local count = 0
|
|
for id, _ in pairs(validator.pointers) do
|
|
local objDrops, npcDrops = validator.QuerySingle(id, "objectDrops"), validator.QuerySingle(id, "npcDrops")
|
|
if objDrops then -- validate object drops
|
|
--print("Validating objs")
|
|
for _, oid in pairs(objDrops) do
|
|
if not obj.QuerySingle(oid, "name") then
|
|
Questie:Error("Missing object " .. tostring(oid) .. " that drops " .. (validator.QuerySingle(id, "name") or "Missing item name!") .. " " .. tostring(id))
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if npcDrops then -- validate npc drops
|
|
for _, nid in pairs(npcDrops) do
|
|
--print("Validating npcs")
|
|
if not npc.QuerySingle(nid, "name") then
|
|
Questie:Error("Missing npc " .. tostring(nid))
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- todo fix this test
|
|
--if false then
|
|
-- local cnt = 0 for _ in pairs(toValidate) do cnt = cnt + 1 end
|
|
-- print("toValidate length: " .. cnt)
|
|
-- --Questie.db.global.__toValidate = toValidate
|
|
-- local validData = QuestieDB:GetItem(id)
|
|
-- for id,key in pairs(QuestieDB.itemCompilerOrder) do
|
|
-- local a = toValidate[id]
|
|
-- local b = validData[key]
|
|
--
|
|
-- if type(a) == "number" and abs(a-(b or 0)) > 0.2 then
|
|
-- print("Nonmatching at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b))
|
|
-- return
|
|
-- elseif type(a) == "string" and a ~= (b or "") then
|
|
-- print("Nonmatching at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b))
|
|
-- return
|
|
-- elseif type(a) == "table" then
|
|
-- if not equals(a, (b or {})) then
|
|
-- print("Nonmatching at " .. key .. " " .. id)
|
|
-- --__nma = a
|
|
-- --__nmb = b or {}
|
|
-- return
|
|
-- end
|
|
-- else
|
|
-- print("MATCHING: " .. key)
|
|
-- end
|
|
-- end
|
|
--end
|
|
if count == TICKS_PER_YIELD_DEBUG then
|
|
count = 0
|
|
safeYield()
|
|
end
|
|
count = count + 1
|
|
end
|
|
count = 0
|
|
for itemId, nonCompiledData in pairs(QuestieDB.itemData) do
|
|
local compiledData = validator.QueryValidator(itemId, QuestieDB.itemCompilerOrder)
|
|
|
|
for id, key in pairs(QuestieDB.itemCompilerOrder) do
|
|
local a = compiledData[id]
|
|
local b = nonCompiledData[QuestieDB.itemKeys[key]]
|
|
|
|
if type(a) == "number" and abs(a-(b or 0)) > 0.2 then
|
|
Questie:Warning("Nonmatching number at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. itemId)
|
|
return
|
|
elseif type(a) == "string" and a ~= (b or "") then
|
|
Questie:Warning("Nonmatching string at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. itemId)
|
|
return
|
|
elseif type(a) == "table" then
|
|
if not equals(a, (b or {})) then
|
|
Questie:Warning("Nonmatching table at " .. key .. " " .. id .. " for ID: ".. itemId)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if count == TICKS_PER_YIELD_DEBUG then
|
|
count = 0
|
|
safeYield()
|
|
end
|
|
count = count + 1
|
|
end
|
|
|
|
validator.stream:finished()
|
|
obj.stream:finished()
|
|
npc.stream:finished()
|
|
Questie:Debug(Questie.DEBUG_INFO, "Finished items validation without issues!")
|
|
end
|
|
|
|
function QuestieDBCompiler:ValidateQuests()
|
|
local questBin, questPtrs
|
|
if Questie.IsSoD then
|
|
questBin = Questie.db.global.sod.questBin
|
|
questPtrs = Questie.db.global.sod.questPtrs
|
|
else
|
|
questBin = Questie.db.global.questBin
|
|
questPtrs = Questie.db.global.questPtrs
|
|
end
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[ValidateQuests] questBin type:", type(questBin), "questPtrs type:", type(questPtrs), "questPtrs length:", questPtrs and #questPtrs or "nil")
|
|
-- Skip validation if compiled data is missing (DB was cached without proper compilation)
|
|
if not questBin or not questPtrs or #questPtrs < 2 then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[ValidateQuests] Skipping - compiled quest data not available (likely cached from incomplete previous session)")
|
|
return
|
|
end
|
|
local validator = QuestieDBCompiler:GetDBHandle(questBin, questPtrs, QuestieDBCompiler:BuildSkipMap(QuestieDB.questCompilerTypes, QuestieDB.questCompilerOrder))
|
|
|
|
local playerLevel = UnitLevel("player")
|
|
|
|
--! IF YOU SEE THIS FUNCTION REMOVE IT AND CHANGE IT TO QUESTIELIB FUNCTION INSTEAD
|
|
local function getTbcLevel(questLevel, requiredLevel, playerLevel)
|
|
if (questLevel == -1) then
|
|
local level = playerLevel
|
|
if (requiredLevel and requiredLevel > level) then
|
|
questLevel = requiredLevel;
|
|
else
|
|
questLevel = level;
|
|
-- We also set the requiredLevel to the player level so the quest is not hidden without "show low level quests"
|
|
requiredLevel = level;
|
|
end
|
|
end
|
|
return questLevel, requiredLevel
|
|
end
|
|
|
|
-- We now only compare the nonCompiled data and the compiled data without overrides, it'll have to do.
|
|
local count = 0
|
|
for questId, nonCompiledData in pairs(QuestieDB.questData) do
|
|
local compiledData = validator.QueryValidator(questId, QuestieDB.questCompilerOrder)
|
|
|
|
for id,key in pairs(QuestieDB.questCompilerOrder) do
|
|
local a = compiledData[id]
|
|
local b = nonCompiledData[QuestieDB.questKeys[key]]
|
|
|
|
--Special case for questLevel
|
|
if (Questie.IsTBC or Questie.IsWotlk) and (key == "questLevel" or key == "requiredLevel") then
|
|
local questLevel, requiredLevel = getTbcLevel(compiledData[2], compiledData[1], playerLevel)
|
|
if (key == "questLevel") then
|
|
a = questLevel
|
|
elseif (key == "requiredLevel") then
|
|
a = requiredLevel
|
|
end
|
|
questLevel, requiredLevel = getTbcLevel(nonCompiledData[QuestieDB.questKeys["questLevel"]], nonCompiledData[QuestieDB.questKeys["requiredLevel"]], playerLevel)
|
|
if (key == "questLevel") then
|
|
b = questLevel
|
|
elseif (key == "requiredLevel") then
|
|
b = requiredLevel
|
|
end
|
|
end
|
|
|
|
-- if key == "extraObjectives" then
|
|
-- -- Do nothing
|
|
-- else
|
|
if type(a) == "number" and abs(a-(b or 0)) > 0.2 then
|
|
Questie:Warning("Nonmatching number at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. questId)
|
|
return
|
|
elseif type(a) == "string" and a ~= (b or "") then
|
|
Questie:Warning("Nonmatching string at " .. key .. " " .. tostring(a) .. " ~= " .. tostring(b) .. " for ID: ".. questId)
|
|
return
|
|
elseif type(a) == "table" then
|
|
--? This is kind of stupid, but because the compiler always has to write a int24 it will always write 0 for empty tables
|
|
--? So we have to emulate the same behavior here
|
|
if key == "extraObjectives" then
|
|
for i = 1, #b do
|
|
if not b[i][4] and a[i][4] == 0 then
|
|
b[i][4] = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
if not equals(a, (b or {})) then
|
|
Questie:Warning("Nonmatching table at " .. key .. " " .. id .. " for ID: ".. questId)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if count == TICKS_PER_YIELD_DEBUG then
|
|
count = 0
|
|
safeYield()
|
|
end
|
|
count = count + 1
|
|
end
|
|
|
|
validator.stream:finished()
|
|
Questie:Debug(Questie.DEBUG_INFO, "Finished quests validation without issues!")
|
|
end
|
|
|
|
function QuestieDBCompiler:GetDBHandle(data, pointers, skipMap, keyToRootIndex, overrides)
|
|
local handle = {}
|
|
local map, lastIndex, lastPtr, types, _, indexToKey, keyToIndex = unpack(skipMap)
|
|
|
|
local stream = QuestieStream:GetStream("raw")
|
|
safeYield()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[GetDBHandle] pointers type:", type(pointers), "length:", pointers and #pointers or "nil")
|
|
stream:Load(pointers)
|
|
safeYield()
|
|
pointers = QuestieDBCompiler:DecodePointerMap(stream)
|
|
--Questie.db.global.__pointers = pointers
|
|
safeYield()
|
|
stream:Load(data)
|
|
handle.stream = stream
|
|
|
|
if overrides then
|
|
---@param id QuestId|ObjectId|ItemId|NpcId|SpellId
|
|
---@param key string
|
|
---@return any
|
|
handle.QuerySingle = function(id, key)
|
|
local override = overrides[id]
|
|
if override then
|
|
local kti = keyToRootIndex[key]
|
|
if kti and override[kti] ~= nil then return override[kti] end
|
|
end
|
|
local ptr = pointers[id]
|
|
if not ptr then
|
|
--print("Entry not found! " .. id)
|
|
return nil
|
|
end
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
skippers[types[indexToKey[i]]](stream)
|
|
-- readers[types[indexToKey[i]]](stream)
|
|
end
|
|
end
|
|
return readers[types[key]](stream)
|
|
end
|
|
---@param id QuestId|ObjectId|ItemId|NpcId|SpellId
|
|
---@param keys string[]
|
|
---@return table|nil
|
|
handle.Query = function(id, keys)
|
|
--if overrides[id] then
|
|
-- local ret = {}
|
|
-- for index,key in pairs({...}) do
|
|
-- local kti = keyToIndex[key]
|
|
-- if kti then
|
|
-- ret[index] = overrides[id][kti]
|
|
-- end
|
|
-- end
|
|
-- return ret
|
|
--end
|
|
local ptr = pointers[id]
|
|
local override = overrides[id]
|
|
if not ptr then
|
|
if override then
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local rootIndex = keyToRootIndex[keys[index]]
|
|
if rootIndex and override[rootIndex] ~= nil then
|
|
ret[index] = override[rootIndex]
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
return nil
|
|
end
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local key = keys[index]
|
|
local rootIndex = keyToRootIndex[key]
|
|
if override and rootIndex and override[rootIndex] ~= nil then
|
|
ret[index] = override[rootIndex]
|
|
else
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
skippers[types[indexToKey[i]]](stream)
|
|
end
|
|
end
|
|
ret[index] = readers[types[key]](stream)
|
|
end
|
|
end
|
|
return ret -- do not unpack the returned table
|
|
end
|
|
---@param id QuestId|ObjectId|ItemId|NpcId|SpellId
|
|
---@param keys string[]
|
|
---@return table|nil
|
|
handle.QueryValidator = function(id, keys)
|
|
local ptr = pointers[id]
|
|
local override = overrides[id]
|
|
if not ptr then
|
|
if override then
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local rootIndex = keyToRootIndex[keys[index]]
|
|
if rootIndex and override[rootIndex] ~= nil then
|
|
ret[index] = override[rootIndex]
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
return nil
|
|
end
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local key = keys[index]
|
|
local rootIndex = keyToRootIndex[key]
|
|
if override and rootIndex and override[rootIndex] ~= nil then
|
|
ret[index] = override[rootIndex]
|
|
else
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
local beforeSkipper = stream._pointer
|
|
skippers[types[indexToKey[i]]](stream)
|
|
local afterSkipper = stream._pointer
|
|
stream._pointer = beforeSkipper
|
|
readers[types[indexToKey[i]]](stream)
|
|
local afterReader = stream._pointer
|
|
if(afterSkipper ~= afterReader) then
|
|
Questie:Error("ERROR: Skipper and reader did not match for key: " .. key, afterSkipper, afterReader, afterReader-afterSkipper)
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
ret[index] = readers[types[key]](stream)
|
|
end
|
|
end
|
|
return ret -- do not unpack the returned table
|
|
end
|
|
else
|
|
handle.QuerySingle = function(id, key)
|
|
local ptr = pointers[id]
|
|
if not ptr then
|
|
--print("Entry not found! " .. id)
|
|
return nil
|
|
end
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
skippers[types[indexToKey[i]]](stream)
|
|
-- readers[types[indexToKey[i]]](stream)
|
|
end
|
|
end
|
|
return readers[types[key]](stream)
|
|
end
|
|
---@param id QuestId|ObjectId|ItemId|NpcId|SpellId
|
|
---@param keys string[]
|
|
---@return table|nil
|
|
handle.Query = function(id, keys)
|
|
local ptr = pointers[id]
|
|
if not ptr then
|
|
--print("Entry not found! " .. id)
|
|
return nil
|
|
end
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local key = keys[index]
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
skippers[types[indexToKey[i]]](stream)
|
|
end
|
|
end
|
|
ret[index] = readers[types[key]](stream)
|
|
end
|
|
return ret -- do not unpack the returned table
|
|
end
|
|
---@param id QuestId|ObjectId|ItemId|NpcId|SpellId
|
|
---@param keys string[]
|
|
---@return table|nil
|
|
handle.QueryValidator = function(id, keys)
|
|
local ptr = pointers[id]
|
|
if not ptr then
|
|
--print("Entry not found! " .. id)
|
|
return nil
|
|
end
|
|
local ret = {}
|
|
for index=1,#keys do
|
|
local key = keys[index]
|
|
if map[key] ~= nil then -- can skip directly
|
|
stream._pointer = map[key] + ptr
|
|
else -- need to skip over some variably sized data
|
|
stream._pointer = lastPtr + ptr
|
|
local targetIndex = keyToIndex[key]
|
|
if not targetIndex then
|
|
Questie:Error("ERROR: Unhandled db key: " .. key)
|
|
return nil
|
|
end
|
|
for i = lastIndex, targetIndex-1 do
|
|
local beforeSkipper = stream._pointer
|
|
skippers[types[indexToKey[i]]](stream)
|
|
local afterSkipper = stream._pointer
|
|
stream._pointer = beforeSkipper
|
|
readers[types[indexToKey[i]]](stream)
|
|
local afterReader = stream._pointer
|
|
if(afterSkipper ~= afterReader) then
|
|
Questie:Error("ERROR: Skipper and reader did not match for key: " .. key, afterSkipper, afterReader, afterReader-afterSkipper)
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
ret[index] = readers[types[key]](stream)
|
|
end
|
|
return ret -- do not unpack the returned table
|
|
end
|
|
end
|
|
|
|
handle.pointers = pointers
|
|
|
|
return handle
|
|
end
|