Main/resources/[standalone]/menuv/menuv/components/utilities.lua
2025-06-07 08:51:21 +02:00

427 lines
No EOL
14 KiB
Lua

----------------------- [ MenuV ] -----------------------
-- GitHub: https://github.com/ThymonA/menuv/
-- License: GNU General Public License v3.0
-- https://choosealicense.com/licenses/gpl-3.0/
-- Author: Thymon Arens <contact@arens.io>
-- Name: MenuV
-- Version: 1.4.1
-- Description: FiveM menu library for creating menu's
----------------------- [ MenuV ] -----------------------
local assert = assert
local type = assert(type)
local tonumber = assert(tonumber)
local tostring = assert(tostring)
local lower = assert(string.lower)
local upper = assert(string.upper)
local sub = assert(string.sub)
local encode = assert(json.encode)
local decode = assert(json.decode)
local floor = assert(math.floor)
local random = assert(math.random)
local randomseed = assert(math.randomseed)
local rawget = assert(rawget)
local setmetatable = assert(setmetatable)
--- FiveM globals
local GET_GAME_TIMER = assert(GetGameTimer)
local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName)
--- Utilities for MenuV
---@class Utilities
local Utilities = setmetatable({ __class = 'Utilities' }, {})
--- Returns `true` if `input` starts with `start`, otherwise `false`
---@param input string Checks if this string starts with `start`
---@param start string Checks if `input` starts with this
---@return boolean `true` if `input` starts with `start`, otherwise `false`
function Utilities:StartsWith(input, start)
if (self:Typeof(input) ~= 'string') then return false end
if (self:Typeof(start) == 'number') then start = tostring(start) end
if (self:Typeof(start) ~= 'string') then return false end
return sub(input, 1, #start) == start
end
--- Returns `true` if `input` ends with `ends`, otherwise `false`
---@param input string Checks if this string ends with `ends`
---@param ends string Checks if `input` ends with this
---@return boolean `true` if `input` ends with `ends`, otherwise `false`
function Utilities:EndsWith(input, ends)
if (self:Typeof(input) ~= 'string') then return false end
if (self:Typeof(ends) == 'number') then ends = tostring(ends) end
if (self:Typeof(ends) ~= 'string') then return false end
return sub(input, -#ends) == ends
end
--- Returns the type of given `input`
---@param input any Any input
---@return string Type of given input
function Utilities:Typeof(input)
if (input == nil) then return 'nil' end
local rawType = type(input) or 'nil'
if (rawType ~= 'table') then return rawType end
local isFXFunction = rawget(input, '__cfx_functionReference') ~= nil or
rawget(input, '__cfx_async_retval') ~= nil
if (isFXFunction) then return 'function' end
if (rawget(input, '__cfx_functionSource') ~= nil) then return 'number' end
local rawClass = rawget(input, '__class')
if (rawClass ~= nil) then return type(rawClass) == 'string' and rawClass or 'class' end
local rawTableType = rawget(input, '__type')
if (rawTableType ~= nil) then return type(rawTableType) == 'string' and rawTableType or 'table' end
return rawType
end
local INPUT_GROUPS = {
[0] = "KEYBOARD",
[2] = "CONTROLLER"
}
local INPUT_TYPE_GROUPS = {
["KEYBOARD"] = 0,
["MOUSE_ABSOLUTEAXIS"] = 0,
["MOUSE_CENTEREDAXIS"] = 0,
["MOUSE_RELATIVEAXIS"] = 0,
["MOUSE_SCALEDAXIS"] = 0,
["MOUSE_NORMALIZED"] = 0,
["MOUSE_WHEEL"] = 0,
["MOUSE_BUTTON"] = 0,
["MOUSE_BUTTONANY"] = 0,
["MKB_AXIS"] = 0,
["PAD_AXIS"] = 2,
["PAD_DIGITALBUTTON"] = 2,
["PAD_DIGITALBUTTONANY"] = 2,
["PAD_ANALOGBUTTON"] = 2,
["JOYSTICK_POV"] = 2,
["JOYSTICK_POV_AXIS"] = 2,
["JOYSTICK_BUTTON"] = 2,
["JOYSTICK_AXIS"] = 2,
["JOYSTICK_IAXIS"] = 2,
["JOYSTICK_AXIS_NEGATIVE"] = 2,
["JOYSTICK_AXIS_POSITIVE"] = 2,
["PAD_DEBUGBUTTON"] = 2,
["GAME_CONTROLLED"] = 2,
["DIGITALBUTTON_AXIS"] = 2,
}
function Utilities:GetInputTypeGroup(inputType)
return INPUT_TYPE_GROUPS[inputType] or 0
end
function Utilities:GetInputGroupName(inputTypeGroup)
return INPUT_GROUPS[inputTypeGroup] or "KEYBOARD"
end
--- Transform any `input` to the same type as `defaultValue`
---@type function
---@param input any Transform this `input` to `defaultValue`'s type
---@param defaultValue any Returns this if `input` can't transformed to this type
---@param ignoreDefault boolean Don't return default value if this is true
---@return any Returns `input` matches the `defaultValue` type or `defaultValue`
function Utilities:Ensure(input, defaultValue, ignoreDefault)
ignoreDefault = type(ignoreDefault) == 'boolean' and ignoreDefault or false
if (defaultValue == nil) then return nil end
local requiredType = self:Typeof(defaultValue)
if (requiredType == 'nil') then return nil end
local inputType = self:Typeof(input)
if (inputType == requiredType) then return input end
if (inputType == 'nil') then return defaultValue end
if (requiredType == 'number') then
if (inputType == 'boolean') then return input and 1 or 0 end
return tonumber(input) or (not ignoreDefault and defaultValue or nil)
end
if (requiredType == 'string') then
if (inputType == 'boolean') then return input and 'yes' or 'no' end
if (inputType == 'vector3') then return encode({ x = input.x, y = input.y, z = input.z }) or (not ignoreDefault and defaultValue or nil) end
if (inputType == 'vector2') then return encode({ x = input.x, y = input.y }) or (not ignoreDefault and defaultValue or nil) end
if (inputType == 'table') then return encode(input) or (not ignoreDefault and defaultValue or nil) end
local result = tostring(input)
if (result == 'nil') then
return not ignoreDefault and defaultValue or 'nil'
end
return result
end
if (requiredType == 'boolean') then
if (inputType == 'string') then
input = lower(input)
if (input == 'true' or input == '1' or input == 'yes' or input == 'y') then return true end
if (input == 'false' or input == '0' or input == 'no' or input == 'n') then return false end
return (not ignoreDefault and defaultValue or nil)
end
if (inputType == 'number') then
if (input == 1) then return true end
if (input == 0) then return false end
return (not ignoreDefault and defaultValue or nil)
end
return (not ignoreDefault and defaultValue or nil)
end
if (requiredType == 'table') then
if (inputType == 'string') then
if (self:StartsWith(input, '{') and self:EndsWith(input, '}')) then
return decode(input) or (not ignoreDefault and defaultValue or nil)
end
if (self:StartsWith(input, '[') and self:EndsWith(input, ']')) then
return decode(input) or (not ignoreDefault and defaultValue or nil)
end
return (not ignoreDefault and defaultValue or nil)
end
if (inputType == 'vector3') then return { x = input.x or 0, y = input.y or 0, z = input.z or 0 } end
if (inputType == 'vector2') then return { x = input.x or 0, y = input.y or 0 } end
return (not ignoreDefault and defaultValue or nil)
end
if (requiredType == 'vector3') then
if (inputType == 'table') then
local _x = self:Ensure(input.x, defaultValue.x)
local _y = self:Ensure(input.y, defaultValue.y)
local _z = self:Ensure(input.z, defaultValue.z)
return vector3(_x, _y, _z)
end
if (inputType == 'vector2') then
local _x = self:Ensure(input.x, defaultValue.x)
local _y = self:Ensure(input.y, defaultValue.y)
return vector3(_x, _y, 0)
end
if (inputType == 'number') then
return vector3(input, input, input)
end
return (not ignoreDefault and defaultValue or nil)
end
if (requiredType == 'vector2') then
if (inputType == 'table') then
local _x = self:Ensure(input.x, defaultValue.x)
local _y = self:Ensure(input.y, defaultValue.y)
return vector2(_x, _y)
end
if (inputType == 'vector3') then
local _x = self:Ensure(input.x, defaultValue.x)
local _y = self:Ensure(input.y, defaultValue.y)
return vector2(_x, _y)
end
if (inputType == 'number') then
return vector2(input, input)
end
return (not ignoreDefault and defaultValue or nil)
end
return (not ignoreDefault and defaultValue or nil)
end
--- Checks if input exists in inputs
--- '0' and 0 are both the same '0' == 0 equals `true`
--- 'yes' and true are both the same 'yes' == true equals `true`
---@param input any Any input
---@param inputs any[] Any table
---@param checkType string | "'value'" | "'key'" | "'both'"
---@return boolean Returns `true` if input has been found as `key` and/or `value`
function Utilities:Any(input, inputs, checkType)
if (input == nil) then return false end
if (inputs == nil) then return false end
inputs = self:Ensure(inputs, {})
checkType = lower(self:Ensure(checkType, 'value'))
local checkMethod = 1
if (checkType == 'value' or checkType == 'v') then
checkMethod = 1
elseif (checkType == 'key' or checkType == 'k') then
checkMethod = -1
elseif (checkType == 'both' or checkType == 'b') then
checkMethod = 0
end
for k, v in pairs(inputs) do
if (checkMethod == 0 or checkMethod == -1) then
local checkK = self:Ensure(input, k, true)
if (checkK ~= nil and checkK == k) then return true end
end
if (checkMethod == 0 or checkMethod == 1) then
local checkV = self:Ensure(input, v, true)
if (checkV ~= nil and checkV == v) then return true end
end
end
return false
end
--- Round any `value`
---@param value number Round this value
---@param decimal number Number of decimals
---@return number Rounded number
function Utilities:Round(value, decimal)
value = self:Ensure(value, 0)
decimal = self:Ensure(decimal, 0)
if (decimal > 0) then
return floor((value * 10 ^ decimal) + 0.5) / (10 ^ decimal)
end
return floor(value + 0.5)
end
--- Checks if `item1` equals `item2`
---@param item1 any Item1
---@param item2 any Item2
---@return boolean `true` if both are equal, otherwise `false`
function Utilities:Equal(item1, item2)
if (item1 == nil and item2 == nil) then return true end
if (item1 == nil or item2 == nil) then return false end
if (type(item1) == 'table') then
local item1EQ = rawget(item1, '__eq')
if (item1EQ ~= nil and self:Typeof(item1EQ) == 'function') then
return item1EQ(item1, item2)
end
return item1 == item2
end
if (type(item2) == 'table') then
local item2EQ = rawget(item2, '__eq')
if (item2EQ ~= nil and self:Typeof(item2EQ) == 'function') then
return item2EQ(item2, item1)
end
return item2 == item1
end
return item1 == item2
end
local function tohex(x)
x = Utilities:Ensure(x, 32)
local s, base, d = '', 16
while x > 0 do
d = x % base + 1
x = floor(x / base)
s = sub('0123456789abcdef', d, d) .. s
end
while #s < 2 do s = ('0%s'):format(s) end
return s
end
local function bitwise(x, y, matrix)
x = Utilities:Ensure(x, 32)
y = Utilities:Ensure(y, 16)
matrix = Utilities:Ensure(matrix, {{0,0}, {0, 1}})
local z, pow = 0, 1
while x > 0 or y > 0 do
z = z + (matrix[x %2 + 1][y %2 + 1] * pow)
pow = pow * 2
x = floor(x / 2)
y = floor(y / 2)
end
return z
end
--- Generates a random UUID like: 00000000-0000-0000-0000-000000000000
---@return string Random generated UUID
function Utilities:UUID()
randomseed(GET_GAME_TIMER() + random(30720, 92160))
---@type number[]
local bytes = {
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255),
random(0, 255)
}
bytes[7] = bitwise(bytes[7], 0x0f, {{0,0},{0,1}})
bytes[7] = bitwise(bytes[7], 0x40, {{0,1},{1,1}})
bytes[9] = bitwise(bytes[7], 0x3f, {{0,0},{0,1}})
bytes[9] = bitwise(bytes[7], 0x80, {{0,1},{1,1}})
return upper(('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s'):format(
tohex(bytes[1]), tohex(bytes[2]), tohex(bytes[3]), tohex(bytes[4]),
tohex(bytes[5]), tohex(bytes[6]),
tohex(bytes[7]), tohex(bytes[8]),
tohex(bytes[9]), tohex(bytes[10]),
tohex(bytes[11]), tohex(bytes[12]), tohex(bytes[13]), tohex(bytes[14]), tohex(bytes[15]), tohex(bytes[16])
))
end
--- Replace a string that contains `this` to `that`
---@param str string String where to replace in
---@param this string Word that's need to be replaced
---@param that string Replace `this` whit given string
---@return string String where `this` has been replaced with `that`
function Utilities:Replace(str, this, that)
local b, e = str:find(this, 1, true)
if b == nil then
return str
else
return str:sub(1, b - 1) .. that .. self:Replace(str:sub(e + 1), this, that)
end
end
_G.Utilities = Utilities
_ENV.Utilities = Utilities