This commit is contained in:
Nordi98 2025-08-06 16:37:06 +02:00
parent 510e3ffcf2
commit f43cf424cf
305 changed files with 34683 additions and 0 deletions

View file

@ -0,0 +1,640 @@
---@class Utility
Utility = Utility or {}
local blipIDs = {}
local spawnedPeds = {}
Locales = Locales or Require('modules/locales/shared.lua')
-- === Local Helpers ===
---Get the hash of a model (string or number)
---@param model string|number
---@return number
local function getModelHash(model)
if type(model) ~= 'number' then
return joaat(model)
end
return model
end
---Ensure a model is loaded into memory
---@param model string|number
---@return boolean, number
local function ensureModelLoaded(model)
local hash = getModelHash(model)
if not IsModelValid(hash) and not IsModelInCdimage(hash) then return false, hash end
RequestModel(hash)
local count = 0
while not HasModelLoaded(hash) and count < 30000 do
Wait(0)
count = count + 1
end
return HasModelLoaded(hash), hash
end
---Add a text entry if possible
---@param key string
---@param text string
local function addTextEntryOnce(key, text)
if not AddTextEntry then return end
AddTextEntry(key, text)
end
---Create a blip safely and store its reference
---@param coords vector3
---@param sprite number
---@param color number
---@param scale number
---@param label string
---@param shortRange boolean
---@param displayType number
---@return number
local function safeAddBlip(coords, sprite, color, scale, label, shortRange, displayType)
local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
SetBlipSprite(blip, sprite or 8)
SetBlipColour(blip, color or 3)
SetBlipScale(blip, scale or 0.8)
SetBlipDisplay(blip, displayType or 2)
SetBlipAsShortRange(blip, shortRange)
addTextEntryOnce(label, label)
BeginTextCommandSetBlipName(label)
EndTextCommandSetBlipName(blip)
table.insert(blipIDs, blip)
return blip
end
---Create a entiyty blip safely and store its reference
---@param entity number
---@param sprite number
---@param color number
---@param scale number
---@param label string
---@param shortRange boolean
---@param displayType number
---@return number
local function safeAddEntityBlip(entity, sprite, color, scale, label, shortRange, displayType)
local blip = AddBlipForEntity(entity)
SetBlipSprite(blip, sprite or 8)
SetBlipColour(blip, color or 3)
SetBlipScale(blip, scale or 0.8)
SetBlipDisplay(blip, displayType or 2)
SetBlipAsShortRange(blip, shortRange)
ShowHeadingIndicatorOnBlip(blip, true)
addTextEntryOnce(label, label)
BeginTextCommandSetBlipName(label)
EndTextCommandSetBlipName(blip)
table.insert(blipIDs, blip)
return blip
end
---Remove a blip safely from the stored list
---@param blip number
---@return boolean
local function safeRemoveBlip(blip)
for i, storedBlip in ipairs(blipIDs) do
if storedBlip == blip then
RemoveBlip(storedBlip)
table.remove(blipIDs, i)
return true
end
end
return false
end
---Add a text entry if possible (shortcut)
---@param text string
local function safeAddTextEntry(text)
if not AddTextEntry then return end
AddTextEntry(text, text)
end
-- === Public Utility Functions ===
---Create a prop with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@return number|nil
function Utility.CreateProp(model, coords, heading, networked)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, Prints and Prints.Error and Prints.Error("Model Has Not Loaded") end
local propEntity = CreateObject(hash, coords.x, coords.y, coords.z, networked, false, false)
SetEntityHeading(propEntity, heading)
SetModelAsNoLongerNeeded(hash)
return propEntity
end
---Get street and crossing names at given coordinates
---@param coords vector3
---@return string, string
function Utility.GetStreetNameAtCoords(coords)
local streetHash, crossingHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
return GetStreetNameFromHashKey(streetHash), GetStreetNameFromHashKey(crossingHash)
end
---Create a vehicle with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@return number|nil, table
function Utility.CreateVehicle(model, coords, heading, networked)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, {}, Prints and Prints.Error and Prints.Error("Model Has Not Loaded") end
local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, heading, networked, false)
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
SetVehicleNeedsToBeHotwired(vehicle, false)
SetVehRadioStation(vehicle, "OFF")
SetModelAsNoLongerNeeded(hash)
return vehicle, {
networkid = NetworkGetNetworkIdFromEntity(vehicle) or 0,
coords = GetEntityCoords(vehicle),
heading = GetEntityHeading(vehicle),
}
end
---Create a ped with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@param settings table|nil
---@return number|nil
function Utility.CreatePed(model, coords, heading, networked, settings)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, Prints and Prints.Error and Prints.Error("Model Has Not Loaded") end
local spawnedEntity = CreatePed(0, hash, coords.x, coords.y, coords.z, heading, networked, false)
SetModelAsNoLongerNeeded(hash)
table.insert(spawnedPeds, spawnedEntity)
return spawnedEntity
end
---Show a busy spinner with the given text
---@param text string
---@return boolean
function Utility.StartBusySpinner(text)
safeAddTextEntry(text)
BeginTextCommandBusyString(text)
AddTextComponentSubstringPlayerName(text)
EndTextCommandBusyString(0)
return true
end
---Stop the busy spinner if active
---@return boolean
function Utility.StopBusySpinner()
if BusyspinnerIsOn() then
BusyspinnerOff()
return true
end
return false
end
---Create a blip at the given coordinates
---@param coords vector3
---@param sprite number
---@param color number
---@param scale number
---@param label string
---@param shortRange boolean
---@param displayType number
---@return number
function Utility.CreateBlip(coords, sprite, color, scale, label, shortRange, displayType)
return safeAddBlip(coords, sprite, color, scale, label, shortRange, displayType)
end
---Create a blip on the provided entity
---@param entity number
---@param sprite number
---@param color number
---@param scale number
---@param label string
---@param shortRange boolean
---@param displayType number
---@return number
function Utility.CreateEntityBlip(entity, sprite, color, scale, label, shortRange, displayType)
return safeAddEntityBlip(entity, sprite, color, scale, label, shortRange, displayType)
end
---Remove a blip if it exists
---@param blip number
---@return boolean
function Utility.RemoveBlip(blip)
return safeRemoveBlip(blip)
end
---Load a model into memory
---@param model string|number
---@return boolean
function Utility.LoadModel(model)
local loaded = ensureModelLoaded(model)
return loaded
end
---Request an animation dictionary
---@param dict string
---@return boolean
function Utility.RequestAnimDict(dict)
RequestAnimDict(dict)
local count = 0
while not HasAnimDictLoaded(dict) and count < 30000 do
Wait(0)
count = count + 1
end
return HasAnimDictLoaded(dict)
end
---Remove a ped if it exists
---@param entity number
---@return boolean
function Utility.RemovePed(entity)
local success = false
if DoesEntityExist(entity) then
DeleteEntity(entity)
end
for i, storedEntity in ipairs(spawnedPeds) do
if storedEntity == entity then
table.remove(spawnedPeds, i)
success = true
break
end
end
return success
end
---Show a native input menu and return the result
---@param text string
---@param length number
---@return string|boolean
function Utility.NativeInputMenu(text, length)
local maxLength = Math and Math.Clamp and Math.Clamp(length, 1, 50) or math.min(math.max(length or 10, 1), 50)
local menuText = text or 'enter text'
safeAddTextEntry(menuText)
DisplayOnscreenKeyboard(1, menuText, "", "", "", "", "", maxLength)
while (UpdateOnscreenKeyboard() == 0) do
DisableAllControlActions(0)
Wait(0)
end
if (GetOnscreenKeyboardResult()) then
return GetOnscreenKeyboardResult()
end
return false
end
---Get the skin data of a ped
---@param entity number
---@return table
function Utility.GetEntitySkinData(entity)
local skinData = { clothing = {}, props = {} }
for i = 0, 11 do
skinData.clothing[i] = { GetPedDrawableVariation(entity, i), GetPedTextureVariation(entity, i) }
end
for i = 0, 13 do
skinData.props[i] = { GetPedPropIndex(entity, i), GetPedPropTextureIndex(entity, i) }
end
return skinData
end
---Apply skin data to a ped
---@param entity number
---@param skinData table
---@return boolean
function Utility.SetEntitySkinData(entity, skinData)
for i = 0, 11 do
SetPedComponentVariation(entity, i, skinData.clothing[i][1], skinData.clothing[i][2], 0)
end
for i = 0, 13 do
SetPedPropIndex(entity, i, skinData.props[i][1], skinData.props[i][2], 0)
end
return true
end
---Reload the player's skin and remove attached objects
---@return boolean
function Utility.ReloadSkin()
local skinData = Utility.GetEntitySkinData(cache.ped)
Utility.SetEntitySkinData(cache.ped, skinData)
for _, props in pairs(GetGamePool("CObject")) do
if IsEntityAttachedToEntity(cache.ped, props) then
SetEntityAsMissionEntity(props, true, true)
DeleteObject(props)
DeleteEntity(props)
end
end
return true
end
---Show a native help text
---@param text string
---@param duration number
function Utility.HelpText(text, duration)
safeAddTextEntry(text)
BeginTextCommandDisplayHelp(text)
EndTextCommandDisplayHelp(0, false, true, duration or 5000)
end
---Draw 3D help text in the world
---@param coords vector3
---@param text string
---@param scale number
function Utility.Draw3DHelpText(coords, text, scale)
local onScreen, x, y = GetScreenCoordFromWorldCoord(coords.x, coords.y, coords.z)
if onScreen then
SetTextScale(scale or 0.35, scale or 0.35)
SetTextFont(4)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
SetTextEntry("STRING")
SetTextCentre(1)
AddTextComponentString(text)
DrawText(x, y)
local factor = (string.len(text)) / 370
DrawRect(x, y + 0.0125, 0.015 + factor, 0.03, 41, 11, 41, 100)
end
end
---Show a native notification
---@param text string
function Utility.NotifyText(text)
safeAddTextEntry(text)
SetNotificationTextEntry(text)
DrawNotification(false, true)
end
---Teleport the player to given coordinates
---@param coords vector3
---@param conditionFunction function|nil
---@param afterTeleportFunction function|nil
function Utility.TeleportPlayer(coords, conditionFunction, afterTeleportFunction)
if conditionFunction ~= nil then
if not conditionFunction() then
return
end
end
DoScreenFadeOut(2500)
Wait(2500)
SetEntityCoords(cache.ped, coords.x, coords.y, coords.z, false, false, false, false)
if coords.w then
SetEntityHeading(cache.ped, coords.w)
end
FreezeEntityPosition(cache.ped, true)
local count = 0
while not HasCollisionLoadedAroundEntity(cache.ped) and count <= 30000 do
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
Wait(0)
count = count + 1
end
FreezeEntityPosition(cache.ped, false)
DoScreenFadeIn(1000)
if afterTeleportFunction ~= nil then
afterTeleportFunction()
end
end
---Get the hash from a model
---@param model string|number
---@return number
function Utility.GetEntityHashFromModel(model)
return getModelHash(model)
end
---Get the closest player to given coordinates
---@param coords vector3|nil
---@param distanceScope number|nil
---@param includeMe boolean|nil
---@return number, number, number
function Utility.GetClosestPlayer(coords, distanceScope, includeMe)
local players = GetActivePlayers()
local closestPlayer = 0
local selfPed = cache.ped
local selfCoords = coords or GetEntityCoords(cache.ped)
local closestDistance = distanceScope or 5
for _, player in ipairs(players) do
local playerPed = GetPlayerPed(player)
if includeMe or playerPed ~= selfPed then
local playerCoords = GetEntityCoords(playerPed)
local distance = #(selfCoords - playerCoords)
if closestDistance == -1 or distance < closestDistance then
closestPlayer = player
closestDistance = distance
end
end
end
return closestPlayer, closestDistance, GetPlayerServerId(closestPlayer)
end
---Get the closest vehicle to given coordinates
---@param coords vector3|nil
---@param distanceScope number|nil
---@param includePlayerVeh boolean|nil
---@return number|nil, vector3|nil, number|nil
function Utility.GetClosestVehicle(coords, distanceScope, includePlayerVeh)
local vehicleEntity = nil
local vehicleNetID = nil
local vehicleCoords = nil
local selfCoords = coords or GetEntityCoords(cache.ped)
local closestDistance = distanceScope or 5
local includeMyVeh = includePlayerVeh or false
local gamePoolVehicles = GetGamePool("CVehicle")
local playerVehicle = IsPedInAnyVehicle(cache.ped, false) and GetVehiclePedIsIn(cache.ped, false) or 0
for i = 1, #gamePoolVehicles do
local thisVehicle = gamePoolVehicles[i]
if DoesEntityExist(thisVehicle) and (includeMyVeh or thisVehicle ~= playerVehicle) then
local thisVehicleCoords = GetEntityCoords(thisVehicle)
local distance = #(selfCoords - thisVehicleCoords)
if closestDistance == -1 or distance < closestDistance then
vehicleEntity = thisVehicle
vehicleNetID = NetworkGetNetworkIdFromEntity(thisVehicle) or nil
vehicleCoords = thisVehicleCoords
closestDistance = distance
end
end
end
return vehicleEntity, vehicleCoords, vehicleNetID
end
-- Deprecated point functions (no changes)
function Utility.RegisterPoint(pointID, pointCoords, pointDistance, _onEnter, _onExit, _nearby)
return Point.Register(pointID, pointCoords, pointDistance, nil, _onEnter, _onExit, _nearby)
end
function Utility.GetPointById(pointID)
return Point.Get(pointID)
end
function Utility.GetActivePoints()
return Point.GetAll()
end
function Utility.RemovePoint(pointID)
return Point.Remove(pointID)
end
---Simple switch-case function
---@generic T
---@param value T The value to match against the cases
---@param cases table<T|false, fun(): any> Table with case functions and an optional default (false key)
---@return any|false result The return value of the matched case function, or false if none matched
function Utility.Switch(value, cases)
local caseFunc = cases[value] or cases[false]
if caseFunc and type(caseFunc) == "function" then
local ok, result = pcall(caseFunc)
return ok and result or false
end
return false
end
function Utility.CopyToClipboard(text)
if not text then return false end
if type(text) ~= "string" then
text = json.encode(text, { indent = true })
end
SendNUIMessage({
type = "copytoclipboard",
text = text
})
local message = Locales and Locales.Locale("clipboard.copy")
--TriggerEvent('community_bridge:Client:Notify', message, 'success')
return true
end
--- Pattern match-like function
---@generic T
---@param value T The value to match
---@param patterns table<T|fun(T):boolean|false, fun(): any> A list of matchers and their handlers
---@return any|false result The result of the first matched case, or false if none
function Utility.Match(value, patterns)
for pattern, handler in pairs(patterns) do
if type(pattern) == "function" then
local ok, matched = pcall(pattern, value)
if ok and matched then
local success, result = pcall(handler)
return success and result or false
end
elseif pattern == value then
local success, result = pcall(handler)
return success and result or false
end
end
if patterns[false] then
local ok, result = pcall(patterns[false])
return ok and result or false
end
return false
end
---Get zone name at coordinates
---@param coords vector3
---@return string
function Utility.GetZoneName(coords)
local zoneHash = GetNameOfZone(coords.x, coords.y, coords.z)
return GetLabelText(zoneHash)
end
local SpecialKeyCodes = {
['b_116'] = 'Scroll Up',
['b_115'] = 'Scroll Down',
['b_100'] = 'LMB',
['b_101'] = 'RMB',
['b_102'] = 'MMB',
['b_103'] = 'Extra 1',
['b_104'] = 'Extra 2',
['b_105'] = 'Extra 3',
['b_106'] = 'Extra 4',
['b_107'] = 'Extra 5',
['b_108'] = 'Extra 6',
['b_109'] = 'Extra 7',
['b_110'] = 'Extra 8',
['b_1015'] = 'AltLeft',
['b_1000'] = 'ShiftLeft',
['b_2000'] = 'Space',
['b_1013'] = 'ControlLeft',
['b_1002'] = 'Tab',
['b_1014'] = 'ControlRight',
['b_140'] = 'Numpad4',
['b_142'] = 'Numpad6',
['b_144'] = 'Numpad8',
['b_141'] = 'Numpad5',
['b_143'] = 'Numpad7',
['b_145'] = 'Numpad9',
['b_200'] = 'Insert',
['b_1012'] = 'CapsLock',
['b_170'] = 'F1',
['b_171'] = 'F2',
['b_172'] = 'F3',
['b_173'] = 'F4',
['b_174'] = 'F5',
['b_175'] = 'F6',
['b_176'] = 'F7',
['b_177'] = 'F8',
['b_178'] = 'F9',
['b_179'] = 'F10',
['b_180'] = 'F11',
['b_181'] = 'F12',
['b_194'] = 'ArrowUp',
['b_195'] = 'ArrowDown',
['b_196'] = 'ArrowLeft',
['b_197'] = 'ArrowRight',
['b_1003'] = 'Enter',
['b_1004'] = 'Backspace',
['b_198'] = 'Delete',
['b_199'] = 'Escape',
['b_1009'] = 'PageUp',
['b_1010'] = 'PageDown',
['b_1008'] = 'Home',
['b_131'] = 'NumpadAdd',
['b_130'] = 'NumpadSubstract',
['b_211'] = 'Insert',
['b_210'] = 'Delete',
['b_212'] = 'End',
['b_1055'] = 'Home',
['b_1056'] = 'PageUp',
}
local function translateKey(key)
if string.find(key, "t_") then
return string.gsub(key, "t_", "")
elseif SpecialKeyCodes[key] then
return SpecialKeyCodes[key]
else
return key
end
end
function Utility.GetCommandKey(commandName)
local hash = GetHashKey(commandName) | 0x80000000
local button = GetControlInstructionalButton(2, hash, true)
if not button or button == "" or button == "NULL" then
hash = GetHashKey(commandName)
button = GetControlInstructionalButton(2, hash, true)
end
return translateKey(button)
end
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
for _, blip in pairs(blipIDs) do
if blip and DoesBlipExist(blip) then
RemoveBlip(blip)
end
end
for _, ped in pairs(spawnedPeds) do
if ped and DoesEntityExist(ped) then
DeleteEntity(ped)
end
end
end)
exports('Utility', Utility)
return Utility

View file

@ -0,0 +1,143 @@
local Callback = {}
local CallbackRegistry = {}
-- Constants
local RESOURCE = GetCurrentResourceName() or 'unknown'
local EVENT_NAMES = {
CLIENT_TO_SERVER = RESOURCE .. ':CS:Callback',
SERVER_TO_CLIENT = RESOURCE .. ':SC:Callback',
CLIENT_RESPONSE = RESOURCE .. ':CSR:Callback',
SERVER_RESPONSE = RESOURCE .. ':SCR:Callback'
}
-- Utility functions
local function generateCallbackId(name)
return string.format('%s_%d', name, math.random(1000000, 9999999))
end
local function handleResponse(registry, name, callbackId, ...)
local data = registry[callbackId]
if not data then return end
if data.callback then
data.callback(...)
end
if data.promise then
data.promise:resolve({ ... })
end
registry[callbackId] = nil
end
local function triggerCallback(eventName, target, name, args, callback)
local callbackId = generateCallbackId(name)
local promise = promise.new()
CallbackRegistry[callbackId] = {
callback = callback,
promise = promise
}
if type(target) == 'table' then
for _, id in ipairs(target) do
TriggerClientEvent(eventName, tonumber(id), name, callbackId, table.unpack(args))
end
else
TriggerClientEvent(eventName, tonumber(target), name, callbackId, table.unpack(args))
end
if not callback then
local result = Citizen.Await(promise)
local returnResults = (result and type(result) == 'table' ) and result or {result}
return table.unpack(returnResults)
end
end
-- Server-side implementation
if IsDuplicityVersion() then
function Callback.Register(name, handler)
Callback[name] = handler
end
function Callback.Trigger(name, target, ...)
local args = { ... }
local callback = type(args[1]) == 'function' and table.remove(args, 1) or nil
return triggerCallback(EVENT_NAMES.SERVER_TO_CLIENT, target or -1, name, args, callback)
end
RegisterNetEvent(EVENT_NAMES.CLIENT_TO_SERVER, function(name, callbackId, ...)
if not name or not callbackId then return print(string.format("[%s] Warning: Invalid callback parameters - name: %s, callbackId: %s", RESOURCE, tostring(name), tostring(callbackId))) end
local handler = Callback[name]
if not handler then return end
local playerId = source
if not playerId or playerId == 0 then return print(string.format("[%s] Warning: Invalid source for callback '%s'", RESOURCE, name)) end
local result = table.pack(handler(playerId, ...))
TriggerClientEvent(EVENT_NAMES.CLIENT_RESPONSE, playerId, name, callbackId, table.unpack(result))
end)
RegisterNetEvent(EVENT_NAMES.SERVER_RESPONSE, function(name, callbackId, ...)
handleResponse(CallbackRegistry, name, callbackId, ...)
end)
-- Client-side implementation
else
local ClientCallbacks = {}
local ReboundCallbacks = {}
function Callback.Register(name, handler)
ClientCallbacks[name] = handler
end
function Callback.RegisterRebound(name, handler)
ReboundCallbacks[name] = handler
end
function Callback.Trigger(name, ...)
local args = { ... }
local callback = type(args[1]) == 'function' and table.remove(args, 1) or nil
local callbackId = generateCallbackId(name)
local promise = promise.new()
CallbackRegistry[callbackId] = {
callback = callback,
promise = promise
}
TriggerServerEvent(EVENT_NAMES.CLIENT_TO_SERVER, name, callbackId, table.unpack(args))
if not callback then
local result = Citizen.Await(promise)
return table.unpack(result)
end
end
RegisterNetEvent(EVENT_NAMES.CLIENT_RESPONSE, function(name, callbackId, ...)
if ReboundCallbacks[name] then
ReboundCallbacks[name](...)
end
handleResponse(CallbackRegistry, name, callbackId, ...)
end)
RegisterNetEvent(EVENT_NAMES.SERVER_TO_CLIENT, function(name, callbackId, ...)
local handler = ClientCallbacks[name]
if not handler then return end
local result = table.pack(handler(...))
TriggerServerEvent(EVENT_NAMES.SERVER_RESPONSE, name, callbackId, table.unpack(result))
end)
end
-- Exports
exports('Callback', Callback)
exports('RegisterCallback', Callback.Register)
exports('TriggerCallback', Callback.Trigger)
if not IsDuplicityVersion() then
exports('RegisterRebound', Callback.RegisterRebound)
end
return Callback

View file

@ -0,0 +1,72 @@
Ids = Ids or {}
---This will generate a unique id.
---@param tbl table | nil
---@param len number | nil
---@param pattern string | nil
---@return string
Ids.CreateUniqueId = function(tbl, len, pattern) -- both optional
tbl = tbl or {} -- table to check uniqueness. Ids to check against must be the key to the tables value
len = len or 8
local id = ""
for i = 1, len do
local char = ""
if pattern then
local charIndex = math.random(1, #pattern)
char = pattern:sub(charIndex, charIndex)
else
char = math.random(1, 2) == 1 and string.char(math.random(65, 90)) or math.random(0, 9) -- CAP letter and number
end
id = id .. char
end
if tbl[id] then
return Ids.CreateUniqueId(tbl, len, pattern)
end
return id
end
---This will generate a unique id.
---@param tbl table
---@param len number
---@return string
Ids.RandomUpper = function(tbl, len)
return Ids.CreateUniqueId(tbl, len, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
end
---This will generate a unique id.
---@param tbl table
---@param len number
---@return string
Ids.RandomLower = function(tbl, len)
return Ids.CreateUniqueId(tbl, len, "abcdefghijklmnopqrstuvwxyz")
end
---This will generate a unique id.
---@param tbl table
---@param len number
---@return string
Ids.RandomString = function(tbl, len)
return Ids.CreateUniqueId(tbl, len, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
end
---This will generate a unique id.
---@param tbl table
---@param len number
---@return string
Ids.RandomNumber = function(tbl, len)
return Ids.CreateUniqueId(tbl, len, "0123456789")
end
---This will generate a unique id.
---@param tbl table
---@param len number
---@return string
Ids.Random = function(tbl, len)
return Ids.CreateUniqueId(tbl, len)
end
exports("Ids", Ids)
return Ids

View file

@ -0,0 +1,532 @@
LA = LA or {}
--https://easings.net
LA.Lerp = function(a, b, t)
return a + (b - a) * t
end
LA.LerpVector = function(a, b, t)
return vector3(LA.Lerp(a.x, b.x, t), LA.Lerp(a.y, b.y, t), LA.Lerp(a.z, b.z, t))
end
LA.EaseInSine = function(t)
return 1 - math.cos((t * math.pi) / 2)
end
LA.EaseOutSine = function(t)
return math.sin((t * math.pi) / 2)
end
LA.EaseInOutSine = function(t)
return -(math.cos(math.pi * t) - 1) / 2
end
LA.EaseInCubic = function(t)
return t ^ 3
end
LA.EaseOutCubic = function(t)
return 1 - (1 - t) ^ 3
end
LA.EaseInOutCubic = function(t)
return t < 0.5 and 4 * t ^ 3 or 1 - ((-2 * t + 2) ^ 3) / 2
end
LA.EaseInQuint = function(t)
return t ^ 5
end
LA.EaseOutQuint = function(t)
return 1 - (1 - t) ^ 5
end
LA.EaseInOutQuint = function(t)
return t < 0.5 and 16 * t ^ 5 or 1 - ((-2 * t + 2) ^ 5) / 2
end
LA.EaseInCirc = function(t)
return 1 - math.sqrt(1 - t ^ 2)
end
LA.EaseOutCirc = function(t)
return math.sqrt(1 - (t - 1) ^ 2)
end
LA.EaseInOutCirc = function(t)
return t < 0.5 and (1 - math.sqrt(1 - (2 * t) ^ 2)) / 2 or (math.sqrt(1 - (-2 * t + 2) ^ 2) + 1) / 2
end
LA.EaseInElastic = function(t)
return t == 0 and 0 or t == 1 and 1 or -2 ^ (10 * t - 10) * math.sin((t * 10 - 10.75) * (2 * math.pi) / 3)
end
LA.EaseOutElastic = function(t)
return t == 0 and 0 or t == 1 and 1 or 2 ^ (-10 * t) * math.sin((t * 10 - 0.75) * (2 * math.pi) / 3) + 1
end
LA.EaseInOutElastic = function(t)
return t == 0 and 0 or t == 1 and 1 or t < 0.5 and -(2 ^ (20 * t - 10) * math.sin((20 * t - 11.125) * (2 * math.pi) / 4.5)) / 2 or (2 ^ (-20 * t + 10) * math.sin((20 * t - 11.125) * (2 * math.pi) / 4.5)) / 2 + 1
end
LA.EaseInQuad = function(t)
return t ^ 2
end
LA.EaseOutQuad = function(t)
return 1 - (1 - t) ^ 2
end
LA.EaseInOutQuad = function(t)
return t < 0.5 and 2 * t ^ 2 or 1 - (-2 * t + 2) ^ 2 / 2
end
LA.EaseInQuart = function(t)
return t ^ 4
end
LA.EaseOutQuart = function(t)
return 1 - (1 - t) ^ 4
end
LA.EaseInOutQuart = function(t)
return t < 0.5 and 8 * t ^ 4 or 1 - ((-2 * t + 2) ^ 4) / 2
end
LA.EaseInExpo = function(t)
return t == 0 and 0 or 2 ^ (10 * t - 10)
end
LA.EaseOutExpo = function(t)
return t == 1 and 1 or 1 - 2 ^ (-10 * t)
end
LA.EaseInOutExpo = function(t)
return t == 0 and 0 or t == 1 and 1 or t < 0.5 and 2 ^ (20 * t - 10) / 2 or (2 - 2 ^ (-20 * t + 10)) / 2
end
LA.EaseInBack = function(t)
return 2.70158 * t ^ 3 - 1.70158 * t ^ 2
end
LA.EaseOutBack = function(t)
return 1 + 2.70158 * (t - 1) ^ 3 + 1.70158 * (t - 1) ^ 2
end
LA.EaseInOutBack = function(t)
return t < 0.5 and (2 * t) ^ 2 * ((1.70158 + 1) * 2 * t - 1.70158) / 2 or ((2 * t - 2) ^ 2 * ((1.70158 + 1) * (t * 2 - 2) + 1.70158) + 2) / 2
end
LA.EaseInBounce = function(t)
print(1 - LA.EaseOutBounce(1 - t))
return 1 - LA.EaseOutBounce(1 - t)
end
LA.EaseOutBounce = function(t)
if t < 1 / 2.75 then
return 7.5625 * t ^ 2
elseif t < 2 / 2.75 then
return 7.5625 * (t - 1.5 / 2.75) ^ 2 + 0.75
elseif t < 2.5 / 2.75 then
return 7.5625 * (t - 2.25 / 2.75) ^ 2 + 0.9375
else
return 7.5625 * (t - 2.625 / 2.75) ^ 2 + 0.984375
end
end
LA.EaseInOutBounce = function(t)
return t < 0.5 and (1 - LA.EaseOutBounce(1 - 2 * t)) / 2 or (1 + LA.EaseOutBounce(2 * t - 1)) / 2
end
LA.EaseIn = function(t, easingType)
easingType = string.lower(easingType)
if easingType == "linear" then
return t
elseif easingType == "sine" then
return LA.EaseInSine(t)
elseif easingType == "cubic" then
return LA.EaseInCubic(t)
elseif easingType == "quint" then
return LA.EaseInQuint(t)
elseif easingType == "circ" then
return LA.EaseInCirc(t)
elseif easingType == "elastic" then
return LA.EaseInElastic(t)
elseif easingType == "quad" then
return LA.EaseInQuad(t)
elseif easingType == "quart" then
return LA.EaseInQuart(t)
elseif easingType == "expo" then
return LA.EaseInExpo(t)
elseif easingType == "back" then
return LA.EaseInBack(t)
elseif easingType == "bounce" then
return LA.EaseInBounce(t)
end
end
LA.EaseOut = function(t, easingType)
easingType = string.lower(easingType)
if easingType == "linear" then
return t
elseif easingType == "sine" then
return LA.EaseOutSine(t)
elseif easingType == "cubic" then
return LA.EaseOutCubic(t)
elseif easingType == "quint" then
return LA.EaseOutQuint(t)
elseif easingType == "circ" then
return LA.EaseOutCirc(t)
elseif easingType == "elastic" then
return LA.EaseOutElastic(t)
elseif easingType == "quad" then
return LA.EaseOutQuad(t)
elseif easingType == "quart" then
return LA.EaseOutQuart(t)
elseif easingType == "expo" then
return LA.EaseOutExpo(t)
elseif easingType == "back" then
return LA.EaseOutBack(t)
elseif easingType == "bounce" then
return LA.EaseOutBounce(t)
end
end
LA.EaseInOut = function(t, easingType)
easingType = string.lower(easingType)
if easingType == "linear" then
return t
elseif easingType == "sine" then
return LA.EaseInOutSine(t)
elseif easingType == "cubic" then
return LA.EaseInOutCubic(t)
elseif easingType == "quint" then
return LA.EaseInOutQuint(t)
elseif easingType == "circ" then
return LA.EaseInOutCirc(t)
elseif easingType == "elastic" then
return LA.EaseInOutElastic(t)
elseif easingType == "quad" then
return LA.EaseInOutQuad(t)
elseif easingType == "quart" then
return LA.EaseInOutQuart(t)
elseif easingType == "expo" then
return LA.EaseInOutExpo(t)
elseif easingType == "back" then
return LA.EaseInOutBack(t)
elseif easingType == "bounce" then
return LA.EaseInOutBounce(t)
end
end
LA.EaseInVector = function(a, b, t, easingType)
local tEase = LA.EaseIn(t, easingType)
local x, y, z = a.x, a.y, a.z
local x2, y2, z2 = b.x, b.y, b.z
local x3, y3, z3 = x2 - x, y2 - y, z2 - z
local x4, y4, z4 = x3 * tEase, y3 * tEase, z3 * tEase
local x5, y5, z5 = x + x4, y + y4, z + z4
return vector3(x5, y5, z5)
end
LA.EaseOutVector = function(a, b, t, easingType)
local tEase = LA.EaseOut(t, easingType)
local x, y, z = a.x, a.y, a.z
local x2, y2, z2 = b.x, b.y, b.z
local x3, y3, z3 = x2 - x, y2 - y, z2 - z
local x4, y4, z4 = x3 * tEase, y3 * tEase, z3 * tEase
local x5, y5, z5 = x + x4, y + y4, z + z4
return vector3(x5, y5, z5)
end
LA.EaseInOutVector = function(a, b, t, easingType)
local tEase = LA.EaseInOut(t, easingType)
local x, y, z = a.x, a.y, a.z
local x2, y2, z2 = b.x, b.y, b.z
local x3, y3, z3 = x2 - x, y2 - y, z2 - z
local x4, y4, z4 = x3 * tEase, y3 * tEase, z3 * tEase
local x5, y5, z5 = x + x4, y + y4, z + z4
return vector3(x5, y5, z5)
end
LA.EaseVector = function(inout, a, b, t, easingType)
inout = string.lower(inout)
if inout == "in" then
return LA.EaseInVector(a, b, t, easingType)
elseif inout == "out" then
return LA.EaseOutVector(a, b, t, easingType)
elseif inout == "inout" then
return LA.EaseInOutVector(a, b, t, easingType)
end
assert(false, "Invalid type")
end
LA.EaseOnAxis = function(inout, a, b, t, easingType, axis)
axis = axis or vector3(0, 0, 0)
local x, y, z = a.x, a.y, a.z
local increment = 0
if inout == "in" then
increment = LA.EaseIn(t, easingType)
elseif inout == "out" then
increment = LA.EaseOut(t, easingType)
elseif inout == "inout" then
increment = LA.EaseInOut(t, easingType)
end
return axis * increment
end
LA.TestCheck = function(point1, point2)
local dx = point1.x - point2.x
local dy = point1.y - point2.y
local dz = point1.z - point2.z
return math.sqrt(dx*dx + dy*dy + dz*dz)
end
LA.BoxZoneCheck = function(point, lower, upper)
local x1, y1, z1 = lower.x, lower.y, lower.z
local x2, y2, z2 = upper.x, upper.y, upper.z
return point.x > x1 and point.x < x2 and point.y > y1 and point.y < y2 and point.z > z1 and point.z < z2
end
LA.DistanceCheck = function(pointA, pointB)
return #(pointA - pointB) < 0.5
end
LA.Chance = function(chance)
assert(chance, "Chance must be passed")
assert(type(chance) == "number", "Chance must be a number")
assert(chance >= 0 and chance <= 100, "Chance must be between 0 and 100")
return math.random(1, 100) <= chance
end
LA.Vector4To3 = function(vector4)
assert(vector4, "Vector4 must be passed")
assert(type(vector4) == "vector4", "Vector4 must be a vector4")
return vector3(vector4.x, vector4.y, vector4.z), vector4.w
end
LA.Dot = function(vectorA, vectorB)
return vectorA.x * vectorB.x + vectorA.y * vectorB.y + vectorA.z * vectorB.z
end
LA.Length = function(vector)
return math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
end
LA.Normalize = function(vector)
local length = LA.Length(vector)
return vector3(vector.x / length, vector.y / length, vector.z / length)
end
LA.Create2DRotationMatrix = function(angle) -- angle in radians
local c = math.cos(angle)
local s = math.sin(angle)
return {
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
end
LA.Create3DAxisRotationMatrix = function(vector)
local yaw = vector.z
local pitch = vector.x
local roll = vector.y
local cy = math.cos(yaw)
local sy = math.sin(yaw)
local cp = math.cos(pitch)
local sp = math.sin(pitch)
local cr = math.cos(roll)
local sr = math.sin(roll)
local matrix = {
vector3(cp * cy, cp * sy, -sp),
vector3(cy * sp * sr - cr * sy, sy * sp * sr + cr * cy, cp * sr),
vector3(cy * sp * cr + sr * sy, cr * cy * sp - sr * sy, cp * cr)
}
return matrix
end
LA.Circle = function(t, radius, center)
local x = radius * math.cos(t) + center.x
local y = radius * math.sin(t) + center.y
return vector3(x, y, center.z)
end
LA.Clamp = function(value, min, max)
return math.min(math.max(value, min), max)
end
LA.CrossProduct = function(a, b)
local x = a.y * b.z - a.z * b.y
local y = a.z * b.x - a.x * b.z
local z = a.x * b.y - a.y * b.x
return vector3(x, y, z)
end
LA.CreateTranslateMatrix = function(x, y, z)
return {
1, 0, 0, x,
0, 1, 0, y,
0, 0, 1, z,
0, 0, 0, 1,
}
end
LA.MultiplyMatrix = function(m1, m2)
local result = {}
for i = 1, 4 do
for j = 1, 4 do
local sum = 0
for k = 1, 4 do
sum = sum + m1[(i - 1) * 4 + k] * m2[(k - 1) * 4 + j]
end
result[(i - 1) * 4 + j] = sum
end
end
return result
end
LA.MultiplyMatrixByVector = function(m, v)
local result = {}
for i = 1, 4 do
local sum = 0
for j = 1, 4 do
sum = sum + m[(i - 1) * 4 + j] * v[j]
end
result[i] = sum
end
return result
end
--Slines
LA.SplineLerp = function(nodes, start, endPos, t)
-- Ensure t is clamped between 0 and 1
t = LA.Clamp(t, 0, 1)
-- Find the two closest nodes around t
local prevNode, nextNode = nil, nil
for i = 1, #nodes - 1 do
if nodes[i].time <= t and nodes[i + 1].time >= t then
prevNode, nextNode = nodes[i], nodes[i + 1]
break
end
end
-- Edge cases: If t is before or after the defined range
if not prevNode then return start end
if not nextNode then return endPos end
-- Normalize t within the segment
local segmentT = (t - prevNode.time) / (nextNode.time - prevNode.time)
-- Interpolate the value (y-axis)
local smoothedT = LA.Lerp(prevNode.value, nextNode.value, segmentT)
-- Perform final Lerp using the interpolated smoothing factor
return LA.Lerp(start, endPos, smoothedT)
end
LA.SplineSmooth = function(nodes, points, t)
-- Ensure valid input
if #points < 2 then return points end
t = LA.Clamp(t, 0, 1)
local smoothedPoints = {}
for i = 1, #points - 1 do
local smoothedPos = LA.SplineLerp(nodes, points[i], points[i + 1], t)
table.insert(smoothedPoints, smoothedPos)
end
-- Ensure last point remains unchanged
table.insert(smoothedPoints, points[#points])
return smoothedPoints
end
LA.Spline = function(points, resolution)
-- Ensure valid input
if #points < 2 then return points end
-- Create a list of nodes
local nodes = {}
for i = 1, #points do
table.insert(nodes, { time = i / (#points - 1), value = i })
end
-- Smooth the points
local smoothedPoints = {}
for i = 0, 1, resolution do
local smoothedPos = LA.SplineSmooth(nodes, points, i)
table.insert(smoothedPoints, smoothedPos)
end
return smoothedPoints
end
LA.SplineCatmullRom = function(points, resolution)
-- Ensure valid input
if #points < 4 then return points end
-- Create a list of nodes
local nodes = {}
for i = 1, #points do
table.insert(nodes, { time = i / (#points - 1), value = i })
end
-- Smooth the points
local smoothedPoints = {}
for i = 0, 1, resolution do
local smoothedPos = LA.SplineSmooth(nodes, points, i)
table.insert(smoothedPoints, smoothedPos)
end
return smoothedPoints
end
exports('LA', LA)
return LA
-- local easingTypes = {
-- "linear",
-- "sine",
-- "cubic",
-- "quint",
-- "circ",
-- "elastic",
-- "quad",
-- "quart",
-- "expo",
-- "back",
-- "bounce"
-- }
--local inout = {
-- "in",
-- "out",
-- "inout"
--}
-- LA.LerpAngle = function(a, b, t)
-- local num = math.abs(b - a) % 360
-- local num2 = 360 - num
-- if num < num2 then
-- return a + num * t
-- else
-- return a - num2 * t
-- end
-- end
-- LA.LerpVectorAngle = function(a, b, t)
-- local x, y, z = a.x, a.y, a.z
-- local x2, y2, z2 = b.x, b.y, b.z
-- local x3, y3, z3 = LA.LerpAngle(x, x2, t), LA.LerpAngle(y, y2, t), LA.LerpAngle(z, z2, t)
-- return vector3(x3, y3, z3)
-- end
-- return LA

View file

@ -0,0 +1,146 @@
Math = Math or {}
function Math.Clamp(value, min, max)
return math.min(math.max(value, min), max)
end
function Math.Remap(value, min, max, newMin, newMax)
return newMin + (value - min) / (max - min) * (newMax - newMin)
end
function Math.PointInRadius(radius)
local angle = math.rad(math.random(0, 360))
return vector2(radius * math.cos(angle), radius * math.sin(angle))
end
function Math.Normalize(value, min, max)
if max == min then return 0 end -- Avoid division by zero
return (value - min) / (max - min)
end
function Math.Normalize2D(x, y)
if type(x) == "vector2" then
x, y = x.x, x.y
end
local length = math.sqrt(x*x + y*y)
return length ~= 0 and vector2(x / length, y / length) or vector2(0, 0)
end
function Math.Normalize3D(x, y, z)
if type(x) == "vector3" then
x, y, z = x.x, x.y, x.z
end
local length = math.sqrt(x*x + y*y + z*z)
return length ~= 0 and vector3(x / length, y / length, z / length) or vector3(0, 0, 0)
end
function Math.Normalize4D(x, y, z, w)
if type(x) == "vector4" then
x, y, z, w = x.x, x.y, x.z, x.w
end
local length = math.sqrt(x*x + y*y + z*z + w*w)
return length ~= 0 and vector4(x / length, y / length, z / length, w / length) or vector4(0, 0, 0, 0)
end
function Math.DirectionToTarget(fromV3, toV3)
return Math.Normalize3D(toV3.x - fromV3.x, toV3.y - fromV3.y, toV3.z - fromV3.z)
end
function Deg2Rad(deg)
return deg * math.pi / 180.0
end
function RotVector(pos, rot)
local pitch = Deg2Rad(rot.x)
local roll = Deg2Rad(rot.y)
local yaw = Deg2Rad(rot.z)
local cosY = math.cos(yaw)
local sinY = math.sin(yaw)
local cosP = math.cos(pitch)
local sinP = math.sin(pitch)
local cosR = math.cos(roll)
local sinR = math.sin(roll)
local m11 = cosY * cosR + sinY * sinP * sinR
local m12 = sinR * cosP
local m13 = -sinY * cosR + cosY * sinP * sinR
local m21 = -cosY * sinR + sinY * sinP * cosR
local m22 = cosR * cosP
local m23 = sinR * sinY + cosY * sinP * cosR
local m31 = sinY * cosP
local m32 = -sinP
local m33 = cosY * cosP
return vector3(pos.x * m11 + pos.y * m21 + pos.z * m31, pos.x * m12 + pos.y * m22 + pos.z * m32, pos.x * m13 + pos.y * m23 + pos.z * m33)
end
function Math.GetOffsetFromMatrix(position, rotation, offset)
local rotated = RotVector(offset, rotation)
print("Rotated: " .. tostring(rotated))
return position + rotated
end
function Math.InBoundary(pos, boundary)
if not boundary then return true end
local x, y, z = table.unpack(pos)
-- Handle legacy min/max boundary format for backwards compatibility
if boundary.min and boundary.max then
local minX, minY, minZ = table.unpack(boundary.min)
local maxX, maxY, maxZ = table.unpack(boundary.max)
return x >= minX and x <= maxX and y >= minY and y <= maxY and z >= minZ and z <= maxZ
end
-- Handle list of points (polygon boundary)
if boundary.points and #boundary.points > 0 then
local points = boundary.points
local minZ = boundary.minZ or -math.huge
local maxZ = boundary.maxZ or math.huge
-- Check Z bounds first
if z < minZ or z > maxZ then
return false
end
-- Point-in-polygon test using ray casting algorithm (improved version)
local inside = false
local n = #points
for i = 1, n do
local j = i == n and 1 or i + 1 -- Next point (wrap around)
local xi, yi = points[i].x or points[i][1], points[i].y or points[i][2]
local xj, yj = points[j].x or points[j][1], points[j].y or points[j][2]
-- Ensure xi, yi, xj, yj are numbers
if not (xi and yi and xj and yj) then
goto continue
end
-- Ray casting test
if ((yi > y) ~= (yj > y)) then
-- Calculate intersection point
local intersect = (xj - xi) * (y - yi) / (yj - yi) + xi
if x < intersect then
inside = not inside
end
end
::continue::
end
return inside
end
-- Fallback to true if boundary format is not recognized
return true
end
exports('Math', Math)
return Math

View file

@ -0,0 +1,110 @@
local Perlin = {}
-- Permutation table for random gradients
local perm = {}
local grad3 = {
{1,1,0}, {-1,1,0}, {1,-1,0}, {-1,-1,0},
{1,0,1}, {-1,0,1}, {1,0,-1}, {-1,0,-1},
{0,1,1}, {0,-1,1}, {0,1,-1}, {0,-1,-1}
}
-- Initialize permutation table
local function ShufflePermutation()
local p = {}
for i = 0, 255 do
p[i] = i
end
for i = 255, 1, -1 do
local j = math.random(i + 1) - 1
p[i], p[j] = p[j], p[i]
end
for i = 0, 255 do
perm[i] = p[i]
perm[i + 256] = p[i] -- Repeat for wrapping
end
end
ShufflePermutation() -- Randomize on load
-- Dot product helper
local function Dot(g, x, y, z)
return g[1] * x + g[2] * y + (z and g[3] * z or 0)
end
-- Fade function (smootherstep)
local function Fade(t)
return t * t * t * (t * (t * 6 - 15) + 10)
end
-- Linear interpolation
local function Lerp(a, b, t)
return a + (b - a) * t
end
-- **Perlin Noise 1D**
function Perlin.Noise1D(x)
local X = math.floor(x) & 255
x = x - math.floor(x)
local u = Fade(x)
local a = perm[X]
local b = perm[X + 1]
return Lerp(a, b, u) * (2 / 255) - 1
end
-- **Perlin Noise 2D**
function Perlin.Noise2D(x, y)
local X = math.floor(x) & 255
local Y = math.floor(y) & 255
x, y = x - math.floor(x), y - math.floor(y)
local u, v = Fade(x), Fade(y)
local aa = perm[X] + Y
local ab = perm[X] + Y + 1
local ba = perm[X + 1] + Y
local bb = perm[X + 1] + Y + 1
return Lerp(
Lerp(Dot(grad3[perm[aa] % 12 + 1], x, y), Dot(grad3[perm[ba] % 12 + 1], x - 1, y), u),
Lerp(Dot(grad3[perm[ab] % 12 + 1], x, y - 1), Dot(grad3[perm[bb] % 12 + 1], x - 1, y - 1), u),
v
)
end
-- **Perlin Noise 3D**
function Perlin.Noise3D(x, y, z)
local X = math.floor(x) & 255
local Y = math.floor(y) & 255
local Z = math.floor(z) & 255
x, y, z = x - math.floor(x), y - math.floor(y), z - math.floor(z)
local u, v, w = Fade(x), Fade(y), Fade(z)
local aaa = perm[X] + Y + Z
local aba = perm[X] + Y + Z + 1
local aab = perm[X] + Y + 1 + Z
local abb = perm[X] + Y + 1 + Z + 1
local baa = perm[X + 1] + Y + Z
local bba = perm[X + 1] + Y + Z + 1
local bab = perm[X + 1] + Y + 1 + Z
local bbb = perm[X + 1] + Y + 1 + Z + 1
return Lerp(
Lerp(
Lerp(Dot(grad3[perm[aaa] % 12 + 1], x, y, z), Dot(grad3[perm[baa] % 12 + 1], x - 1, y, z), u),
Lerp(Dot(grad3[perm[aab] % 12 + 1], x, y - 1, z), Dot(grad3[perm[bab] % 12 + 1], x - 1, y - 1, z), u),
v
),
Lerp(
Lerp(Dot(grad3[perm[aba] % 12 + 1], x, y, z - 1), Dot(grad3[perm[bba] % 12 + 1], x - 1, y, z - 1), u),
Lerp(Dot(grad3[perm[abb] % 12 + 1], x, y - 1, z - 1), Dot(grad3[perm[bbb] % 12 + 1], x - 1, y - 1, z - 1), u),
v
),
w
)
end
exports('Perlin', Perlin)
return Perlin

View file

@ -0,0 +1,34 @@
Prints = Prints or {}
local function printMessage(level, color, message)
if type(message) == 'table' then
message = json.encode(message)
end
print(color .. '[' .. level .. ']:', message)
end
---This will print a colored message to the console with the designated prefix.
---@param message string
Prints.Info = function(message)
printMessage('INFO', '^5', message)
end
---This will print a colored message to the console with the designated prefix.
---@param message string
Prints.Warn = function(message)
printMessage('WARN', '^3', message)
end
---This will print a colored message to the console with the designated prefix.
---@param message string
Prints.Error = function(message)
printMessage('ERROR', '^1', message)
end
---This will print a colored message to the console with the designated prefix.
---@param message string
Prints.Debug = function(message)
printMessage('DEBUG', '^2', message)
end
return Prints

View file

@ -0,0 +1,324 @@
local Entities = {}
local isServer = IsDuplicityVersion()
local registeredFunctions = {}
ReboundEntities = {}
function ReboundEntities.AddFunction(func)
assert(func, "Func is nil")
table.insert(registeredFunctions, func)
return #registeredFunctions
end
function ReboundEntities.RemoveFunction(id)
assert(id and registeredFunctions[id], "Invalid ID")
registeredFunctions[id] = false
end
function FireFunctions(...)
for _, func in pairs(registeredFunctions) do
func(...)
end
end
function ReboundEntities.Register(entityData)
assert(entityData and entityData.position, "Invalid entity data")
entityData.id = entityData.id or Ids.CreateUniqueId(Entities)
entityData.isServer = isServer
setmetatable(entityData, { __tostring = function() return entityData.id end })
Entities[entityData.id] = entityData
FireFunctions(entityData)
return entityData
end
local function Unregister(id)
Entities[id] = nil
end
function ReboundEntities.GetSyncData(entityData, key)
return entityData[key]
end
function ReboundEntities.GetAll()
return Entities
end
function ReboundEntities.GetById(id)
return Entities[tostring(id)]
end
function ReboundEntities.GetByModel(model)
local entities = {}
for _, entity in pairs(Entities) do
if entity.model == model then
table.insert(entities, entity)
end
end
return entities
end
function ReboundEntities.GetClosest(pos)
local closest, closestDist = nil, 9999
for _, entity in pairs(Entities) do
local dist = #(pos - entity.position)
if dist < closestDist then
closest, closestDist = entity, dist
end
end
return closest
end
function ReboundEntities.GetWithinRadius(pos, radius)
local entities = {}
for _, entity in pairs(Entities) do
if #(pos - entity.position) < radius then
table.insert(entities, entity)
end
end
return entities
end
function ReboundEntities.SetOnSyncKeyChange(entityData, cb)
local data = ReboundEntities.GetById(entityData.id or entityData)
assert(data, "Entity not found")
data.onSyncKeyChange = cb
end
exports('ReboundEntities', ReboundEntities)
if not isServer then goto client end
function ReboundEntities.Create(entityData, src)
local entity = ReboundEntities.Register(entityData)
assert(entity and entity.position, "Invalid entity data")
entity.rotation = entity.rotation or vector3(0, 0, entityData.heading or 0)
TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", src or -1, entity)
return entity
end
function ReboundEntities.SetCheckRestricted(entityData, cb)
assert(type(cb) == "function", "Check restricted is not a function")
entityData.restricted = cb
end
function ReboundEntities.CheckRestricted(src, entityData)
return entityData.restricted and entityData.restricted(tonumber(src), entityData) or false
end
function ReboundEntities.Refresh(src)
TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", tonumber(src), ReboundEntities.GetAccessibleList(src))
end
function ReboundEntities.Delete(id)
Unregister(id)
TriggerClientEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", -1, id)
end
function ReboundEntities.DeleteMultiple(entityDatas)
local ids = {}
for _, data in pairs(entityDatas) do
Unregister(data.id)
table.insert(ids, data.id)
end
TriggerClientEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntities", -1, ids)
end
function ReboundEntities.CreateMultiple(entityDatas, src, restricted)
local _entityDatas = {}
for _, data in pairs(entityDatas) do
local entity = ReboundEntities.Register(data)
assert(entity and entity.position, "Invalid entity data")
entity.rotation = entity.rotation or vector3(0, 0, 0)
if restricted then ReboundEntities.SetCheckRestricted(entity, restricted) end
if not ReboundEntities.CheckRestricted(src, entity) then
_entityDatas[entity.id] = entity
end
end
TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", src or -1, _entityDatas)
return _entityDatas
end
function ReboundEntities.SetSyncData(entityData, key, value)
entityData[key] = value
if entityData.onSyncKeyChange then
entityData.onSyncKeyChange(entityData, key, value)
end
TriggerClientEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", -1, entityData.id, key, value)
end
function ReboundEntities.GetAccessibleList(src)
local list = {}
for _, data in pairs(ReboundEntities.GetAll()) do
if not ReboundEntities.CheckRestricted(src, data) then
list[data.id] = data
end
end
return list
end
RegisterNetEvent("playerJoining", function()
ReboundEntities.Refresh(source)
end)
AddEventHandler('onResourceStart', function(resource)
if resource ~= GetCurrentResourceName() then return end
Wait(1000)
for _, src in pairs(GetPlayers()) do
ReboundEntities.Refresh(src)
end
end)
::client::
if isServer then return ReboundEntities end
function ReboundEntities.LoadModel(model)
assert(model, "Model is nil")
model = type(model) == "number" and model or GetHashKey(model) -- Corrected to GetHashKey
RequestModel(model)
for i = 1, 100 do
if HasModelLoaded(model) then return model end
Wait(100)
end
error(string.format("Failed to load model %s", model))
end
function ReboundEntities.Spawn(entityData)
local model = entityData.model
local position = entityData.position
assert(position, "Position is nil")
local rotation = entityData.rotation or vector3(0, 0, 0)
local entity = model and CreateObject(ReboundEntities.LoadModel(model), position.x, position.y, position.z, false, false, false)
if entity then SetEntityRotation(entity, rotation.x, rotation.y, rotation.z) end
if entityData.onSpawn then entity = entityData.onSpawn(entityData, entity) or entity end
return entity, true
end
function ReboundEntities.SetOnSpawn(id, cb)
local entity = ReboundEntities.GetById(tostring(id))
assert(entity, "Entity not found")
entity.onSpawn = cb
return true
end
function ReboundEntities.Despawn(entityData)
local entity = entityData.entity
if entityData.onDespawn then entityData.onDespawn(entityData, entity) end
if entity and DoesEntityExist(entity) then DeleteEntity(entity) end
return nil, nil
end
function ReboundEntities.SetOnDespawn(id, cb)
local entity = ReboundEntities.GetById(tostring(id))
assert(entity, "Entity not found")
entity.onDespawn = cb
return true
end
local spawnLoopRunning = false
function ReboundEntities.SpawnLoop(distanceToCheck, waitTime)
if spawnLoopRunning then return end
spawnLoopRunning = true
distanceToCheck = distanceToCheck or 50
waitTime = waitTime or 2500
CreateThread(function()
while spawnLoopRunning do
for _, entity in pairs(Entities) do
local pos = GetEntityCoords(PlayerPedId())
local position = vector3(entity.position.x, entity.position.y, entity.position.z)
local dist = #(pos - position)
if dist <= distanceToCheck and not entity.entity then
entity.entity, entity.inRange = ReboundEntities.Spawn(entity)
elseif dist > distanceToCheck and entity.entity then
entity.entity, entity.inRange = ReboundEntities.Despawn(entity)
end
end
Wait(waitTime)
end
end)
end
function ReboundEntities.GetByEntity(entity)
for _, data in pairs(Entities) do
if data.entity == entity then return data end
end
end
function ReboundEntities.CreateClient(entityData)
local entity = ReboundEntities.Register(entityData)
if entity then ReboundEntities.SpawnLoop() end
return entity
end
function ReboundEntities.DeleteClient(id)
local entityData = ReboundEntities.GetById(tostring(id))
assert(not entityData.isServer, "Cannot delete server entity from client")
if entityData.entity then ReboundEntities.Despawn(entityData) end
Unregister(id)
return true
end
function ReboundEntities.CreateMultipleClient(entityDatas)
local _entityDatas = {}
for _, data in pairs(entityDatas) do
local entityData = ReboundEntities.CreateClient(data)
_entityDatas[entityData.id] = entityData
end
return _entityDatas
end
function ReboundEntities.DeleteMultipleClient(ids)
for _, id in pairs(ids) do
ReboundEntities.DeleteClient(id)
end
end
local function DeleteFromServer(id)
local entityData = ReboundEntities.GetById(tostring(id))
if entityData then
entityData.isServer = nil
ReboundEntities.DeleteClient(id)
end
end
local function DeleteMultipleFromServer(ids)
for _, id in pairs(ids) do
DeleteFromServer(id)
end
end
RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", function(entityData)
ReboundEntities.CreateClient(entityData)
end)
RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", function(id)
DeleteFromServer(id)
end)
RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", function(entityDatas)
ReboundEntities.CreateMultipleClient(entityDatas)
end)
RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntities", function(ids)
ReboundEntities.DeleteMultipleClient(ids)
end)
RegisterNetEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", function(id, key, value)
local entityData = ReboundEntities.GetById(id)
if not entityData then return end
entityData[key] = value
if entityData.onSyncKeyChange then
entityData.onSyncKeyChange(entityData, key, value)
end
end)
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
spawnLoopRunning = false
for _, entity in pairs(Entities) do
if entity.entity then DeleteEntity(tonumber(entity.entity)) end
end
end)
return ReboundEntities

View file

@ -0,0 +1,45 @@
local ReboundEntities = ReboundEntities or Require("lib/utility/shared/rebound_entities.lua") -- Fixed path
local Action = Action or Require("lib/entities/shared/actions.lua") -- Fixed path and name
local LA = LA or Require("lib/utility/shared/la.lua") -- Fixed path
REA = {}
if not IsDuplicityVersion() then goto client end
if IsDuplicityVersion() then return end
::client::
local ActionsInProgress = {}
Action.Create("LerpToPosition", function(reboundId, start, position, duration, shouldRepeat, overrideStartTime)
local re = ReboundEntities.GetById(reboundId) -- Use GetById
assert(re, "Rebound entity not found")
local entity = re and re.entity
if not entity or not DoesEntityExist(entity) then return end -- Check existence
local startTime = GetGameTimer()
local endTime = overrideStartTime or startTime + duration
ActionsInProgress[reboundId] = {reboundId, start, position, duration, shouldRepeat, startTime}
local t = 0
CreateThread(function()
while shouldRepeat or t < 1 do
-- Check if entity still exists
if not re.entity or not DoesEntityExist(re.entity) then
ActionsInProgress[reboundId] = nil
break
end
entity = re.entity -- Update entity handle in case it changed (unlikely but safe)
t = (GetGameTimer() - startTime) / duration
local lerpPosition = LA.LerpVector(start, position, t)
SetEntityCoordsNoOffset(entity, lerpPosition.x, lerpPosition.y, lerpPosition.z, false, false, false) -- Use NoOffset
Wait(0)
end
-- Set final position if not repeating
if not shouldRepeat and re.entity and DoesEntityExist(re.entity) then
SetEntityCoordsNoOffset(re.entity, position.x, position.y, position.z, false, false, false)
end
ActionsInProgress[reboundId] = nil -- Clear when done
end)
end)

View file

@ -0,0 +1,138 @@
Table = {}
Table.CheckPopulated = function(tbl)
if #tbl == 0 then
for _, _ in pairs(tbl) do
return true
end
return false
end
return true
end
Table.DeepClone = function(tbl, out, omit)
if type(tbl) ~= "table" then return tbl end
local new = out or {}
omit = omit or {}
for key, data in pairs(tbl) do
if not omit[key] then
if type(data) == "table" then
new[key] = Table.DeepClone(data)
else
new[key] = data
end
end
end
return new
end
Table.TableContains = function(tbl, search, nested)
for _, v in pairs(tbl) do
if nested and type(v) == "table" then
return Table.TableContains(v, search)
elseif v == search then
return true, v
end
end
return false
end
Table.TableContainsKey = function(tbl, search)
for k, _ in pairs(tbl) do
if k == search then
return true, k
end
end
return false
end
Table.TableGetKeys = function(tbl)
local keys = {}
for k ,_ in pairs(tbl) do
table.insert(keys,k)
end
return keys
end
Table.GetClosest = function(coords, tbl)
local closestPoint = nil
local dist = math.huge
for k, v in pairs(tbl) do
local c = v.coords
local d = c and #(coords - c)
if d < dist then
dist = d
closestPoint = v
end
end
return closestPoint
end
Table.FindFirstUnoccupiedSlot = function(tbl)
local occupiedSlots = {}
for _, v in pairs(tbl) do
if v.slot then
occupiedSlots[v.slot] = true
end
end
for i = 1, BridgeServerConfig.MaxInventorySlots do
if not occupiedSlots[i] then
return i
end
end
return nil
end
Table.Append = function(tbl1, tbl2)
for _, v in pairs(tbl2) do
table.insert(tbl1, v)
end
return tbl1
end
Table.Split = function(tbl, size)
local new1 = {}
local new2 = {}
size = size or math.floor(#tbl / 2)
if size > #tbl then
assert(false, "Size is greater than the length of the table.")
end
for i = 1, size do
table.insert(new1, tbl[i])
end
for i = size + 1, #tbl do
table.insert(new2, tbl[i])
end
return new1, new2
end
Table.Shuffle = function(tbl)
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
return tbl
end
Table.Compare = function(a, b)
if type(a) == "table" then
for k, v in pairs(a) do
if not Table.Compare(v, b[k]) then return false end
end
return true
else
return a == b
end
end
Table.Count = function(tbl)
local count = 0
for _ in pairs(tbl) do
count = count + 1
end
return count
end
exports("Table", Table)
return Table