ed
This commit is contained in:
parent
510e3ffcf2
commit
f43cf424cf
305 changed files with 34683 additions and 0 deletions
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue