640 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			640 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| ---@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
 | 
