Globals = {} Functions = {} Locale = Locales[Config.Locale or "en"] -- Height offset for interaction points/zones above ground level -- Stored coords are at ground + this offset, visualizations use GetGroundZFor_3dCoord Globals.InteractionZOffset = 1.0 exports("locale", function() return Locale end) exports("config", function() return Config end) ---@param text string ---@param debugtype? "warning"|"debug" function DebugPrint(text, debugtype, ...) if not Config.Debug then return end local prefix = "^2[DEBUG]^7" if debugtype == "warning" then prefix = "^3[WARNING]^7" end local args = {...} local output = "" for i = 1, #args do if type(args[i]) == "table" then output = output .. json.encode(args[i]) elseif type(args[i]) ~= "string" then output = output .. tostring(args[i]) else output = output .. args[i] end if i ~= #args then output = output .. " " end end print(prefix, text, output) end ---@param vehicle integer function JGDeleteVehicle(vehicle) if not DoesEntityExist(vehicle) then return end if GetResourceState("AdvancedParking") == "started" then exports["AdvancedParking"]:DeleteVehicle(vehicle, false) else DeleteEntity(vehicle) end end ---@param vehicle integer ---@param plate string function SetVehiclePlateText(vehicle, plate) if GetResourceState("AdvancedParking") == "started" then exports["AdvancedParking"]:UpdatePlate(vehicle, plate) else SetVehicleNumberPlateText(vehicle, plate) end end ---@param model string | number ---@return number hash function ConvertModelToHash(model) return type(model) == "string" and joaat(model) or model --[[@as number]] end ---@param plate string function IsValidGTAPlate(plate) -- Check if the plate matches the pattern and is not longer than 8 characters -- %w matches alphanumeric characters, %s matches space characters -- The pattern checks for a string starting with 0 or more alphanumeric or space characters if #plate <= 8 and plate:match("^[%w%s]*$") then return true end return false end ---@param table table function TableKeys(table) if not table then return {} end local keys = {} for k, _ in pairs(table) do keys[#keys+1] = k end return keys end -- Round a number to so-many decimal of places, -- Which can be negative, e.g. -1 places rounds to 10's ---@param num integer ---@param dp? integer ---@return number function Round(num, dp) dp = dp or 0 local mult = 10^(dp or 0) return math.floor(num * mult + 0.5) / mult end ---@param list table ---@param item string|number ---@return boolean function IsItemInList(list, item) if #list == 0 then return true end for _, value in ipairs(list) do if value == item then return true end end return false end ---@param s string ---@return string function Trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end function TableValues(table) local values = {} for _, val in pairs(table) do values[#values+1] = val end return values end ---Case-insensitive table key lookup ---@param tbl table The table to search ---@param key string The key to find (case-insensitive) ---@return any value The value if found, nil otherwise ---@return string|nil actualKey The actual key in the table if found function GetCaseInsensitive(tbl, key) if not tbl or not key then return nil, nil end -- Try exact match first if tbl[key] ~= nil then return tbl[key], key end -- Try case-insensitive match local lowerKey = string.lower(key) for k, v in pairs(tbl) do if type(k) == "string" and string.lower(k) == lowerKey then return v, k end end return nil, nil end ---Checks if a job/gang passes a whitelist ---@param whitelist JobGangWhitelist|nil ---@param name string|nil ---@param grade number|nil ---@return boolean function CheckJobGangWhitelist(whitelist, name, grade) if not whitelist or not next(whitelist) then return true end if not name or not grade then return false end local allowedGrades = whitelist[name] if not allowedGrades then return false end for _, allowedGrade in ipairs(allowedGrades) do if allowedGrade == grade then return true end end return false end