1
0
Fork 0
forked from Simnation/Main
Main/resources/[carscripts]/lc_utils/functions/shared.lua
2025-06-07 08:51:21 +02:00

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