634 lines
No EOL
21 KiB
Lua
634 lines
No EOL
21 KiB
Lua
--[[
|
|
-- Author: Tim Plate
|
|
-- Project: Advanced Roleplay Environment
|
|
-- Copyright (c) 2022 Tim Plate Solutions
|
|
--]]
|
|
|
|
--- Client extension module for Advanced Roleplay Environment.
|
|
|
|
local progressBarPool = {}
|
|
|
|
-- Notification
|
|
|
|
---Shows a notification
|
|
---@param text any
|
|
function ShowNotification(text)
|
|
LogDebug(true, "ShowNotification: ", text)
|
|
BeginTextCommandThefeedPost("STRING")
|
|
AddTextComponentString(text)
|
|
EndTextCommandThefeedPostTicker(false, false)
|
|
end
|
|
|
|
-- ClosestPlayer
|
|
---Gets all players
|
|
---@return table
|
|
function GetPlayers()
|
|
local players = {}
|
|
|
|
for _, player in ipairs(GetActivePlayers()) do
|
|
local ped = GetPlayerPed(player)
|
|
|
|
if DoesEntityExist(ped) then
|
|
table.insert(players, player)
|
|
end
|
|
end
|
|
|
|
return players
|
|
end
|
|
|
|
---Gets the closest player
|
|
---@return integer
|
|
---@return number
|
|
function GetClosestPlayer()
|
|
local players = GetPlayers()
|
|
local closestDistance = -1
|
|
local closestPlayer = -1
|
|
local playerId = PlayerId()
|
|
|
|
for i=1, #players, 1 do
|
|
local target = GetPlayerPed(players[i])
|
|
|
|
if players[i] ~= playerId then
|
|
local targetCoords = GetEntityCoords(target)
|
|
local distance = GetDistanceBetweenCoords(targetCoords, ClientData.coords, true)
|
|
|
|
if closestDistance == -1 or closestDistance > distance then
|
|
closestPlayer = players[i]
|
|
closestDistance = distance
|
|
end
|
|
end
|
|
end
|
|
|
|
return closestPlayer, closestDistance
|
|
end
|
|
|
|
-- Animations
|
|
|
|
-- Source: https://github.com/zaphosting/esx/blob/master/es_extended/client/modules/streaming.lua#L59
|
|
---Requests an animation dictionary
|
|
---@param animDict any
|
|
---@param cb any
|
|
function RequestAnimDictCustom(animDict, cb)
|
|
if not HasAnimDictLoaded(animDict) then
|
|
RequestAnimDict(animDict)
|
|
|
|
while not HasAnimDictLoaded(animDict) do
|
|
Citizen.Wait(1)
|
|
end
|
|
end
|
|
|
|
if cb ~= nil then
|
|
cb()
|
|
end
|
|
end
|
|
|
|
---Runs an animation
|
|
---@param animation any
|
|
---@param duration any
|
|
function RunAnimation(animation, duration)
|
|
RequestAnimDictCustom(animation.lib, function()
|
|
TaskPlayAnim(ClientData.ped, animation.lib, animation.name, 8.0, -8.0, -1, 9, 0, false, false, false)
|
|
|
|
local gameTimer = GetGameTimer()
|
|
while true do
|
|
if GetGameTimer() - gameTimer < duration then
|
|
Citizen.Wait(0)
|
|
else
|
|
BroadcastStop3DSound()
|
|
ClearPedTasks(ClientData.ped)
|
|
return
|
|
end
|
|
|
|
Citizen.Wait(0)
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- ProgressBar
|
|
|
|
---Creates a progress bar
|
|
---@param text any
|
|
---@param duration any
|
|
---@param xFactorParam any
|
|
function CreateProgressBar(text, duration, xFactorParam)
|
|
if #progressBarPool > 0 then LogWarning("Can't create a progressbar. A progressbar is already running.") return end
|
|
local gameTimer = GetGameTimer()
|
|
table.insert(progressBarPool, {
|
|
text = TranslateText("ACTION_" .. text),
|
|
duration = duration,
|
|
xFactorParam = xFactorParam,
|
|
creationTimestamp = gameTimer,
|
|
endTimestamp = gameTimer + duration
|
|
})
|
|
|
|
progressBarPool[#progressBarPool].position = #progressBarPool
|
|
end
|
|
|
|
---Destroys a progress bar
|
|
---@param data any
|
|
function DestroyProgressBar(data)
|
|
table.remove(progressBarPool, data.position)
|
|
end
|
|
|
|
---Returns true if a progress bar is active
|
|
---@return boolean
|
|
function IsProgressBarActive()
|
|
return #progressBarPool > 0
|
|
end
|
|
|
|
Citizen.CreateThread(function()
|
|
local safeZoneSize = GetSafeZoneSize()
|
|
|
|
if not HasStreamedTextureDictLoaded('timerbars') then
|
|
RequestStreamedTextureDict('timerbars', true)
|
|
while not HasStreamedTextureDictLoaded('timerbars') do Citizen.Wait(1) end
|
|
end
|
|
|
|
while true do
|
|
Citizen.Wait(0)
|
|
local gameTimer = GetGameTimer()
|
|
|
|
for _, v in ipairs(progressBarPool) do
|
|
local width, xValue, yValue
|
|
|
|
if v.endTimestamp > gameTimer then
|
|
local timeDifference = gameTimer - v.creationTimestamp
|
|
if timeDifference < v.duration then width = timeDifference * (0.085 / v.duration) end
|
|
local correctionValue = ((1.0 - RoundValue(safeZoneSize, 2)) * 100) * 0.005
|
|
xValue, yValue = 0.9350 - correctionValue, 0.99 - correctionValue
|
|
|
|
SetScriptGfxDrawOrder(0)
|
|
DrawSprite('timerbars', 'all_black_bg', xValue, yValue, 0.15, 0.0325, 0.0, 255, 255, 255, 180)
|
|
|
|
SetScriptGfxDrawOrder(1)
|
|
DrawRect(xValue + 0.0275, yValue, 0.085, 0.0095, 143, 95, 13, 180)
|
|
|
|
SetScriptGfxDrawOrder(2)
|
|
DrawRect(xValue - 0.015 + (width / 2), yValue, width, 0.0095, 255, 162, 0, 180)
|
|
|
|
SetTextColour(255, 255, 255, 180)
|
|
SetTextFont(0)
|
|
SetTextScale(0.3, 0.3)
|
|
SetTextCentre(true)
|
|
SetTextEntry('STRING')
|
|
AddTextComponentString(v.text)
|
|
SetScriptGfxDrawOrder(3)
|
|
DrawText(xValue - v.xFactorParam, yValue - 0.012)
|
|
else
|
|
DestroyProgressBar(v)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Carry player
|
|
|
|
local carrying = false
|
|
local carryingData = {}
|
|
---Carrys a player
|
|
---@param targetData any
|
|
---@param targetHealthBuffer any
|
|
function CarryPlayer(targetData, targetHealthBuffer)
|
|
if carrying then StopCarry() return end
|
|
if not targetHealthBuffer.unconscious and not targetHealthBuffer.anesthesia then return end
|
|
|
|
if targetData.ped == -1 or not DoesEntityExist(targetData.ped) or targetData.distance > 3.0 then return end
|
|
carrying = true
|
|
carryingData = {
|
|
ped = targetData.ped,
|
|
source = targetData.source,
|
|
distance = targetData.distance
|
|
}
|
|
|
|
TriggerServerEvent(ENUM_EVENT_TYPES.EVENT_START_CARRY, TempAuthToken, TargetData.source)
|
|
ClosePlayerMenu(false)
|
|
end
|
|
|
|
---Stop active carry
|
|
function StopCarry()
|
|
if not carrying then return end
|
|
|
|
local distance = GetDistanceBetweenCoords(ClientData.coords, GetEntityCoords(carryingData.ped), true)
|
|
if carryingData.ped == -1 or distance > 3.0 then return end
|
|
|
|
TriggerServerEvent(ENUM_EVENT_TYPES.EVENT_STOP_CARRY, TempAuthToken, carryingData.source)
|
|
carrying = false
|
|
ClearPedTasks(ClientData.ped)
|
|
DetachEntity(ClientData.ped, true, true)
|
|
end
|
|
|
|
---Plays the local animation loop
|
|
function PlayLocalAnimationLoop()
|
|
while not IsEntityPlayingAnim(ClientData.ped, "mini@cpr@char_b@cpr_def", "cpr_pumpchest_idle", 3) and (ClientHealthBuffer.unconscious or ClientHealthBuffer.anesthesia) and CarryAnimationData == nil and not IsPedInAnyVehicle(ClientData.ped, true) do
|
|
if UnconsciousAnimationAfter or 0 < GetGameTimer() then
|
|
TaskPlayAnim(ClientData.ped, "mini@cpr@char_b@cpr_def", "cpr_pumpchest_idle", 8.0, -8.0, 100000, 1, 0, false, false, false)
|
|
end
|
|
Citizen.Wait(0)
|
|
end
|
|
|
|
if ClientData.inVehicle and IsEntityPlayingAnim(ClientData.ped, "mini@cpr@char_b@cpr_def", "cpr_pumpchest_idle", 3) then
|
|
ClearPedTasks(ClientData.ped)
|
|
end
|
|
|
|
while CarryAnimationData ~= nil and not IsEntityPlayingAnim(ClientData.ped, CarryAnimationData.lib, CarryAnimationData.name, 3) do
|
|
TaskPlayAnim(ClientData.ped, CarryAnimationData.lib, CarryAnimationData.name, 8.0, 8.0, -1, CarryAnimationData.flag, 0, false, false, false)
|
|
Citizen.Wait(0)
|
|
end
|
|
end
|
|
|
|
-- Player menu
|
|
---Callbacks if the player is allowed to use the menu and gets active items from player
|
|
function GetMenuInfo()
|
|
local p = promise.new()
|
|
|
|
TriggerServerCallback {
|
|
eventName = ENUM_EVENT_TYPES.PROCEDURE_GET_MENU_INFO,
|
|
args = { TempAuthToken },
|
|
callback = function(result)
|
|
if not result then p:reject("No value returned")end
|
|
p:resolve(result)
|
|
end
|
|
}
|
|
|
|
return Citizen.Await(p)
|
|
end
|
|
|
|
-- Framework functions
|
|
---Respawns the player
|
|
---@param coords any
|
|
---@param authToken any
|
|
function RespawnPlayer(coords, authToken)
|
|
_TriggerEvent(ENUM_HOOKABLE_EVENTS.PLAYER_RESPAWNED, nil)
|
|
TriggerServerEvent(ENUM_EVENT_TYPES.EVENT_RESPAWN_PLAYER, json.encode(coords), authToken)
|
|
end
|
|
|
|
-- https://github.com/zaphosting/esx_12/blob/master/esx_ambulancejob/client/main.lua#L331
|
|
---Respawns the ped
|
|
---@param ped any
|
|
---@param coords any
|
|
---@param heading any
|
|
function RespawnPed(ped, coords, heading)
|
|
coords = { x = coords.x, y = coords.y, z = coords.z }
|
|
local inWater, waterHeight = GetWaterHeight(coords.x, coords.y, coords.z, 0)
|
|
if inWater then
|
|
coords.z = waterHeight + 0.6
|
|
end
|
|
|
|
--SetEntityCoords(ped, coords.x, coords.y, coords.z - 0.8, false, false, false, false)
|
|
--SetEntityHeading(ped, heading)
|
|
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z - 0.8, heading, true, false)
|
|
SetEntityHealth(ped, 200)
|
|
SetPlayerInvincible(ped, false)
|
|
ClearPedBloodDamage(ped)
|
|
|
|
-- Causes bad spawning?
|
|
-- TriggerEvent('playerSpawned')
|
|
end
|
|
|
|
---Emergency dispatch to configured receiver
|
|
---@param playerPed any
|
|
---@param coords any
|
|
function EmergencyDispatch(playerPed, coords)
|
|
if not ClientHealthBuffer.unconscious then return end
|
|
|
|
local emergencyDispatch = ClientConfig.m_emergencyDispatch
|
|
if not emergencyDispatch.enabled then return end
|
|
|
|
if emergencyDispatch.phoneConfiguration == "gcphone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerServerEvent('esx_addons_gcphone:startCall', v, TranslateText("DISTRESS_MESSAGE"), coords, { PlayerCoords = { x = coords.x, y = coords.y, z = coords.z }, })
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "esx_phone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerServerEvent('esx_phone:send', v, TranslateText("DISTRESS_MESSAGE"), false, { x = coords.x, y = coords.y, z = coords.z })
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "visn_phone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerServerEvent('visn_phone:postService', v, TranslateText("DISTRESS_MESSAGE"), coords)
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "dphone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerEvent("d-phone:client:message:senddispatch", TranslateText("DISTRESS_MESSAGE"), v, 0, 1, coords, "5")
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "roadphone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerServerEvent('roadphone:sendDispatch', GetPlayerServerId(ClientData.id), TranslateText("DISTRESS_MESSAGE"), v, coords, false)
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "gksphone" then
|
|
TriggerServerEvent('gksphone:gkcs:jbmessage', "", "", TranslateText("DISTRESS_MESSAGE"), '', 'GPS: ' .. coords.x .. ', ' .. coords.y, json.encode(emergencyDispatch.receivers), true)
|
|
elseif emergencyDispatch.phoneConfiguration == "qs-smartphone" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerServerEvent('qs-smartphone:server:sendJobAlert', { message = TranslateText("DISTRESS_MESSAGE"), location = coords }, v)
|
|
TriggerServerEvent('qs-smartphone:server:AddNotifies', {
|
|
head = "EMS",
|
|
msg = TranslateText("DISTRESS_MESSAGE"),
|
|
app = 'business'
|
|
})
|
|
end
|
|
elseif emergencyDispatch.phoneConfiguration == "emergencydispatch" then
|
|
for _, v in pairs(emergencyDispatch.receivers) do
|
|
TriggerEvent('emergencydispatch:emergencycall:new', v, TranslateText("DISTRESS_MESSAGE"), false)
|
|
end
|
|
else
|
|
-- Implement your phone event here
|
|
end
|
|
end
|
|
|
|
---Loads the player
|
|
function LoadPlayer()
|
|
while not NetworkIsPlayerActive(PlayerId()) do
|
|
Citizen.Wait(0)
|
|
end
|
|
|
|
while not TempAuthToken or TempAuthToken == "" do
|
|
Citizen.Wait(0)
|
|
end
|
|
|
|
TriggerServerEvent(ENUM_EVENT_TYPES.EVENT_LOAD_PLAYER, TempAuthToken)
|
|
end
|
|
|
|
---Spawns a game object
|
|
---@param name any
|
|
---@param x any
|
|
---@param y any
|
|
---@param z any
|
|
---@return any
|
|
function SpawnGameObject(name, x, y, z)
|
|
if not ClientConfig.m_spawnGameObjects.enabled then return 0 end
|
|
local model = GetHashKey(name)
|
|
RequestModel(model)
|
|
local timeout = 0
|
|
while not HasModelLoaded(model) and timeout < 500 do timeout = timeout + 1 Citizen.Wait(0) end
|
|
if timeout >= 500 then return end
|
|
|
|
if #SpawnedGameObjects > (ClientConfig.m_spawnGameObjects.limit or 50) then DeleteObject(SpawnedGameObjects[1].object) table.remove(SpawnedGameObjects, 1) end
|
|
|
|
local object = CreateObject(model, x, y, z, true, true, false)
|
|
SetModelAsNoLongerNeeded(model)
|
|
table.insert(SpawnedGameObjects, {
|
|
object = object,
|
|
timestamp = GetGameTimer()
|
|
})
|
|
|
|
return object
|
|
end
|
|
|
|
---Cleans up spawned game objects
|
|
function CleanUpGameObjects()
|
|
for i, v in ipairs(SpawnedGameObjects) do
|
|
if (GetGameTimer() - v.timestamp) > ClientConfig.m_spawnGameObjects.lifetime then
|
|
DeleteObject(v.object)
|
|
table.remove(SpawnedGameObjects, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Command functions
|
|
---Function for the emergency dispatch command
|
|
function CallEmergencyDispatch()
|
|
if not ClientHealthBuffer then return end
|
|
if not ClientHealthBuffer.unconscious then return end
|
|
if not ClientConfig.m_emergencyDispatch.enabled then return end
|
|
if (GetGameTimer() - LastDispatch) < ClientConfig.m_emergencyDispatch.cooldown * 1000 then return end
|
|
if IsPauseMenuActive() then return end
|
|
|
|
EmergencyDispatch(ClientData.ped, ClientData.coords)
|
|
LastDispatch = GetGameTimer()
|
|
SendNUIMessage({
|
|
payload = "toggleDispatchInfo",
|
|
payloadData = {
|
|
value = false
|
|
}
|
|
})
|
|
|
|
LastDispatchHidden = true
|
|
end
|
|
|
|
---Function for the self menu command
|
|
function OpenSelfMenu()
|
|
if not ClientHealthBuffer then return end
|
|
if ClientHealthBuffer.unconscious then return end
|
|
if IsPauseMenuActive() then return end
|
|
|
|
ShowPlayerMenu()
|
|
end
|
|
|
|
---Function for the interaction menu command
|
|
function OpenInteractionMenu()
|
|
if not ClientHealthBuffer then return end
|
|
if ClientHealthBuffer.unconscious then return end
|
|
if IsPauseMenuActive() then return end
|
|
|
|
local closestPlayer, closestDistance = GetClosestPlayer()
|
|
if closestPlayer ~= -1 and closestDistance <= 3.0 then ShowPlayerMenu(GetPlayerServerId(closestPlayer)) end
|
|
end
|
|
|
|
---Function for the manual respawn command
|
|
function ManualRespawn()
|
|
if not ClientHealthBuffer then return end
|
|
if not ClientHealthBuffer.unconscious and not ClientHealthBuffer.bodybag then return end
|
|
if not ClientConfig.m_allowManualRespawnWhileBeingUnconscious and not AllowManualRespawn then return end
|
|
if not AllowManualRespawn and GetRemainingUnconsciousSeconds(ClientHealthBuffer) > 0 then return end
|
|
if IsPauseMenuActive() then return end
|
|
|
|
if AllowManualRespawn then AllowManualRespawn = false end
|
|
RespawnPlayer(ClientData.coords, TempAuthToken)
|
|
end
|
|
|
|
---Function for the cancel interaction command
|
|
function CancelInteraction()
|
|
if not ClientHealthBuffer then return end
|
|
if CarryAnimationData ~= nil then StopCarry() end
|
|
if ClientHealthBuffer.unconscious then return end
|
|
if not IsProgressBarActive() then return end
|
|
if IsPauseMenuActive() then return end
|
|
|
|
BroadcastStop3DSound()
|
|
DestroyProgressBar({ position = 1 })
|
|
ClearPedTasks(ClientData.ped)
|
|
TargetData.canceled = true
|
|
end
|
|
|
|
-- Non-Input-Mapper Key function handler
|
|
---Handles the key interactions
|
|
---@param keyConfig any
|
|
function HandleKeyInteractions(keyConfig)
|
|
if IsControlJustPressed(0, keyConfig.keys.CANCEL_INTERACTION.control_id) then CancelInteraction() end
|
|
if IsControlJustPressed(0, keyConfig.keys.OPEN_SELF_MENU.control_id) then OpenSelfMenu() end
|
|
if IsControlJustPressed(0, keyConfig.keys.OPEN_OTHER_MENU.control_id) then OpenInteractionMenu() end
|
|
if IsControlJustPressed(0, keyConfig.keys.MANUAL_RESPAWN.control_id) then ManualRespawn() end
|
|
if IsControlJustPressed(0, keyConfig.keys.EMERGENCY_DISPATCH.control_id) then CallEmergencyDispatch() end
|
|
end
|
|
|
|
-- Deathscreen function
|
|
---Handles the deathscreen
|
|
---@param bloodVolume any
|
|
function HandleUnconsciousScreen(bloodVolume)
|
|
local remainingSeconds = GetRemainingUnconsciousSeconds(ClientHealthBuffer)
|
|
|
|
SendNUIMessage({
|
|
payload = "updateUnconsciousTime",
|
|
payloadData = {
|
|
seconds = remainingSeconds,
|
|
max_seconds = ClientHealthBuffer.unconsciousTimeUntilDeath
|
|
}
|
|
})
|
|
|
|
if GetGameTimer() - LastDispatch > ClientConfig.m_emergencyDispatch.cooldown * 1000 and LastDispatchHidden then
|
|
SendNUIMessage({
|
|
payload = "toggleDispatchInfo",
|
|
payloadData = {
|
|
value = (ClientConfig.m_emergencyDispatch.enabled and ClientConfig.m_configurableKeys.keys.EMERGENCY_DISPATCH.enabled) and not ClientHealthBuffer.bodybag,
|
|
key = ClientConfig.m_configurableKeys.m_useInputMapper
|
|
and GetControlStringFromKeyMapping("emergency_dispatch")
|
|
or GetControlStringFromKeyMappingNoInputMapper(ClientConfig.m_configurableKeys.keys.EMERGENCY_DISPATCH.control_id)
|
|
}
|
|
})
|
|
|
|
LastDispatchHidden = false
|
|
end
|
|
|
|
if not CheckForCriticalVitals(bloodVolume) and math.random(0, 100) > 75 and
|
|
(
|
|
ClientHealthBuffer.heartRate > 50 and
|
|
ClientHealthBuffer.bloodVolume > ENUM_BLOOD_VOLUME_CLASSES.CLASS_4_HERMORRHAGE) then
|
|
SetUnconsciousState(false)
|
|
end
|
|
|
|
if ClientHealthBuffer.unconscious and remainingSeconds == 0 then
|
|
if ClientConfig.m_allowManualRespawnWhileBeingUnconscious then
|
|
SendNUIMessage({
|
|
payload = "showManualRespawnText",
|
|
payloadData = {
|
|
value = ClientConfig.m_configurableKeys.keys.MANUAL_RESPAWN.enabled,
|
|
key = ClientConfig.m_configurableKeys.m_useInputMapper
|
|
and GetControlStringFromKeyMapping("manual_respawn")
|
|
or GetControlStringFromKeyMappingNoInputMapper(ClientConfig.m_configurableKeys.keys.MANUAL_RESPAWN.control_id)
|
|
}
|
|
})
|
|
else
|
|
RespawnPlayer(ClientData.coords, TempAuthToken)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ShowAnesthesiaScreen function
|
|
---Handles the anesthesia screen
|
|
function ShowAnesthesiaScreen(state)
|
|
SendNUIMessage({
|
|
payload = "toggleAnesthesiaScreen",
|
|
payloadData = {
|
|
value = state
|
|
}
|
|
})
|
|
end
|
|
|
|
-- Enumerator
|
|
local entityEnumerator = {
|
|
__gc = function(enum)
|
|
if enum.destructor and enum.handle then
|
|
enum.destructor(enum.handle)
|
|
end
|
|
enum.destructor = nil
|
|
enum.handle = nil
|
|
end
|
|
}
|
|
|
|
local function EnumerateEntities(initFunc, moveFunc, disposeFunc)
|
|
return coroutine.wrap(function()
|
|
local iter, id = initFunc()
|
|
if not id or id == 0 then
|
|
disposeFunc(iter)
|
|
return
|
|
end
|
|
|
|
local enum = {handle = iter, destructor = disposeFunc}
|
|
setmetatable(enum, entityEnumerator)
|
|
|
|
local next = true
|
|
repeat
|
|
coroutine.yield(id)
|
|
next, id = moveFunc(iter)
|
|
until not next
|
|
|
|
enum.destructor, enum.handle = nil, nil
|
|
disposeFunc(iter)
|
|
end)
|
|
end
|
|
|
|
function EnumerateObjects()
|
|
return EnumerateEntities(FindFirstObject, FindNextObject, EndFindObject)
|
|
end
|
|
|
|
function EnumeratePeds()
|
|
return EnumerateEntities(FindFirstPed, FindNextPed, EndFindPed)
|
|
end
|
|
|
|
function EnumerateVehicles()
|
|
return EnumerateEntities(FindFirstVehicle, FindNextVehicle, EndFindVehicle)
|
|
end
|
|
|
|
function EnumeratePickups()
|
|
return EnumerateEntities(FindFirstPickup, FindNextPickup, EndFindPickup)
|
|
end
|
|
|
|
function GetCustomClosestVehicle(coords)
|
|
local oldDistance = 99999
|
|
local vehicle = nil
|
|
|
|
for v in EnumerateVehicles() do
|
|
local checkDistance = GetDistanceBetweenCoords(coords, GetEntityCoords(v), true)
|
|
if checkDistance <= oldDistance and v ~= vehicle then
|
|
vehicle = v
|
|
oldDistance = checkDistance
|
|
end
|
|
end
|
|
|
|
return vehicle, oldDistance
|
|
end
|
|
|
|
-- Function
|
|
|
|
---Returns if the player damage is disabled
|
|
---@return boolean
|
|
function IsPlayerDamageDisabled()
|
|
if GetResourceState("ws_ffa") == "started" and exports["ws_ffa"]:isInZone() then return true end
|
|
return PlayerDamageDisabled
|
|
end
|
|
|
|
-- ox_target
|
|
if ClientConfig.m_ox_target_support then
|
|
Citizen.CreateThread(function()
|
|
Citizen.Wait(1000)
|
|
local ox_menu_options = {
|
|
{
|
|
name = 'visn_are:openMenu',
|
|
icon = 'fa-solid fa-diagnoses',
|
|
label = TranslateText("OPEN_PLAYER_MENU"),
|
|
distance = 1.5,
|
|
onSelect = function(data)
|
|
ShowPlayerMenu(GetPlayerServerId(NetworkGetPlayerIndexFromPed(data.entity)))
|
|
end
|
|
}
|
|
}
|
|
|
|
exports["ox_target"]:addGlobalPlayer(ox_menu_options)
|
|
end)
|
|
end
|
|
|
|
-- State saving
|
|
Citizen.CreateThread(function()
|
|
while not NetworkIsPlayerActive(PlayerId()) do
|
|
Citizen.Wait(0)
|
|
end
|
|
|
|
while not TempAuthToken or TempAuthToken == "" do
|
|
Citizen.Wait(0)
|
|
end
|
|
|
|
Citizen.Wait(2500)
|
|
TriggerServerEvent(ENUM_EVENT_TYPES.EVENT_LOAD_PLAYER_STANDALONE, TempAuthToken)
|
|
end)
|
|
|
|
RegisterNetEvent('esx:playerLoaded', LoadPlayer)
|
|
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', LoadPlayer) |