forked from Simnation/Main
451 lines
No EOL
14 KiB
Lua
451 lines
No EOL
14 KiB
Lua
Utils = {}
|
|
Utils.Debug = {}
|
|
Utils.Table = {}
|
|
Utils.Math = {}
|
|
Utils.String = {}
|
|
Utils.CustomScripts = {}
|
|
Utils.Config = Config
|
|
Utils.Lang = {}
|
|
Utils.Version = LoadResourceFile("lc_utils", "version") and string.gsub(LoadResourceFile("lc_utils", "version"), '^%s*(.-)%s*$', '%1') or nil
|
|
|
|
exports('GetUtils', function()
|
|
return Utils
|
|
end)
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Debug
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.Debug.printTable(...)
|
|
local args = {...}
|
|
for _, arg in ipairs(args) do
|
|
printNode(arg)
|
|
end
|
|
end
|
|
|
|
function printNode(node)
|
|
if type(node) == "table" then
|
|
-- to make output beautiful
|
|
local function tab(amt)
|
|
local str = ""
|
|
for i=1,amt do
|
|
str = str .. "\t"
|
|
end
|
|
return str
|
|
end
|
|
|
|
local cache, stack, output = {},{},{}
|
|
local depth = 1
|
|
local output_str = "{\n"
|
|
|
|
while true do
|
|
local size = 0
|
|
for k,v in pairs(node) do
|
|
size = size + 1
|
|
end
|
|
|
|
local cur_index = 1
|
|
for k,v in pairs(node) do
|
|
if (cache[node] == nil) or (cur_index >= cache[node]) then
|
|
|
|
if (string.find(output_str,"}",output_str:len())) then
|
|
output_str = output_str .. ",\n"
|
|
elseif not (string.find(output_str,"\n",output_str:len())) then
|
|
output_str = output_str .. "\n"
|
|
end
|
|
|
|
-- This is necessary for working with HUGE tables otherwise we run out of memory using concat on huge strings
|
|
table.insert(output,output_str)
|
|
output_str = ""
|
|
|
|
local key
|
|
if (type(k) == "number" or type(k) == "boolean") then
|
|
key = "["..tostring(k).."]"
|
|
else
|
|
key = "['"..tostring(k).."']"
|
|
end
|
|
|
|
if (type(v) == "number" or type(v) == "boolean") then
|
|
output_str = output_str .. tab(depth) .. key .. " = "..tostring(v)
|
|
elseif (type(v) == "table") then
|
|
output_str = output_str .. tab(depth) .. key .. " = {\n"
|
|
table.insert(stack,node)
|
|
table.insert(stack,v)
|
|
cache[node] = cur_index+1
|
|
break
|
|
else
|
|
output_str = output_str .. tab(depth) .. key .. " = '"..tostring(v).."'"
|
|
end
|
|
|
|
if (cur_index == size) then
|
|
output_str = output_str .. "\n" .. tab(depth-1) .. "}"
|
|
else
|
|
output_str = output_str .. ","
|
|
end
|
|
else
|
|
-- close the table
|
|
if (cur_index == size) then
|
|
output_str = output_str .. "\n" .. tab(depth-1) .. "}"
|
|
end
|
|
end
|
|
|
|
cur_index = cur_index + 1
|
|
end
|
|
|
|
if (#stack > 0) then
|
|
node = stack[#stack]
|
|
stack[#stack] = nil
|
|
depth = cache[node] == nil and depth + 1 or depth - 1
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
-- This is necessary for working with HUGE tables otherwise we run out of memory using concat on huge strings
|
|
table.insert(output,output_str)
|
|
output_str = table.concat(output)
|
|
|
|
print(output_str)
|
|
else
|
|
print(node)
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Table
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.Table.tableLength(T)
|
|
local count = 0
|
|
for _ in pairs(T) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
function Utils.Table.contains(table, element)
|
|
for _, value in pairs(table) do
|
|
if value == element then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Utils.Table.deepCopy(orig)
|
|
local orig_type = type(orig)
|
|
local copy
|
|
if orig_type == 'table' then
|
|
copy = {}
|
|
for orig_key, orig_value in next, orig, nil do
|
|
copy[Utils.Table.deepCopy(orig_key)] = Utils.Table.deepCopy(orig_value)
|
|
end
|
|
setmetatable(copy, Utils.Table.deepCopy(getmetatable(orig)))
|
|
else -- number, string, boolean, etc
|
|
copy = orig
|
|
end
|
|
return copy
|
|
end
|
|
|
|
function Utils.Table.deepMerge(target, source)
|
|
for key, value in pairs(source) do
|
|
if type(value) == "function" then
|
|
target[key] = value
|
|
elseif type(value) == "table" and value ~= nil then
|
|
-- If the target does not have the key, initialize it as a table
|
|
if type(target[key]) ~= "table" then
|
|
target[key] = {}
|
|
end
|
|
-- Recursively merge tables
|
|
Utils.Table.deepMerge(target[key], value)
|
|
else
|
|
target[key] = value
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- String
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.String.capitalizeFirst(str)
|
|
if str == nil or str == '' then
|
|
return str
|
|
end
|
|
return (str:sub(1, 1):upper() .. str:sub(2))
|
|
end
|
|
|
|
function Utils.String.split(str, sep)
|
|
sep = sep or "%s"
|
|
local fields = {}
|
|
local pattern = string.format("([^%s]+)", sep)
|
|
str:gsub(pattern, function(c) fields[#fields + 1] = c end)
|
|
return fields
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Math
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.numberFormat(number, decimalPlaces)
|
|
local formatString = "%f"
|
|
if decimalPlaces ~= nil then
|
|
formatString = string.format("%%.%df", decimalPlaces)
|
|
end
|
|
|
|
local formattedNumber = string.format(formatString, number)
|
|
|
|
-- Remove trailing zeros if needed
|
|
if decimalPlaces == nil then
|
|
formattedNumber = formattedNumber:gsub("%.?0*$", "")
|
|
end
|
|
|
|
return formattedNumber
|
|
end
|
|
|
|
function Utils.Math.trim(value)
|
|
if not value then return nil end
|
|
return (string.gsub(value, '^%s*(.-)%s*$', '%1'))
|
|
end
|
|
|
|
function Utils.Math.round(value, numDecimalPlaces)
|
|
if not numDecimalPlaces then return math.floor(value + 0.5) end
|
|
local power = 10 ^ numDecimalPlaces
|
|
return math.floor((value * power) + 0.5) / (power)
|
|
end
|
|
|
|
function Utils.Math.weightedRandom(weights, shift)
|
|
local sum = 0
|
|
for _, weight in pairs(weights) do
|
|
sum = sum + weight
|
|
end
|
|
|
|
local threshold = math.random(0, sum) + (shift or 0)
|
|
if threshold > sum then threshold = sum end
|
|
local cumulative = 0
|
|
for number, weight in pairs(weights) do
|
|
cumulative = cumulative + weight
|
|
if threshold <= cumulative then
|
|
return number
|
|
end
|
|
end
|
|
end
|
|
|
|
function Utils.Math.getRandomKeyFromTable(tbl)
|
|
local keys = {}
|
|
for key in pairs(tbl) do
|
|
table.insert(keys, key)
|
|
end
|
|
local index = keys[math.random(1, #keys)]
|
|
return index
|
|
end
|
|
|
|
function Utils.Math.checkIfCurrentVersionisOutdated(latestVersion, curVersion)
|
|
local curVersionParts = {}
|
|
for part in string.gmatch(curVersion, "[^.]+") do
|
|
table.insert(curVersionParts, part)
|
|
end
|
|
|
|
local latestVersionParts = {}
|
|
for part in string.gmatch(latestVersion, "[^.]+") do
|
|
table.insert(latestVersionParts, part)
|
|
end
|
|
|
|
local function isPositiveInteger(str)
|
|
return tonumber(str) ~= nil and math.floor(tonumber(str) or 0) == tonumber(str) and tonumber(str) >= 0
|
|
end
|
|
|
|
local function validateParts(parts)
|
|
for i = 1, #parts do
|
|
if not isPositiveInteger(parts[i]) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
if not validateParts(curVersionParts) or not validateParts(latestVersionParts) then
|
|
return 0 / 0 -- NaN in Lua
|
|
end
|
|
|
|
for i = 1, #latestVersionParts do
|
|
if tonumber(curVersionParts[i]) == tonumber(latestVersionParts[i]) then
|
|
-- Do nothing, continue to the next part
|
|
elseif tonumber(curVersionParts[i]) > tonumber(latestVersionParts[i]) then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
if #curVersionParts ~= #latestVersionParts then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Language
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
local cached_langs = {}
|
|
function Utils.loadLanguageFile(lang_file)
|
|
local resource = getResourceName()
|
|
assert(resource,"^3Unknown resource loading the language files.^7")
|
|
|
|
Utils.Table.deepMerge(lang_file, Utils.Lang)
|
|
cached_langs[resource] = lang_file
|
|
end
|
|
|
|
function Utils.translate(key)
|
|
local resource = getResourceName()
|
|
if not resource then
|
|
return 'missing_resource'
|
|
end
|
|
|
|
if not cached_langs[resource] then
|
|
return 'missing_lang'
|
|
end
|
|
|
|
local locale = Config.locale
|
|
local langObj = cached_langs[resource][locale]
|
|
if not langObj then
|
|
print(string.format("Language '%s' is not available. Using default 'en'.", locale))
|
|
Config.locale = 'en'
|
|
langObj = cached_langs[resource][Config.locale]
|
|
end
|
|
|
|
local keys = Utils.String.split(key,".")
|
|
for _, k in ipairs(keys) do
|
|
if not langObj[k] then
|
|
print(string.format("Translation key '%s' not found for language '%s'.", key, locale))
|
|
return 'missing_translation'
|
|
end
|
|
langObj = langObj[k]
|
|
end
|
|
|
|
return langObj
|
|
end
|
|
|
|
Citizen.CreateThread(function()
|
|
if GetCurrentResourceName() ~= "lc_utils" then return end
|
|
Wait(1000)
|
|
|
|
Utils.loadLanguageFile(Utils.Lang)
|
|
|
|
if Utils.Version then
|
|
print("^2[lc_utils] Loaded! Support discord: https://discord.gg/U5YDgbh ^3[v"..Utils.Version.."]^7")
|
|
else
|
|
error("^1[lc_utils] Warning: Could not load the version file.^7")
|
|
end
|
|
|
|
assert(Config, "^3You have errors in your config file, consider fixing it or redownload the original config.^7")
|
|
assert(Config.framework == "QBCore" or Config.framework == "ESX", string.format("^3The Config.framework must be set to ^1ESX^3 or ^1QBCore^3, its actually set to ^1%s^3.^7", Config.framework))
|
|
|
|
local configs_to_validate = {
|
|
{ config_path = {"custom_scripts_compatibility"}, default_value = { ['fuel'] = "default", ['inventory'] = "default", ['keys'] = "default", ['mdt'] = "default", ['target'] = "disabled", ['notification'] = "default"} },
|
|
{ config_path = {"custom_scripts_compatibility", "notification"}, default_value = "default" },
|
|
{ config_path = {"owned_vehicles", "default"}, default_value = { ['garage'] = 'motelgarage', ['garage_display_name'] = 'Motel Parking' } },
|
|
{ config_path = {"notification"}, default_value = { ['has_title'] = false, ['position'] = "top-right", ['duration'] = 8000 } },
|
|
{ config_path = {"spawned_vehicles"}, default_value = {
|
|
['lc_truck_logistics'] = {
|
|
['is_static'] = false,
|
|
['plate_prefix'] = "TR"
|
|
},
|
|
['lc_stores'] = {
|
|
['is_static'] = false,
|
|
['plate_prefix'] = "ST"
|
|
},
|
|
['lc_gas_stations'] = {
|
|
['is_static'] = false,
|
|
['plate_prefix'] = "GS"
|
|
},
|
|
['lc_dealership'] = {
|
|
['is_static'] = false,
|
|
['plate_prefix'] = "DE"
|
|
},
|
|
['lc_factories'] = {
|
|
['is_static'] = false,
|
|
['plate_prefix'] = "FA"
|
|
}
|
|
} },
|
|
}
|
|
Config = Utils.validateConfig(Config, configs_to_validate)
|
|
end)
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- File functions validator
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.validateFunctions(functions, file_name)
|
|
local resourceName = GetCurrentResourceName()
|
|
|
|
for i = 1, #functions do
|
|
local fnName = functions[i]
|
|
local fn = _G[fnName]
|
|
if not Utils.functionExists(fn) then
|
|
print("^8[" .. resourceName .. "] ^3You have a missing function (^1" .. fnName .. "^3) in your '^1" .. file_name .. "^3' file. Please update this file to ensure the script functions correctly.^7")
|
|
_G[fnName] = function()
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local cached_functions = {}
|
|
function Utils.functionExists(fn)
|
|
if fn == nil then
|
|
return false
|
|
end
|
|
|
|
if cached_functions[fn] ~= nil then
|
|
return cached_functions[fn]
|
|
end
|
|
|
|
-- Cache the result of type check
|
|
local exists = type(fn) == "function"
|
|
cached_functions[fn] = exists
|
|
return exists
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Config validator
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
function Utils.validateConfig(_Config, configs_to_validate)
|
|
for _, v in pairs(configs_to_validate) do
|
|
local config_entry = getConfigEntry(_Config, v.config_path)
|
|
|
|
if config_entry == nil then
|
|
printMissingConfigMessage("Config."..table.concat(v.config_path, "."))
|
|
setConfigValue(_Config, v.config_path, v.default_value)
|
|
end
|
|
end
|
|
return _Config
|
|
end
|
|
|
|
function getConfigEntry(_Config, path)
|
|
for _, key in ipairs(path) do
|
|
_Config = _Config and _Config[key]
|
|
end
|
|
return _Config
|
|
end
|
|
|
|
function setConfigValue(_Config, path, value)
|
|
for i = 1, #path - 1 do
|
|
if _Config[path[i]] == nil then
|
|
_Config[path[i]] = {}
|
|
end
|
|
_Config = _Config[path[i]]
|
|
end
|
|
_Config[path[#path]] = value
|
|
end
|
|
|
|
function printMissingConfigMessage(config_entry)
|
|
local resource = getResourceName()
|
|
print("^3WARNING: Missing config '^1" .. config_entry .. "^3' in resource '^1"..resource.."^3'. The value will be set to its default. Consider redownloading the original config to obtain the correct config.^7")
|
|
end
|
|
|
|
function getResourceName()
|
|
return GetCurrentResourceName()
|
|
end |