Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7644b37
multi spr loader
Zbizu Jan 3, 2026
8f11376
refactor sprite manager
Zbizu Jan 4, 2026
c6c8c55
resource id field
Zbizu Jan 4, 2026
e037f79
outfit resource ids, some refactoring
Zbizu Jan 6, 2026
69f91d0
resource id in protocol
Zbizu Jan 7, 2026
4735d09
fix garbage collection
Zbizu Jan 7, 2026
76fe1a8
fix appearances path and gc infinite loop
Zbizu Jan 7, 2026
770082d
fix return value
Zbizu Jan 7, 2026
4c13d27
fix multispr loader
Zbizu Jan 7, 2026
b034bb4
resourceid fields in Lua outfit
Zbizu Jan 7, 2026
b3dc2dd
fix version check
Zbizu Jan 7, 2026
00ed01a
better onstyleapply
Zbizu Jan 7, 2026
299f2de
simplify access to sprite manager
Zbizu Jan 8, 2026
0a96dbf
fix loading legacy sprite manager
Zbizu Jan 8, 2026
5383de9
another gc fix
Zbizu Jan 8, 2026
dc4ab72
fix drawpool crash and loadspr return value
Zbizu Jan 8, 2026
7e31af2
fix lua loaders
Zbizu Jan 8, 2026
bc17d96
fix action bar issue
Zbizu Jan 8, 2026
a15b2fa
Merge branch 'main' into resources
Zbizu Jan 9, 2026
3976020
thing type getter cleanup
Zbizu Jan 9, 2026
631698a
address two sonar blockers
Zbizu Jan 9, 2026
60b53e6
address nested code issue
Zbizu Jan 9, 2026
ce614e4
remove commented code
Zbizu Jan 9, 2026
015e21f
better handling for setId on creature
Zbizu Jan 9, 2026
7ff0810
fix nesting issue
Zbizu Jan 9, 2026
29c7e86
refactor dat unserializer monolith
Zbizu Jan 9, 2026
4b183ab
addressing sonar issues
Zbizu Jan 9, 2026
398aa05
precise class operators, resolve setid conflict
Zbizu Jan 9, 2026
53c1962
sonar issues - function complexity
Zbizu Jan 9, 2026
4940fc0
sonar low prio issues
Zbizu Jan 9, 2026
d810695
adjust to changes from main branch
Zbizu Jan 9, 2026
6cc6453
Merge branch 'main' into resources
Zbizu Jan 9, 2026
9050f83
address sonar medium priority issues
Zbizu Jan 9, 2026
114fca7
Merge branch 'resources' of https://github.com/zbizu/otclient into re…
Zbizu Jan 9, 2026
7bd3c65
clean up outfit class
Zbizu Jan 9, 2026
c0f3762
no dat dumping at all without FRAMEWORK_EDITOR
Zbizu Jan 9, 2026
800a2c5
framework_editor compliance
Zbizu Jan 10, 2026
1a811fa
sonar const methods
Zbizu Jan 10, 2026
2dcf2a0
fix reading creature
Zbizu Jan 10, 2026
bede788
split outfit class, implement wings drawing
Zbizu Jan 11, 2026
f5bced0
mount colors, fix displacement
Zbizu Jan 11, 2026
333a34e
one more fix to mounts
Zbizu Jan 11, 2026
e28e70f
aura adjustments
Zbizu Jan 11, 2026
8094044
fix spr signature getter for legacy login
Zbizu Jan 11, 2026
b6df4b3
do not bounce the aura
Zbizu Jan 11, 2026
b006bdf
protocolgame: send methods
Zbizu Jan 11, 2026
2806fb5
parse methods
Zbizu Jan 12, 2026
80d984a
small code adjustments
Zbizu Jan 12, 2026
1d1d432
fix action bar
Zbizu Jan 12, 2026
02b42e3
proper handling of gamestore packets
Zbizu Jan 13, 2026
a06bad9
protocol changes
Zbizu Jan 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 4 additions & 45 deletions meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,19 @@ g_things = {}

---@param file string
---@return boolean
function g_things.loadAppearances(file) end
function g_things.loadAppearances(file, resourceId) end

---@param file string
---@return boolean
function g_things.loadStaticData(file) end

---@param file string
---@return boolean
function g_things.loadDat(file) end
function g_things.loadDat(file, resourceId) end

---@param file string
---@return boolean
function g_things.loadOtml(file) end
function g_things.loadOtml(file, resourceId) end

---@return boolean
function g_things.isDatLoaded() end
Expand All @@ -181,7 +181,7 @@ function g_things.getThingType(id, category) end

---@param category integer
---@return ThingType[]
function g_things.getThingTypes(category) end
function g_things.getThingTypes(category, resourceId) end

---@param attr integer
---@param category integer
Expand Down Expand Up @@ -308,47 +308,6 @@ function g_towns.getTowns() end

function g_towns.sort() end

--------------------------------
---------- g_sprites -----------
--------------------------------

---@class g_sprites
g_sprites = {}

---@param file string
---@return boolean
function g_sprites.loadSpr(file) end

function g_sprites.unload() end

---@return boolean
function g_sprites.isLoaded() end

---@return number
function g_sprites.getSprSignature() end

---@return integer
function g_sprites.getSpritesCount() end

---* FRAMEWORK_EDITOR
---@param fileName string
function g_sprites.saveSpr(fileName) end

--------------------------------
------ g_spriteAppearances -----
--------------------------------

---@class g_spriteAppearances
g_spriteAppearances = {}

---@param id integer
---@param file string
function g_spriteAppearances.saveSpriteToFile(id, file) end

---@param id integer
---@param file string
function g_spriteAppearances.saveSheetToFileBySprite(id, file) end

--------------------------------
------------ g_map -------------
--------------------------------
Expand Down
2 changes: 1 addition & 1 deletion modules/client_options/data_options.lua
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ return {
asyncTxtLoading = {
value = false,
action = function(value, options, controller, panels, extraWidgets)
if g_game.isUsingProtobuf() then
if true then --g_game.isUsingProtobuf() then -- deprecated
value = true
elseif g_app.isEncrypted() then
local asyncWidget = panels.graphicsPanel:recursiveGetChildById('asyncTxtLoading')
Expand Down
4 changes: 2 additions & 2 deletions modules/game_actionbar/logics/ActionButtonLogic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ function updateButton(button)

button.cache = getButtonCache(button)
if button.item.getItemId and not button.cache.actionType then
button.item:setItemId(0, true)
button.item:setItemId(0)
button.item:setOn(false)
end

Expand All @@ -704,7 +704,7 @@ function updateButton(button)
local passiveAbility = buttonData["actionsetting"]["passiveAbility"]

if useAction then
button.item:setItemId(useAction, true)
button.item:setItemId(useAction, 0) -- to do: resourceId
button.item:setOn(true)
local cached = cachedItemWidget[useAction]
if cached then
Expand Down
254 changes: 215 additions & 39 deletions modules/game_things/things.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
ThingsLoaderController = Controller:new()

local filename = nil
local loaded = false

function setFileName(name)
filename = name
end

function isLoaded()
return loaded
end
Expand Down Expand Up @@ -41,62 +36,243 @@ local function tryLoadDatWithFallbacks(datPath)
return false
end

local function load(version)
local errorList = {}
local function findFileByExtension(path, ext)
-- find Tibia.ext
local fileName = "Tibia" .. ext
local resolvedPath = resolvepath(path .. fileName)
if g_resources.fileExists(resolvedPath) then
return resolvedPath
end

if version >= 1281 and not g_game.getFeature(GameLoadSprInsteadProtobuf) then
local filePath = resolvepath(string.format('/things/%d/', version))
if not g_things.loadAppearances(filePath) then
errorList[#errorList + 1] = "Couldn't load assets"
end
if not g_things.loadStaticData(filePath) then
errorList[#errorList + 1] = "Couldn't load staticdata"
end
else
local datPath, sprPath
if filename then
datPath = resolvepath('/data/things/' .. filename)
sprPath = resolvepath('/data/things/' .. filename)
else
datPath = resolvepath('/data/things/' .. version .. '/Tibia')
sprPath = resolvepath('/data/things/' .. version .. '/Tibia')
-- find any filename.ext
resolvedPath = resolvepath(path)
local files = g_resources.listDirectoryFiles(resolvedPath)
for _, file in ipairs(files) do
-- match .otfi extension
if file:lower():sub(ext:len()) == ext then
resolvedPath = resolvepath(path .. "/" .. file)
return resolvedPath
end
end

g_logger.setLevel(5)
if not tryLoadDatWithFallbacks(datPath) then
errorList[#errorList + 1] = tr('Unable to load dat file, please place a valid dat in \'%s.dat\'', datPath)
end
g_logger.setLevel(1)
-- no file found
end

-- helper to get boolean from parsed otfi
local function toboolean(v)
if v == nil then
return false
end

if type(v) == "boolean" then
return v
end

if not g_sprites.loadSpr(sprPath) then
errorList[#errorList + 1] = tr('Unable to load spr file, please place a valid spr in \'%s.spr\'', sprPath)
if type(v) ~= "string" then
return false
end

v = string.lower(v)
return v == "true" or v == "1" or v == "yes" or v == "on" or v == "enabled"
end

local function setFeature(feature, value)
if value == nil then
-- use version default if not defined in otfi
return
elseif toboolean(value) then
-- evaluated to true
g_game.enableFeature(feature)
else
-- evaluated to false
g_game.disableFeature(feature)
end
end

local function addError(errorList, message, resourceId)
if resourceId > 0 then
errorList[#errorList + 1] = string.format("Resource %d: %s", resourceId, message)
else
errorList[#errorList + 1] = message
end
end

local function loadResource(path, version, resourceId, errorList)
-- file loading fallback order:
-- 1. catalog-content.json - if found: load assets
-- 2. Tibia.otfi - if found: load dat specified in it
-- 3. any otfi - if found: load dat specified in it
-- 4. Tibia.dat
-- 5. any dat

-- assets
if g_resources.fileExists(resolvepath(path .. 'catalog-content.json')) then
g_logger.info(string.format("Loading resource %d (assets) ...", resourceId))
if not g_things.loadAppearances(resolvepath(path), resourceId) then
addError(errorList, "Couldn't load assets", resourceId)
end
if g_game.getFeature(GameLoadSprInsteadProtobuf) and version >= 1281 then
local staticPath = resolvepath(string.format('/things/%d/appearances', version))
if not g_things.loadAppearances(staticPath) then
g_logger.warning(string.format(
"[game_things.load()] Couldn't load /things/%d/appearances.dat, possible packets error.", version))
end
if not g_things.loadStaticData(path) then
addError(errorList, "Couldn't load staticdata", resourceId)
end
end

return
end

g_logger.info(string.format("Loading resource %d (spr/dat) from %s ...", resourceId, path))

-- otfi-defined spr/dat
local otfiPath = findFileByExtension(path, ".otfi")
if otfiPath then
-- read config from otfi
local otfiSettings = g_configs.create(otfiPath)
if not otfiSettings then
addError(errorList, "Failed to load OTFI", resourceId)
return
end

local datSpr = otfiSettings:getNode("DatSpr")
if not datSpr then
addError(errorList, "Invalid OTFI structure", resourceId)
return
end

-- nodes priority:
-- 1. otfi assets-name
-- 2. otfi "-file" nodes
-- 3. (if not defined by otfi) Tibia .spr/.dat
local sprName = "Tibia.spr"
local datName = "Tibia.dat"

local assetsName = datSpr["assets-name"]
if assetsName then
sprName = assetsName .. ".spr"
datName = assetsName .. ".dat"
else
sprName = datSpr["sprites-file"] or sprName
datName = datSpr["metadata-file"] or datName
end

-- set features according to otfi
setFeature(GameSpritesU32, datSpr["extended"])
setFeature(GameSpritesAlphaChannel, datSpr["transparency"])
setFeature(GameIdleAnimations, datSpr["frame-groups"])
setFeature(GameEnhancedAnimations,datSpr["frame-durations"])

-- check if otfi-specified dat file exists
local datPath = resolvepath(path .. datName)
if not g_resources.fileExists(datPath) then
addError(errorList, string.format("Unable to load %s: file not found", datName), resourceId)
return
end

-- try to load dat file
if not g_things.loadDat(datPath, resourceId) then
addError(errorList, string.format("Failed to read %s: file structure does not match the defined version or OTFI specification", datName), resourceId)
return
end

-- check if otfi-specified spr file exists
local sprPath = resolvepath(path .. sprName)
if not g_resources.fileExists(sprPath) then
addError(errorList, string.format("Unable to load %s: file not found", datName), resourceId)
return
end

-- try to load spr file
if not g_things.loadSpr(sprPath, resourceId) then
addError(errorList, string.format("Failed to read %s: file structure does not match the defined version or OTFI specification", datName), resourceId)
return
end
else
-- normal dat file
local datPath = findFileByExtension(path, ".dat")
if not datPath then
addError(errorList, "DAT file not found", resourceId)
return
end

-- try to load dat
local datResult = tryLoadDatWithFallbacks(datPath, resourceId)
if not datResult then
addError(errorList, "Failed to read dat file: file structure does not match the defined version", resourceId)
end

-- normal spr file
local sprPath = findFileByExtension(path, ".spr")
if not sprPath then
addError(errorList, "SPR file not found", resourceId)
return
end

-- try to load spr
local sprResult = g_things.loadSpr(sprPath, resourceId)
if not sprResult then
addError(errorList, "Failed to read dat file: file structure does not match the defined version", resourceId)
end
end
end

local function loadPackInfo(packPath, errorList)
g_logger.info("Found packinfo.xml")
local packInfo = g_things.decodePackInfo(resolvepath(packPath))
if #packInfo == 0 then
addError(errorList, "Failed to decode packinfo.xml", 0)
return
end

for _, resInfo in pairs(packInfo) do
loadResource(string.format("%s%s/", packPath, resInfo.dir), resInfo.version, resInfo.id, errorList)

if #errorList > 0 then
break
end
end

-- enable resource ids in packets
-- g_game.enableFeature(GameMultiSpr)
end

local function load(version)
-- prevent calling again after a failed attempt
if version == 0 then
return
end

local errorList = {}
local path = string.format('/data/things/%s/', version)

g_logger.info("Loading game assets from " .. path)

local packPath = resolvepath(path .. 'packinfo.xml')
if g_resources.fileExists(packPath) then
loadPackInfo(path, errorList)
else
loadResource(path, version, 0, errorList)
end

loaded = #errorList == 0
if loaded then
-- loading client files was successful, try to load sounds now
-- sound files are optional, this means that failing to load them
-- will not block logging into game
g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version)))
if version > 1300 then
g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version)))
end

g_logger.info("Assets loading complete.")
return
end

local messageBox = displayErrorBox(tr('Error'), table.concat(errorList, "\n"))
local errors = table.concat(errorList, "\n")
local messageBox = displayErrorBox(tr('Error'), errors)
addEvent(function()
messageBox:raise()
messageBox:focus()
end)

g_game.setClientVersion(0)
g_game.setProtocolVersion(0)
g_logger.error(errors)
end

function ThingsLoaderController:onInit()
Expand Down
Loading