-- @docfuncs @{ function print(...) local msg = '' local args = { ... } local appendSpace = #args > 1 for i, v in ipairs(args) do msg = msg .. tostring(v) if appendSpace and i < #args then msg = msg .. ' ' end end g_logger.log(LogInfo, msg) end function pinfo(msg) g_logger.log(LogInfo, msg) end function perror(msg) g_logger.log(LogError, msg) end function pwarning(msg) g_logger.log(LogWarning, msg) end function pdebug(msg) g_logger.log(LogDebug, msg) end function fatal(msg) g_logger.log(LogFatal, msg) end function exit() g_app.exit() end function quit() g_app.quit() end function connect(object, arg1, arg2, arg3) if not object then return end local signalsAndSlots local pushFront if type(arg1) == 'string' then signalsAndSlots = { [arg1] = arg2 } pushFront = arg3 else signalsAndSlots = arg1 pushFront = arg2 end for signal, slot in pairs(signalsAndSlots) do if not object[signal] then local mt = getmetatable(object) if mt and type(object) == 'userdata' then object[signal] = function(...) return signalcall(mt[signal], ...) end end end if not object[signal] then object[signal] = slot elseif type(object[signal]) == 'function' then object[signal] = { object[signal] } end if type(slot) ~= 'function' then perror(debug.traceback('unable to connect a non function value')) end if type(object[signal]) == 'table' then if pushFront then table.insert(object[signal], 1, slot) else table.insert(object[signal], #object[signal] + 1, slot) end end end end function disconnect(object, arg1, arg2) local signalsAndSlots if type(arg1) == 'string' then if arg2 == nil then object[arg1] = nil return end signalsAndSlots = { [arg1] = arg2 } elseif type(arg1) == 'table' then signalsAndSlots = arg1 else perror(debug.traceback('unable to disconnect')) end for signal, slot in pairs(signalsAndSlots) do if not object[signal] then elseif type(object[signal]) == 'function' then if object[signal] == slot then object[signal] = nil end elseif type(object[signal]) == 'table' then for k, func in pairs(object[signal]) do if func == slot then table.remove(object[signal], k) if #object[signal] == 1 then object[signal] = object[signal][1] end break end end end end end function newclass(name) if not name then perror(debug.traceback('new class has no name.')) end local class = {} function class.internalCreate() local instance = {} for k, v in pairs(class) do instance[k] = v end return instance end class.create = class.internalCreate class.__class = name class.getClassName = function() return name end return class end function extends(base, name) if not name then perror(debug.traceback('extended class has no name.')) end local derived = {} function derived.internalCreate() local instance = base.create() for k, v in pairs(derived) do instance[k] = v end return instance end derived.create = derived.internalCreate derived.__class = name derived.getClassName = function() return name end return derived end function runinsandbox(func, ...) if type(func) == 'string' then func, err = loadfile(resolvepath(func, 2)) if not func then error(err) end end local env = {} local oldenv = getfenv(0) setmetatable(env, { __index = oldenv }) setfenv(0, env) func(...) setfenv(0, oldenv) return env end function loadasmodule(name, file) file = file or resolvepath(name, 2) if package.loaded[name] then return package.loaded[name] end local env = runinsandbox(file) package.loaded[name] = env return env end local function module_loader(modname) local module = g_modules.getModule(modname) if not module then return '\n\tno module \'' .. modname .. '\'' end return function() if not module:load() then error('unable to load required module ' .. modname) end return module:getSandbox() end end table.insert(package.loaders, 1, module_loader) function import(table) assert(type(table) == 'table') local env = getfenv(2) for k, v in pairs(table) do env[k] = v end end function export(what, key) if key ~= nil then _G[key] = what else for k, v in pairs(what) do _G[k] = v end end end function unexport(key) if type(key) == 'table' then for _k, v in pairs(key) do _G[v] = nil end else _G[key] = nil end end function getfsrcpath(depth) depth = depth or 2 local info = debug.getinfo(1 + depth, 'Sn') local path if info.short_src then path = info.short_src:match('(.*)/.*') end if not path then path = '/' elseif path:sub(0, 1) ~= '/' then path = '/' .. path end return path end function resolvepath(filePath, depth) if not filePath then return nil end depth = depth or 1 if filePath then if filePath:sub(0, 1) ~= '/' then local basepath = getfsrcpath(depth + 1) if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end return basepath .. filePath else return filePath end else local basepath = getfsrcpath(depth + 1) if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end return basepath end end function toboolean(v) if type(v) == 'string' then v = v:trim():lower() if v == '1' or v == 'true' then return true end elseif type(v) == 'number' then if v == 1 then return true end elseif type(v) == 'boolean' then return v end return false end function fromboolean(boolean) if boolean then return 'true' else return 'false' end end function booleantonumber(boolean) if boolean then return 1 else return 0 end end function numbertoboolean(number) if number ~= 0 then return true else return false end end function protectedcall(func, ...) local status, ret = pcall(func, ...) if status then return ret end perror(ret) return false end function signalcall(param, ...) if type(param) == 'function' then local status, ret = pcall(param, ...) if status then return ret else perror(ret) end elseif type(param) == 'table' then for k, v in pairs(param) do local status, ret = pcall(v, ...) if status then if ret then return true end else perror(ret) end end elseif param ~= nil then error('attempt to call a non function value') end return false end function tr(s, ...) return string.format(s, ...) end function getOppositeAnchor(anchor) if anchor == AnchorLeft then return AnchorRight elseif anchor == AnchorRight then return AnchorLeft elseif anchor == AnchorTop then return AnchorBottom elseif anchor == AnchorBottom then return AnchorTop elseif anchor == AnchorVerticalCenter then return AnchorHorizontalCenter elseif anchor == AnchorHorizontalCenter then return AnchorVerticalCenter end return anchor end function makesingleton(obj) local singleton = {} if obj.getClassName then for key, value in pairs(_G[obj:getClassName()]) do if type(value) == 'function' then singleton[key] = function(...) return value(obj, ...) end end end end return singleton end -- TFS compliant Lua debugging: -- https://github.com/otland/forgottenserver/blob/b23850046a2aec05636761c49d296c755865288a/data/lib/debugging/dump.lua -- recursive dump function function dumpLevel(input, level) local indent = '' for i = 1, level do indent = indent .. ' ' end if type(input) == 'table' then local str = '{ \n' local lines = {} for k, v in pairs(input) do if type(k) ~= 'number' then k = '"' .. k .. '"' end if type(v) == 'string' then v = '"' .. v .. '"' end table.insert(lines, indent .. ' [' .. k .. '] = ' .. dumpLevel(v, level + 1)) end return str .. table.concat(lines, ',\n') .. '\n' .. indent .. '}' end return tostring(input) end -- Return a string representation of input for debugging purposes function dump(input) return dumpLevel(input, 0) end -- Call the dump function and print it to console function pdump(input) local dump_str = dump(input) print(dump_str) return dump_str end -- Call the dump function with a title and print it beautifully to the console function tdump(title, input) local title_fill = '' for i = 1, title:len() do title_fill = title_fill .. '=' end local header_str = '\n====' .. title_fill .. '====\n' header_str = header_str .. '=== ' .. title .. ' ===\n' header_str = header_str .. '====' .. title_fill .. '====\n' local dump_str = dump(input) local footer_str = '\n====' .. title_fill .. '====\n' print(header_str .. dump_str .. footer_str) return dump_str end -- @} io.content = function(path) return g_resources.readFileContents('/' .. path) end function pdumpWidgetId(widget, indent) indent = indent or "" local children = widget:getChildren() for i, child in ipairs(children) do local prefix = (i == #children) and "`-- " or "|-- " print(indent .. prefix .. child:getId()) local newIndent = (i == #children) and (indent .. " ") or (indent .. "| ") pdumpWidgetId(child, newIndent) end end function comma_value(amount) local formatted = tostring(amount) while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if k == 0 then break end end return formatted end function convertGold(amount, shortValue) local formatType = 0 if shortValue and amount > 9999999999 then formatType = 1 amount = math.floor(amount / 1000) end local formatted = tostring(amount) while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if k == 0 then break end end if formatType == 1 then formatted = formatted .. " k" end return formatted end