0.0.1
This commit is contained in:
56
resources/[esx_addons]/esx_ambulancejob/client/deathcam.lua
Normal file
56
resources/[esx_addons]/esx_ambulancejob/client/deathcam.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
function GetShapeTestResultSync(shape)
|
||||
local handle, hit, coords, normal, entity
|
||||
repeat handle, hit, coords, normal, entity = GetShapeTestResult(shape)
|
||||
until handle ~= 1 or Wait()
|
||||
return hit, coords, normal, entity
|
||||
end
|
||||
|
||||
local camera
|
||||
local x = 0
|
||||
local y = 0
|
||||
local camera_radius = 5
|
||||
local zoom = Config.zoom
|
||||
local playerPed
|
||||
|
||||
function StartDeathCam()
|
||||
camera = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', 0, 0, 0, 0, 0, 0, GetGameplayCamFov(), 1)
|
||||
RenderScriptCams(true, true, 1000, true, false)
|
||||
|
||||
playerPed = PlayerPedId()
|
||||
end
|
||||
|
||||
function EndDeathCam()
|
||||
RenderScriptCams(0)
|
||||
camera = DestroyCam(camera)
|
||||
end
|
||||
|
||||
function ProcessCamControls()
|
||||
local playerCoords = GetEntityCoords(playerPed)
|
||||
if camera_radius < zoom.max and IsDisabledControlPressed(0, 14) then
|
||||
camera_radius += zoom.step
|
||||
elseif camera_radius > zoom.min and IsDisabledControlPressed(0, 15) then
|
||||
camera_radius -= zoom.step
|
||||
end
|
||||
|
||||
local coords = ProcessNewPosition(playerCoords)
|
||||
SetCamCoord(camera, coords)
|
||||
PointCamAtCoord(camera, playerCoords.x, playerCoords.y, playerCoords.z)
|
||||
end
|
||||
|
||||
function ProcessNewPosition(playerCoords)
|
||||
x -= GetDisabledControlNormal(0, 1)
|
||||
y -= GetDisabledControlNormal(0, 2)
|
||||
if y < math.rad(15) then
|
||||
y = math.rad(15)
|
||||
elseif y > math.rad(90) then
|
||||
y = math.rad(90)
|
||||
end
|
||||
local normal = vector3(
|
||||
math.sin(y) * math.cos(x),
|
||||
math.sin(y) * math.sin(x),
|
||||
math.cos(y)
|
||||
)
|
||||
local pos = playerCoords + normal * camera_radius
|
||||
local hit, coords = GetShapeTestResultSync(StartShapeTestLosProbe(playerCoords, pos, -1, playerPed))
|
||||
return (hit == 1 and playerCoords + normal * (#(playerCoords - coords) - 1)) or pos
|
||||
end
|
||||
604
resources/[esx_addons]/esx_ambulancejob/client/job.lua
Normal file
604
resources/[esx_addons]/esx_ambulancejob/client/job.lua
Normal file
@@ -0,0 +1,604 @@
|
||||
local CurrentAction, CurrentActionMsg, CurrentActionData = nil, '', {}
|
||||
local HasAlreadyEnteredMarker, LastHospital, LastPart, LastPartNum
|
||||
local isBusy, deadPlayers, deadPlayerBlips, isOnDuty = false, {}, {}, false
|
||||
isInShopMenu = false
|
||||
|
||||
RegisterNetEvent('esx:playerLoaded')
|
||||
AddEventHandler('esx:playerLoaded', function(xPlayer)
|
||||
ESX.PlayerData = xPlayer
|
||||
ESX.PlayerLoaded = true
|
||||
end)
|
||||
|
||||
|
||||
function OpenAmbulanceActionsMenu()
|
||||
local elements = {
|
||||
{ unselectable = true, icon = "fas fa-shirt", title = TranslateCap('ambulance') },
|
||||
{ icon = "fas fa-shirt", title = TranslateCap('cloakroom'), value = 'cloakroom' }
|
||||
}
|
||||
|
||||
if Config.EnablePlayerManagement and ESX.PlayerData.job.grade_name == 'boss' then
|
||||
elements[#elements + 1] = {
|
||||
icon = "fas fa-ambulance",
|
||||
title = TranslateCap('boss_actions'),
|
||||
value = 'boss_actions'
|
||||
}
|
||||
end
|
||||
|
||||
ESX.OpenContext("right", elements, function(menu, element)
|
||||
if element.value == 'cloakroom' then
|
||||
OpenCloakroomMenu()
|
||||
elseif element.value == 'boss_actions' then
|
||||
TriggerEvent('esx_society:openBossMenu', 'ambulance', function(data, menu)
|
||||
menu.close()
|
||||
end, { wash = false })
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function OpenMobileAmbulanceActionsMenu()
|
||||
local elements = {
|
||||
{ unselectable = true, icon = "fas fa-ambulance", title = TranslateCap('ambulance') },
|
||||
{ icon = "fas fa-ambulance", title = TranslateCap('ems_menu'), value = "citizen_interaction" }
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements, function(menu, element)
|
||||
if element.value == "citizen_interaction" then
|
||||
local elements2 = {
|
||||
{ unselectable = true, icon = "fas fa-ambulance", title = element.title },
|
||||
{ icon = "fas fa-syringe", title = TranslateCap('ems_menu_revive'), value = "revive" },
|
||||
{ icon = "fas fa-bandage", title = TranslateCap('ems_menu_small'), value = "small" },
|
||||
{ icon = "fas fa-bandage", title = TranslateCap('ems_menu_big'), value = "big" },
|
||||
{ icon = "fas fa-car", title = TranslateCap('ems_menu_putincar'), value = "put_in_vehicle" },
|
||||
{ icon = "fas fa-syringe", title = TranslateCap('ems_menu_search'), value = "search" },
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements2, function(menu2, element2)
|
||||
if isBusy then return end
|
||||
local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer()
|
||||
|
||||
if element2.value == 'search' then
|
||||
TriggerServerEvent('esx_ambulancejob:svsearch')
|
||||
elseif closestPlayer == -1 or closestDistance > 1.0 then
|
||||
ESX.ShowNotification(TranslateCap('no_players'))
|
||||
else
|
||||
if element2.value == 'revive' then
|
||||
revivePlayer(closestPlayer)
|
||||
elseif element2.value == 'small' then
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:getItemAmount', function(quantity)
|
||||
if quantity > 0 then
|
||||
local closestPlayerPed = GetPlayerPed(closestPlayer)
|
||||
local health = GetEntityHealth(closestPlayerPed)
|
||||
|
||||
if health > 0 then
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
isBusy = true
|
||||
ESX.ShowNotification(TranslateCap('heal_inprogress'))
|
||||
TaskStartScenarioInPlace(playerPed, 'CODE_HUMAN_MEDIC_TEND_TO_DEAD', 0, true)
|
||||
Wait(10000)
|
||||
ClearPedTasks(playerPed)
|
||||
|
||||
TriggerServerEvent('esx_ambulancejob:removeItem', 'bandage')
|
||||
TriggerServerEvent('esx_ambulancejob:heal', GetPlayerServerId(closestPlayer), 'small')
|
||||
ESX.ShowNotification(TranslateCap('heal_complete', GetPlayerName(closestPlayer)))
|
||||
isBusy = false
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('player_not_conscious'))
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('not_enough_bandage'))
|
||||
end
|
||||
end, 'bandage')
|
||||
|
||||
elseif element2.value == 'big' then
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:getItemAmount', function(quantity)
|
||||
if quantity > 0 then
|
||||
local closestPlayerPed = GetPlayerPed(closestPlayer)
|
||||
local health = GetEntityHealth(closestPlayerPed)
|
||||
|
||||
if health > 0 then
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
isBusy = true
|
||||
ESX.ShowNotification(TranslateCap('heal_inprogress'))
|
||||
TaskStartScenarioInPlace(playerPed, 'CODE_HUMAN_MEDIC_TEND_TO_DEAD', 0, true)
|
||||
Wait(10000)
|
||||
ClearPedTasks(playerPed)
|
||||
|
||||
TriggerServerEvent('esx_ambulancejob:removeItem', 'medikit')
|
||||
TriggerServerEvent('esx_ambulancejob:heal', GetPlayerServerId(closestPlayer), 'big')
|
||||
ESX.ShowNotification(TranslateCap('heal_complete', GetPlayerName(closestPlayer)))
|
||||
isBusy = false
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('player_not_conscious'))
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('not_enough_medikit'))
|
||||
end
|
||||
end, 'medikit')
|
||||
elseif element2.value == 'put_in_vehicle' then
|
||||
TriggerServerEvent('esx_ambulancejob:putInVehicle', GetPlayerServerId(closestPlayer))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function revivePlayer(closestPlayer)
|
||||
isBusy = true
|
||||
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:getItemAmount', function(quantity)
|
||||
if quantity > 0 then
|
||||
local closestPlayerPed = GetPlayerPed(closestPlayer)
|
||||
local closestPlayerSrc = GetPlayerServerId(closestPlayer)
|
||||
|
||||
if Player(closestPlayerSrc).state.isDead then
|
||||
local playerPed = PlayerPedId()
|
||||
local lib, anim = 'mini@cpr@char_a@cpr_str', 'cpr_pumpchest'
|
||||
ESX.ShowNotification(TranslateCap('revive_inprogress'))
|
||||
|
||||
for i = 1, 15 do
|
||||
Wait(900)
|
||||
|
||||
ESX.Streaming.RequestAnimDict(lib, function()
|
||||
TaskPlayAnim(playerPed, lib, anim, 8.0, -8.0, -1, 0, 0.0, false, false, false)
|
||||
RemoveAnimDict(lib)
|
||||
end)
|
||||
end
|
||||
|
||||
TriggerServerEvent('esx_ambulancejob:removeItem', 'medikit')
|
||||
TriggerServerEvent('esx_ambulancejob:revive', GetPlayerServerId(closestPlayer))
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('player_not_unconscious'))
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('not_enough_medikit'))
|
||||
end
|
||||
isBusy = false
|
||||
end, 'medikit')
|
||||
end
|
||||
|
||||
function FastTravel(coords, heading)
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
DoScreenFadeOut(800)
|
||||
|
||||
while not IsScreenFadedOut() do
|
||||
Wait(500)
|
||||
end
|
||||
|
||||
ESX.Game.Teleport(playerPed, coords, function()
|
||||
DoScreenFadeIn(800)
|
||||
|
||||
if heading then
|
||||
SetEntityHeading(playerPed, heading)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Draw markers & Marker logic
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local sleep = 1500
|
||||
|
||||
if ESX.PlayerData.job and ESX.PlayerData.job.name == 'ambulance' then
|
||||
local playerCoords = GetEntityCoords(PlayerPedId())
|
||||
local isInMarker, hasExited = false, false
|
||||
local currentHospital, currentPart, currentPartNum
|
||||
|
||||
for hospitalNum, hospital in pairs(Config.Hospitals) do
|
||||
-- Ambulance Actions
|
||||
for k, v in ipairs(hospital.AmbulanceActions) do
|
||||
local distance = #(playerCoords - v)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(Config.Marker.type, v, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.Marker.x, Config.Marker.y, Config.Marker.z,
|
||||
Config.Marker.r, Config.Marker.g, Config.Marker.b, Config.Marker.a, false, false, 2, Config.Marker.rotate, nil,
|
||||
nil, false)
|
||||
|
||||
|
||||
if distance < Config.Marker.x then
|
||||
isInMarker, currentHospital, currentPart, currentPartNum = true, hospitalNum, 'AmbulanceActions', k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Pharmacies
|
||||
for k, v in ipairs(hospital.Pharmacies) do
|
||||
local distance = #(playerCoords - v)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(Config.Marker.type, v, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.Marker.x, Config.Marker.y, Config.Marker.z,
|
||||
Config.Marker.r, Config.Marker.g, Config.Marker.b, Config.Marker.a, false, false, 2, Config.Marker.rotate, nil,
|
||||
nil, false)
|
||||
|
||||
|
||||
if distance < Config.Marker.x then
|
||||
isInMarker, currentHospital, currentPart, currentPartNum = true, hospitalNum, 'Pharmacy', k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Vehicle Spawners
|
||||
for k, v in ipairs(hospital.Vehicles) do
|
||||
local distance = #(playerCoords - v.Spawner)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(v.Marker.type, v.Spawner, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.Marker.x, v.Marker.y, v.Marker.z, v.Marker.r,
|
||||
v.Marker.g, v.Marker.b, v.Marker.a, false, false, 2, v.Marker.rotate, nil, nil, false)
|
||||
|
||||
|
||||
if distance < v.Marker.x then
|
||||
isInMarker, currentHospital, currentPart, currentPartNum = true, hospitalNum, 'Vehicles', k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Helicopter Spawners
|
||||
for k, v in ipairs(hospital.Helicopters) do
|
||||
local distance = #(playerCoords - v.Spawner)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(v.Marker.type, v.Spawner, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.Marker.x, v.Marker.y, v.Marker.z, v.Marker.r,
|
||||
v.Marker.g, v.Marker.b, v.Marker.a, false, false, 2, v.Marker.rotate, nil, nil, false)
|
||||
|
||||
|
||||
if distance < v.Marker.x then
|
||||
isInMarker, currentHospital, currentPart, currentPartNum = true, hospitalNum, 'Helicopters', k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fast Travels (Prompt)
|
||||
for k, v in ipairs(hospital.FastTravelsPrompt) do
|
||||
local distance = #(playerCoords - v.From)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(v.Marker.type, v.From, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.Marker.x, v.Marker.y, v.Marker.z, v.Marker.r,
|
||||
v.Marker.g, v.Marker.b, v.Marker.a, false, false, 2, v.Marker.rotate, nil, nil, false)
|
||||
|
||||
|
||||
if distance < v.Marker.x then
|
||||
isInMarker, currentHospital, currentPart, currentPartNum = true, hospitalNum, 'FastTravelsPrompt', k
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Logic for exiting & entering markers
|
||||
if isInMarker and not HasAlreadyEnteredMarker or
|
||||
(isInMarker and (LastHospital ~= currentHospital or LastPart ~= currentPart or LastPartNum ~= currentPartNum)) then
|
||||
if (LastHospital ~= nil and LastPart ~= nil and LastPartNum ~= nil) and
|
||||
(LastHospital ~= currentHospital or LastPart ~= currentPart or LastPartNum ~= currentPartNum)
|
||||
then
|
||||
TriggerEvent('esx_ambulancejob:hasExitedMarker', LastHospital, LastPart, LastPartNum)
|
||||
hasExited = true
|
||||
end
|
||||
|
||||
HasAlreadyEnteredMarker, LastHospital, LastPart, LastPartNum = true, currentHospital, currentPart, currentPartNum
|
||||
|
||||
TriggerEvent('esx_ambulancejob:hasEnteredMarker', currentHospital, currentPart, currentPartNum)
|
||||
end
|
||||
|
||||
if not hasExited and not isInMarker and HasAlreadyEnteredMarker then
|
||||
HasAlreadyEnteredMarker = false
|
||||
TriggerEvent('esx_ambulancejob:hasExitedMarker', LastHospital, LastPart, LastPartNum)
|
||||
end
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('esx_ambulancejob:hasEnteredMarker', function(hospital, part, partNum)
|
||||
if part == 'AmbulanceActions' then
|
||||
CurrentAction = part
|
||||
CurrentActionMsg = TranslateCap('actions_prompt')
|
||||
CurrentActionData = {}
|
||||
elseif part == 'Pharmacy' then
|
||||
CurrentAction = part
|
||||
CurrentActionMsg = TranslateCap('open_pharmacy')
|
||||
CurrentActionData = {}
|
||||
elseif part == 'Vehicles' then
|
||||
CurrentAction = part
|
||||
CurrentActionMsg = TranslateCap('garage_prompt')
|
||||
CurrentActionData = { hospital = hospital, partNum = partNum }
|
||||
elseif part == 'Helicopters' then
|
||||
CurrentAction = part
|
||||
CurrentActionMsg = TranslateCap('helicopter_prompt')
|
||||
CurrentActionData = { hospital = hospital, partNum = partNum }
|
||||
elseif part == 'FastTravelsPrompt' then
|
||||
local travelItem = Config.Hospitals[hospital][part][partNum]
|
||||
|
||||
CurrentAction = part
|
||||
CurrentActionMsg = travelItem.Prompt
|
||||
CurrentActionData = { to = travelItem.To.coords, heading = travelItem.To.heading }
|
||||
end
|
||||
|
||||
ESX.TextUI(CurrentActionMsg)
|
||||
end)
|
||||
|
||||
AddEventHandler('esx_ambulancejob:hasExitedMarker', function(hospital, part, partNum)
|
||||
if not isInShopMenu then
|
||||
ESX.CloseContext()
|
||||
end
|
||||
ESX.HideUI()
|
||||
CurrentAction = nil
|
||||
end)
|
||||
|
||||
-- Key Controls
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local sleep = 1500
|
||||
|
||||
if CurrentAction then
|
||||
sleep = 0
|
||||
|
||||
if IsControlJustReleased(0, 38) then
|
||||
if CurrentAction == 'AmbulanceActions' then
|
||||
OpenAmbulanceActionsMenu()
|
||||
elseif CurrentAction == 'Pharmacy' then
|
||||
OpenPharmacyMenu()
|
||||
elseif CurrentAction == 'Vehicles' then
|
||||
OpenVehicleSpawnerMenu('car', CurrentActionData.hospital, CurrentAction, CurrentActionData.partNum)
|
||||
elseif CurrentAction == 'Helicopters' then
|
||||
OpenVehicleSpawnerMenu('helicopter', CurrentActionData.hospital, CurrentAction, CurrentActionData.partNum)
|
||||
elseif CurrentAction == 'FastTravelsPrompt' then
|
||||
FastTravel(CurrentActionData.to, CurrentActionData.heading)
|
||||
end
|
||||
|
||||
CurrentAction = nil
|
||||
end
|
||||
end
|
||||
|
||||
local playerCoords, letSleep = GetEntityCoords(PlayerPedId()), true
|
||||
|
||||
for hospitalNum, hospital in pairs(Config.Hospitals) do
|
||||
-- Fast Travels
|
||||
for k, v in ipairs(hospital.FastTravels) do
|
||||
local distance = #(playerCoords - v.From)
|
||||
|
||||
if distance < Config.DrawDistance then
|
||||
sleep = 0
|
||||
DrawMarker(v.Marker.type, v.From, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.Marker.x, v.Marker.y, v.Marker.z, v.Marker.r,
|
||||
v.Marker.g, v.Marker.b, v.Marker.a, false, false, 2, v.Marker.rotate, nil, nil, false)
|
||||
|
||||
|
||||
if distance < v.Marker.x then
|
||||
FastTravel(v.To.coords, v.To.heading)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand("ambulance", function(src)
|
||||
if ESX.PlayerData.job and ESX.PlayerData.job.name == 'ambulance' and not ESX.PlayerData.dead then
|
||||
OpenMobileAmbulanceActionsMenu()
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterKeyMapping("ambulance", "Open Ambulance Actions Menu", "keyboard", "F6")
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:putInVehicle')
|
||||
AddEventHandler('esx_ambulancejob:putInVehicle', function()
|
||||
local playerPed = PlayerPedId()
|
||||
local vehicle, distance = ESX.Game.GetClosestVehicle()
|
||||
|
||||
if vehicle and distance < 5 then
|
||||
local maxSeats, freeSeat = GetVehicleMaxNumberOfPassengers(vehicle)
|
||||
|
||||
for i = maxSeats - 1, 0, -1 do
|
||||
if IsVehicleSeatFree(vehicle, i) then
|
||||
freeSeat = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if freeSeat then
|
||||
TaskWarpPedIntoVehicle(playerPed, vehicle, freeSeat)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function OpenCloakroomMenu()
|
||||
local elements = {
|
||||
{ unselectable = true, icon = "fas fa-shirt", title = TranslateCap('cloakroom') },
|
||||
{ icon = "fas fa-shirt", title = TranslateCap('ems_clothes_civil'), value = "citizen_wear" },
|
||||
{ icon = "fas fa-shirt", title = TranslateCap('ems_clothes_ems'), value = "ambulance_wear" },
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements, function(menu, element)
|
||||
if element.value == "citizen_wear" then
|
||||
ESX.TriggerServerCallback('esx_skin:getPlayerSkin', function(skin, jobSkin)
|
||||
TriggerEvent('skinchanger:loadSkin', skin)
|
||||
isOnDuty = false
|
||||
|
||||
for playerId, v in pairs(deadPlayerBlips) do
|
||||
RemoveBlip(v)
|
||||
deadPlayerBlips[playerId] = nil
|
||||
end
|
||||
deadPlayers = {}
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Off Duty")
|
||||
end
|
||||
end)
|
||||
elseif element.value == "ambulance_wear" then
|
||||
ESX.TriggerServerCallback('esx_skin:getPlayerSkin', function(skin, jobSkin)
|
||||
if skin.sex == 0 then
|
||||
TriggerEvent('skinchanger:loadClothes', skin, jobSkin.skin_male)
|
||||
else
|
||||
TriggerEvent('skinchanger:loadClothes', skin, jobSkin.skin_female)
|
||||
end
|
||||
|
||||
isOnDuty = true
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:getDeadPlayers', function(_deadPlayers)
|
||||
TriggerEvent('esx_ambulancejob:setDeadPlayers', _deadPlayers)
|
||||
end)
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Player Sex |^5" .. tostring(skin.sex) .. "^7")
|
||||
print("[^2INFO^7] On Duty")
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function OpenPharmacyMenu()
|
||||
local elements = {
|
||||
{ unselectable = true, icon = "fas fa-pills", title = TranslateCap('pharmacy_menu_title') }
|
||||
}
|
||||
|
||||
for k, v in pairs(Config.PharmacyItems) do
|
||||
elements[#elements + 1] = {
|
||||
icon = "fas fa-pills",
|
||||
title = v.title,
|
||||
item = v.item
|
||||
}
|
||||
end
|
||||
|
||||
ESX.OpenContext("right", elements, function(menu, element)
|
||||
local elements2 = {
|
||||
{ unselectable = true, icon = "fas fa-pills", title = element.title },
|
||||
{ title = "Amount", input = true, inputType = "number", inputMin = 1, inputMax = 100,
|
||||
inputPlaceholder = "Amount to buy.." },
|
||||
{ icon = "fas fa-check-double", title = "Confirm", val = "confirm" }
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements2, function(menu2, element2)
|
||||
local amount = menu2.eles[2].inputValue
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Attempting to Give Item - ^5" .. tostring(element.item) .. "^7")
|
||||
end
|
||||
TriggerServerEvent('esx_ambulancejob:giveItem', element.item, amount)
|
||||
end, function(menu)
|
||||
OpenPharmacyMenu()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:heal')
|
||||
AddEventHandler('esx_ambulancejob:heal', function(healType, quiet)
|
||||
local playerPed = PlayerPedId()
|
||||
local maxHealth = GetEntityMaxHealth(playerPed)
|
||||
|
||||
if healType == 'small' then
|
||||
local health = GetEntityHealth(playerPed)
|
||||
local newHealth = math.min(maxHealth, math.floor(health + maxHealth / 8))
|
||||
SetEntityHealth(playerPed, newHealth)
|
||||
elseif healType == 'big' then
|
||||
SetEntityHealth(playerPed, maxHealth)
|
||||
end
|
||||
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Healing Player - ^5" .. tostring(healType) .. "^7")
|
||||
end
|
||||
if not quiet then
|
||||
ESX.ShowNotification(TranslateCap('healed'))
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:setJob')
|
||||
AddEventHandler('esx:setJob', function(job)
|
||||
if isOnDuty and job.name ~= 'ambulance' then
|
||||
for playerId, v in pairs(deadPlayerBlips) do
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Removing dead blip - ^5" .. tostring(playerId) .. "^7")
|
||||
end
|
||||
RemoveBlip(v)
|
||||
deadPlayerBlips[playerId] = nil
|
||||
end
|
||||
|
||||
isOnDuty = false
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:PlayerDead')
|
||||
AddEventHandler('esx_ambulancejob:PlayerDead', function(Player)
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Player Dead | ^5" .. tostring(Player) .. "^7")
|
||||
end
|
||||
deadPlayers[Player] = "dead"
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:PlayerNotDead')
|
||||
AddEventHandler('esx_ambulancejob:PlayerNotDead', function(Player)
|
||||
if deadPlayerBlips[Player] then
|
||||
RemoveBlip(deadPlayerBlips[Player])
|
||||
deadPlayerBlips[Player] = nil
|
||||
end
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Player Alive | ^5" .. tostring(Player) .. "^7")
|
||||
end
|
||||
deadPlayers[Player] = nil
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:setDeadPlayers')
|
||||
AddEventHandler('esx_ambulancejob:setDeadPlayers', function(_deadPlayers)
|
||||
deadPlayers = _deadPlayers
|
||||
|
||||
if isOnDuty then
|
||||
for playerId, v in pairs(deadPlayerBlips) do
|
||||
RemoveBlip(v)
|
||||
deadPlayerBlips[playerId] = nil
|
||||
end
|
||||
|
||||
for playerId, status in pairs(deadPlayers) do
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Player Dead | ^5" .. tostring(playerId) .. "^7")
|
||||
end
|
||||
if status == 'distress' then
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Creating Distress Blip for Player - ^5" .. tostring(playerId) .. "^7")
|
||||
end
|
||||
local player = GetPlayerFromServerId(playerId)
|
||||
local playerPed = GetPlayerPed(player)
|
||||
local blip = AddBlipForEntity(playerPed)
|
||||
|
||||
SetBlipSprite(blip, 303)
|
||||
SetBlipColour(blip, 1)
|
||||
SetBlipFlashes(blip, true)
|
||||
SetBlipCategory(blip, 7)
|
||||
|
||||
BeginTextCommandSetBlipName('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('blip_dead'))
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
deadPlayerBlips[playerId] = blip
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:PlayerDistressed')
|
||||
AddEventHandler('esx_ambulancejob:PlayerDistressed', function(playerId, playerCoords)
|
||||
deadPlayers[playerId] = 'distress'
|
||||
|
||||
if isOnDuty then
|
||||
if Config.Debug then
|
||||
print("[^2INFO^7] Player Distress Recived - ID:^5" .. tostring(playerId) .. "^7")
|
||||
end
|
||||
ESX.ShowNotification(TranslateCap('unconscious_found'), "error", 10000)
|
||||
deadPlayerBlips[playerId] = nil
|
||||
|
||||
local blip = AddBlipForCoord(playerCoords.x, playerCoords.y, playerCoords.z)
|
||||
SetBlipSprite(blip, Config.DistressBlip.Sprite)
|
||||
SetBlipColour(blip, Config.DistressBlip.Color)
|
||||
SetBlipScale(blip, Config.DistressBlip.Scale)
|
||||
SetBlipFlashes(blip, true)
|
||||
|
||||
BeginTextCommandSetBlipName('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('blip_dead'))
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
deadPlayerBlips[playerId] = blip
|
||||
end
|
||||
end)
|
||||
396
resources/[esx_addons]/esx_ambulancejob/client/main.lua
Normal file
396
resources/[esx_addons]/esx_ambulancejob/client/main.lua
Normal file
@@ -0,0 +1,396 @@
|
||||
local firstSpawn = true
|
||||
isDead, isSearched, medic = false, false, 0
|
||||
|
||||
RegisterNetEvent('esx:playerLoaded')
|
||||
AddEventHandler('esx:playerLoaded', function(xPlayer)
|
||||
ESX.PlayerLoaded = true
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:onPlayerLogout')
|
||||
AddEventHandler('esx:onPlayerLogout', function()
|
||||
ESX.PlayerLoaded = false
|
||||
firstSpawn = true
|
||||
end)
|
||||
|
||||
AddEventHandler('esx:onPlayerSpawn', function()
|
||||
if firstSpawn then
|
||||
firstSpawn = false
|
||||
return
|
||||
end
|
||||
isDead = false
|
||||
ClearTimecycleModifier()
|
||||
SetPedMotionBlur(PlayerPedId(), false)
|
||||
ClearExtraTimecycleModifier()
|
||||
EndDeathCam()
|
||||
end)
|
||||
|
||||
-- Create blips
|
||||
CreateThread(function()
|
||||
for k, v in pairs(Config.Hospitals) do
|
||||
local blip = AddBlipForCoord(v.Blip.coords)
|
||||
|
||||
SetBlipSprite(blip, v.Blip.sprite)
|
||||
SetBlipScale(blip, v.Blip.scale)
|
||||
SetBlipColour(blip, v.Blip.color)
|
||||
SetBlipAsShortRange(blip, true)
|
||||
|
||||
BeginTextCommandSetBlipName('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('blip_hospital'))
|
||||
EndTextCommandSetBlipName(blip)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:clsearch')
|
||||
AddEventHandler('esx_ambulancejob:clsearch', function(medicId)
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
if isDead then
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
local playersInArea = ESX.Game.GetPlayersInArea(coords, 50.0)
|
||||
|
||||
for i = 1, #playersInArea, 1 do
|
||||
local player = playersInArea[i]
|
||||
if player == GetPlayerFromServerId(medicId) then
|
||||
medic = tonumber(medicId)
|
||||
isSearched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function OnPlayerDeath()
|
||||
ESX.CloseContext()
|
||||
ClearTimecycleModifier()
|
||||
SetTimecycleModifier("REDMIST_blend")
|
||||
SetTimecycleModifierStrength(0.7)
|
||||
SetExtraTimecycleModifier("fp_vig_red")
|
||||
SetExtraTimecycleModifierStrength(1.0)
|
||||
SetPedMotionBlur(PlayerPedId(), true)
|
||||
TriggerServerEvent('esx_ambulancejob:setDeathStatus', true)
|
||||
StartDeathTimer()
|
||||
StartDeathCam()
|
||||
isDead = true
|
||||
StartDeathLoop()
|
||||
StartDistressSignal()
|
||||
|
||||
if Config.DeathAnim.enabled then
|
||||
local coords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, 0.0, 0.0, false)
|
||||
ESX.Streaming.RequestAnimDict(Config.DeathAnim.dict)
|
||||
TaskPlayAnim(ESX.PlayerData.ped, Config.DeathAnim.dict, Config.DeathAnim.name, Config.DeathAnim.fadeIn, Config.DeathAnim.fadeOut,
|
||||
-1, Config.DeathAnim.flags, Config.DeathAnim.playbackRate, false, false, false)
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, true)
|
||||
|
||||
Citizen.CreateThreadNow(function()
|
||||
while ESX.PlayerData.dead do
|
||||
if not IsEntityPlayingAnim(ESX.PlayerData.ped, Config.DeathAnim.dict, Config.DeathAnim.name, 3) then
|
||||
TaskPlayAnim(ESX.PlayerData.ped, Config.DeathAnim.dict, Config.DeathAnim.name, Config.DeathAnim.fadeIn, Config.DeathAnim.fadeOut,
|
||||
-1, Config.DeathAnim.flags, Config.DeathAnim.playbackRate, false, false, false)
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
RemoveAnimDict(Config.DeathAnim.dict)
|
||||
end)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:useItem')
|
||||
AddEventHandler('esx_ambulancejob:useItem', function(itemName)
|
||||
ESX.CloseContext()
|
||||
|
||||
if itemName == 'medikit' then
|
||||
local lib, anim = 'anim@heists@narcotics@funding@gang_idle', 'gang_chatting_idle01' -- TODO better animations
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
ESX.Streaming.RequestAnimDict(lib, function()
|
||||
TaskPlayAnim(playerPed, lib, anim, 8.0, -8.0, -1, 0, 0, false, false, false)
|
||||
RemoveAnimDict(lib)
|
||||
|
||||
Wait(500)
|
||||
while IsEntityPlayingAnim(playerPed, lib, anim, 3) do
|
||||
Wait(0)
|
||||
DisableAllControlActions(0)
|
||||
end
|
||||
|
||||
TriggerEvent('esx_ambulancejob:heal', 'big', true)
|
||||
ESX.ShowNotification(TranslateCap('used_medikit'))
|
||||
end)
|
||||
|
||||
elseif itemName == 'bandage' then
|
||||
local lib, anim = 'anim@heists@narcotics@funding@gang_idle', 'gang_chatting_idle01' -- TODO better animations
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
ESX.Streaming.RequestAnimDict(lib, function()
|
||||
TaskPlayAnim(playerPed, lib, anim, 8.0, -8.0, -1, 0, 0, false, false, false)
|
||||
RemoveAnimDict(lib)
|
||||
|
||||
Wait(500)
|
||||
while IsEntityPlayingAnim(playerPed, lib, anim, 3) do
|
||||
Wait(0)
|
||||
DisableAllControlActions(0)
|
||||
end
|
||||
|
||||
TriggerEvent('esx_ambulancejob:heal', 'small', true)
|
||||
ESX.ShowNotification(TranslateCap('used_bandage'))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
function StartDeathLoop()
|
||||
CreateThread(function()
|
||||
while isDead do
|
||||
DisableAllControlActions(0)
|
||||
EnableControlAction(0, 47, true) -- G
|
||||
EnableControlAction(0, 245, true) -- T
|
||||
EnableControlAction(0, 38, true) -- E
|
||||
|
||||
ProcessCamControls()
|
||||
if isSearched then
|
||||
local playerPed = PlayerPedId()
|
||||
local ped = GetPlayerPed(GetPlayerFromServerId(medic))
|
||||
isSearched = false
|
||||
|
||||
AttachEntityToEntity(playerPed, ped, 11816, 0.54, 0.54, 0.0, 0.0, 0.0, 0.0, false, false, false, false, 2, true)
|
||||
Wait(1000)
|
||||
DetachEntity(playerPed, true, false)
|
||||
ClearPedTasksImmediately(playerPed)
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function StartDistressSignal()
|
||||
CreateThread(function()
|
||||
local timer = Config.BleedoutTimer
|
||||
|
||||
while timer > 0 and isDead do
|
||||
Wait(0)
|
||||
timer = timer - 30
|
||||
|
||||
SetTextFont(4)
|
||||
SetTextScale(0.5, 0.5)
|
||||
SetTextColour(200, 50, 50, 255)
|
||||
SetTextDropshadow(0.1, 3, 27, 27, 255)
|
||||
BeginTextCommandDisplayText('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('distress_send'))
|
||||
EndTextCommandDisplayText(0.446, 0.77)
|
||||
|
||||
if IsControlJustReleased(0, 47) then
|
||||
SendDistressSignal()
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function SendDistressSignal()
|
||||
local playerPed = PlayerPedId()
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
|
||||
ESX.ShowNotification(TranslateCap('distress_sent'))
|
||||
TriggerServerEvent('esx_ambulancejob:onPlayerDistress')
|
||||
end
|
||||
|
||||
function DrawGenericTextThisFrame()
|
||||
SetTextFont(4)
|
||||
SetTextScale(0.0, 0.5)
|
||||
SetTextColour(255, 255, 255, 255)
|
||||
SetTextDropshadow(0, 0, 0, 0, 255)
|
||||
SetTextDropShadow()
|
||||
SetTextOutline()
|
||||
SetTextCentre(true)
|
||||
end
|
||||
|
||||
function secondsToClock(seconds)
|
||||
local seconds, hours, mins, secs = tonumber(seconds), 0, 0, 0
|
||||
|
||||
if seconds <= 0 then
|
||||
return 0, 0
|
||||
else
|
||||
local hours = string.format('%02.f', math.floor(seconds / 3600))
|
||||
local mins = string.format('%02.f', math.floor(seconds / 60 - (hours * 60)))
|
||||
local secs = string.format('%02.f', math.floor(seconds - hours * 3600 - mins * 60))
|
||||
|
||||
return mins, secs
|
||||
end
|
||||
end
|
||||
|
||||
function StartDeathTimer()
|
||||
local canPayFine = false
|
||||
|
||||
if Config.EarlyRespawnFine then
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:checkBalance', function(canPay)
|
||||
canPayFine = canPay
|
||||
end)
|
||||
end
|
||||
|
||||
local earlySpawnTimer = ESX.Math.Round(Config.EarlyRespawnTimer / 1000)
|
||||
local bleedoutTimer = ESX.Math.Round(Config.BleedoutTimer / 1000)
|
||||
|
||||
CreateThread(function()
|
||||
-- early respawn timer
|
||||
while earlySpawnTimer > 0 and isDead do
|
||||
Wait(1000)
|
||||
|
||||
if earlySpawnTimer > 0 then
|
||||
earlySpawnTimer = earlySpawnTimer - 1
|
||||
end
|
||||
end
|
||||
|
||||
-- bleedout timer
|
||||
while bleedoutTimer > 0 and isDead do
|
||||
Wait(1000)
|
||||
|
||||
if bleedoutTimer > 0 then
|
||||
bleedoutTimer = bleedoutTimer - 1
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
local text, timeHeld
|
||||
|
||||
-- early respawn timer
|
||||
while earlySpawnTimer > 0 and isDead do
|
||||
Wait(0)
|
||||
text = TranslateCap('respawn_available_in', secondsToClock(earlySpawnTimer))
|
||||
|
||||
DrawGenericTextThisFrame()
|
||||
BeginTextCommandDisplayText('STRING')
|
||||
AddTextComponentSubstringPlayerName(text)
|
||||
EndTextCommandDisplayText(0.5, 0.8)
|
||||
end
|
||||
|
||||
-- bleedout timer
|
||||
while bleedoutTimer > 0 and isDead do
|
||||
Wait(0)
|
||||
text = TranslateCap('respawn_bleedout_in', secondsToClock(bleedoutTimer))
|
||||
|
||||
if not Config.EarlyRespawnFine then
|
||||
text = text .. TranslateCap('respawn_bleedout_prompt')
|
||||
|
||||
if IsControlPressed(0, 38) and timeHeld > 120 then
|
||||
RemoveItemsAfterRPDeath()
|
||||
break
|
||||
end
|
||||
elseif Config.EarlyRespawnFine and canPayFine then
|
||||
text = text .. TranslateCap('respawn_bleedout_fine', ESX.Math.GroupDigits(Config.EarlyRespawnFineAmount))
|
||||
|
||||
if IsControlPressed(0, 38) and timeHeld > 120 then
|
||||
TriggerServerEvent('esx_ambulancejob:payFine')
|
||||
RemoveItemsAfterRPDeath()
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if IsControlPressed(0, 38) then
|
||||
timeHeld += 1
|
||||
else
|
||||
timeHeld = 0
|
||||
end
|
||||
|
||||
DrawGenericTextThisFrame()
|
||||
|
||||
BeginTextCommandDisplayText('STRING')
|
||||
AddTextComponentSubstringPlayerName(text)
|
||||
EndTextCommandDisplayText(0.5, 0.8)
|
||||
end
|
||||
|
||||
if bleedoutTimer < 1 and isDead then
|
||||
RemoveItemsAfterRPDeath()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function GetClosestRespawnPoint()
|
||||
local plyCoords = GetEntityCoords(PlayerPedId())
|
||||
local closestDist, closestHospital
|
||||
|
||||
for i=1, #Config.RespawnPoints do
|
||||
local dist = #(plyCoords - Config.RespawnPoints[i].coords)
|
||||
|
||||
if not closestDist or dist <= closestDist then
|
||||
closestDist, closestHospital = dist, Config.RespawnPoints[i]
|
||||
end
|
||||
end
|
||||
|
||||
return closestHospital
|
||||
end
|
||||
|
||||
function RemoveItemsAfterRPDeath()
|
||||
TriggerServerEvent('esx_ambulancejob:setDeathStatus', false)
|
||||
|
||||
CreateThread(function()
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:removeItemsAfterRPDeath', function()
|
||||
local ClosestHospital = GetClosestRespawnPoint()
|
||||
|
||||
ESX.SetPlayerData('loadout', {})
|
||||
|
||||
DoScreenFadeOut(800)
|
||||
RespawnPed(PlayerPedId(), ClosestHospital.coords, ClosestHospital.heading)
|
||||
while not IsScreenFadedOut() do
|
||||
Wait(0)
|
||||
end
|
||||
DoScreenFadeIn(800)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function RespawnPed(ped, coords, heading)
|
||||
SetEntityCoordsNoOffset(ped, coords.x, coords.y, coords.z, false, false, false)
|
||||
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, heading, true, false)
|
||||
SetPlayerInvincible(ped, false)
|
||||
ClearPedBloodDamage(ped)
|
||||
|
||||
if Config.DeathAnim.enabled then
|
||||
FreezeEntityPosition(ped, false)
|
||||
end
|
||||
|
||||
TriggerEvent('esx_basicneeds:resetStatus')
|
||||
TriggerServerEvent('esx:onPlayerSpawn')
|
||||
TriggerEvent('esx:onPlayerSpawn')
|
||||
TriggerEvent('playerSpawned') -- compatibility with old scripts, will be removed soon
|
||||
end
|
||||
|
||||
RegisterNetEvent('esx_phone:loaded')
|
||||
AddEventHandler('esx_phone:loaded', function(phoneNumber, contacts)
|
||||
local specialContact = {name = 'Ambulance', number = 'ambulance',
|
||||
base64Icon = 'data:image/png;base64,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'}
|
||||
|
||||
TriggerEvent('esx_phone:addSpecialContact', specialContact.name, specialContact.number, specialContact.base64Icon)
|
||||
end)
|
||||
|
||||
AddEventHandler('esx:onPlayerDeath', function(data)
|
||||
OnPlayerDeath()
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx_ambulancejob:revive')
|
||||
AddEventHandler('esx_ambulancejob:revive', function()
|
||||
local playerPed = PlayerPedId()
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
TriggerServerEvent('esx_ambulancejob:setDeathStatus', false)
|
||||
|
||||
DoScreenFadeOut(800)
|
||||
|
||||
while not IsScreenFadedOut() do
|
||||
Wait(50)
|
||||
end
|
||||
|
||||
local formattedCoords = {x = ESX.Math.Round(coords.x, 1), y = ESX.Math.Round(coords.y, 1), z = ESX.Math.Round(coords.z, 1)}
|
||||
|
||||
RespawnPed(playerPed, formattedCoords, 0.0)
|
||||
isDead = false
|
||||
ClearTimecycleModifier()
|
||||
SetPedMotionBlur(playerPed, false)
|
||||
ClearExtraTimecycleModifier()
|
||||
EndDeathCam()
|
||||
DoScreenFadeIn(800)
|
||||
end)
|
||||
|
||||
-- Load unloaded IPLs
|
||||
if Config.LoadIpl then
|
||||
RequestIpl('Coroner_Int_on') -- Morgue
|
||||
end
|
||||
314
resources/[esx_addons]/esx_ambulancejob/client/vehicle.lua
Normal file
314
resources/[esx_addons]/esx_ambulancejob/client/vehicle.lua
Normal file
@@ -0,0 +1,314 @@
|
||||
local spawnedVehicles = {}
|
||||
|
||||
function OpenVehicleSpawnerMenu(type, hospital, part, partNum)
|
||||
local playerCoords = GetEntityCoords(PlayerPedId())
|
||||
local elements = {
|
||||
{unselectable = true, icon = "fas fa-car", title = TranslateCap('garage_title')},
|
||||
{icon = "fas fa-car", title = TranslateCap('garage_storeditem'), action = 'garage'},
|
||||
{icon = "fas fa-car", title = TranslateCap('garage_storeitem'), action = 'store_garage'},
|
||||
{icon = "fas fa-car", title = TranslateCap('garage_buyitem'), action = 'buy_vehicle'}
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements, function(menu,element)
|
||||
if element.action == "buy_vehicle" then
|
||||
local shopElements = {}
|
||||
local authorizedVehicles = Config.AuthorizedVehicles[type][ESX.PlayerData.job.grade_name]
|
||||
local shopCoords = Config.Hospitals[hospital][part][partNum].InsideShop
|
||||
|
||||
if #authorizedVehicles > 0 then
|
||||
for k,vehicle in ipairs(authorizedVehicles) do
|
||||
if IsModelInCdimage(vehicle.model) then
|
||||
local vehicleLabel = GetLabelText(GetDisplayNameFromVehicleModel(vehicle.model))
|
||||
|
||||
shopElements[#shopElements+1] = {
|
||||
icon = 'fas fa-car',
|
||||
title = ('%s - <span style="color:green;">%s</span>'):format(vehicleLabel, TranslateCap('shop_item', ESX.Math.GroupDigits(vehicle.price))),
|
||||
name = vehicleLabel,
|
||||
model = vehicle.model,
|
||||
price = vehicle.price,
|
||||
props = vehicle.props,
|
||||
type = type
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if #shopElements > 0 then
|
||||
OpenShopMenu(shopElements, playerCoords, shopCoords)
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_notauthorized'))
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_notauthorized'))
|
||||
end
|
||||
elseif element.action == "garage" then
|
||||
local garage = {
|
||||
{unselectable = true, icon = "fas fa-car", title = "Garage"}
|
||||
}
|
||||
|
||||
ESX.TriggerServerCallback('esx_vehicleshop:retrieveJobVehicles', function(jobVehicles)
|
||||
if #jobVehicles > 0 then
|
||||
local allVehicleProps = {}
|
||||
|
||||
for k,v in ipairs(jobVehicles) do
|
||||
local props = json.decode(v.vehicle)
|
||||
|
||||
if IsModelInCdimage(props.model) then
|
||||
local vehicleName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
|
||||
local label = ('%s - <span style="color:darkgoldenrod;">%s</span>: '):format(vehicleName, props.plate)
|
||||
|
||||
if v.stored == 1 or v.stored == true then
|
||||
label = label .. ('<span style="color:green;">%s</span>'):format(TranslateCap('garage_stored'))
|
||||
elseif v.stored == 0 or v.stored == false then
|
||||
label = label .. ('<span style="color:darkred;">%s</span>'):format(TranslateCap('garage_notstored'))
|
||||
end
|
||||
|
||||
garage[#garage+1] = {
|
||||
icon = 'fas fa-car',
|
||||
title = label,
|
||||
stored = v.stored,
|
||||
model = props.model,
|
||||
plate = props.plate
|
||||
}
|
||||
|
||||
allVehicleProps[props.plate] = props
|
||||
end
|
||||
end
|
||||
|
||||
if #garage > 0 then
|
||||
ESX.OpenContext("right", garage, function(menuG,elementG)
|
||||
if elementG.stored == 1 then
|
||||
local foundSpawn, spawnPoint = GetAvailableVehicleSpawnPoint(hospital, part, partNum)
|
||||
|
||||
if foundSpawn then
|
||||
ESX.CloseContext()
|
||||
|
||||
ESX.Game.SpawnVehicle(elementG.model, spawnPoint.coords, spawnPoint.heading, function(vehicle)
|
||||
local vehicleProps = allVehicleProps[elementG.plate]
|
||||
ESX.Game.SetVehicleProperties(vehicle, vehicleProps)
|
||||
|
||||
TriggerServerEvent('esx_vehicleshop:setJobVehicleState', elementG.plate, false)
|
||||
ESX.ShowNotification(TranslateCap('garage_released'))
|
||||
end)
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_notavailable'))
|
||||
end
|
||||
end)
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_empty'))
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_empty'))
|
||||
end
|
||||
end, type)
|
||||
elseif element.action == "store_garage" then
|
||||
StoreNearbyVehicle(playerCoords)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function StoreNearbyVehicle(playerCoords)
|
||||
local vehicles, plates, index = ESX.Game.GetVehiclesInArea(playerCoords, 30.0), {}, {}
|
||||
|
||||
if next(vehicles) then
|
||||
for i = 1, #vehicles do
|
||||
local vehicle = vehicles[i]
|
||||
|
||||
-- Make sure the vehicle we're saving is empty, or else it won't be deleted
|
||||
if GetVehicleNumberOfPassengers(vehicle) == 0 and IsVehicleSeatFree(vehicle, -1) then
|
||||
local plate = ESX.Math.Trim(GetVehicleNumberPlateText(vehicle))
|
||||
plates[#plates + 1] = plate
|
||||
index[plate] = vehicle
|
||||
end
|
||||
end
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_store_nearby'))
|
||||
return
|
||||
end
|
||||
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:storeNearbyVehicle', function(plate)
|
||||
if plate then
|
||||
local vehicleId = index[plate]
|
||||
local attempts = 0
|
||||
ESX.Game.DeleteVehicle(vehicleId)
|
||||
isBusy = true
|
||||
|
||||
CreateThread(function()
|
||||
BeginTextCommandBusyspinnerOn('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('garage_storing'))
|
||||
EndTextCommandBusyspinnerOn(4)
|
||||
|
||||
while isBusy do
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
BusyspinnerOff()
|
||||
end)
|
||||
|
||||
-- Workaround for vehicle not deleting when other players are near it.
|
||||
while DoesEntityExist(vehicleId) do
|
||||
Wait(500)
|
||||
attempts = attempts + 1
|
||||
|
||||
-- Give up
|
||||
if attempts > 30 then
|
||||
break
|
||||
end
|
||||
|
||||
vehicles = ESX.Game.GetVehiclesInArea(playerCoords, 30.0)
|
||||
if #vehicles > 0 then
|
||||
for i = 1, #vehicles do
|
||||
local vehicle = vehicles[i]
|
||||
if ESX.Math.Trim(GetVehicleNumberPlateText(vehicle)) == plate then
|
||||
ESX.Game.DeleteVehicle(vehicle)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
isBusy = false
|
||||
ESX.ShowNotification(TranslateCap('garage_has_stored'))
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_has_notstored'))
|
||||
end
|
||||
end, plates)
|
||||
end
|
||||
|
||||
function GetAvailableVehicleSpawnPoint(hospital, part, partNum)
|
||||
local spawnPoints = Config.Hospitals[hospital][part][partNum].SpawnPoints
|
||||
local found, foundSpawnPoint = false, nil
|
||||
|
||||
for i=1, #spawnPoints, 1 do
|
||||
if ESX.Game.IsSpawnPointClear(spawnPoints[i].coords, spawnPoints[i].radius) then
|
||||
found, foundSpawnPoint = true, spawnPoints[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
return true, foundSpawnPoint
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('garage_blocked'))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function OpenShopMenu(elements, restoreCoords, shopCoords)
|
||||
local playerPed = PlayerPedId()
|
||||
isInShopMenu = true
|
||||
ESX.OpenContext("right", elements, function(menu,element)
|
||||
local elements2 = {
|
||||
{unselectable = true, icon = "fas fa-car", title = element.title},
|
||||
{icon = "fas fa-eye", title = "View", value = "view"}
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements2, function(menu2,element2)
|
||||
if element2.value == "view" then
|
||||
DeleteSpawnedVehicles()
|
||||
WaitForVehicleToLoad(element.model)
|
||||
|
||||
ESX.Game.SpawnLocalVehicle(element.model, shopCoords.xyz, shopCoords.w, function(vehicle)
|
||||
table.insert(spawnedVehicles, vehicle)
|
||||
TaskWarpPedIntoVehicle(playerPed, vehicle, -1)
|
||||
FreezeEntityPosition(vehicle, true)
|
||||
SetModelAsNoLongerNeeded(element.model)
|
||||
|
||||
if element.props then
|
||||
ESX.Game.SetVehicleProperties(vehicle, element.props)
|
||||
end
|
||||
end)
|
||||
|
||||
local elements3 = {
|
||||
{unselectable = true, icon = "fas fa-car", title = element.title},
|
||||
{icon = "fas fa-check-double", title = "Buy", value = "buy"},
|
||||
{icon = "fas fa-eye", title = "Stop Viewing", value = "stop"}
|
||||
}
|
||||
|
||||
ESX.OpenContext("right", elements3, function(menu3,element3)
|
||||
if element3.value == 'stop' then
|
||||
isInShopMenu = false
|
||||
ESX.CloseContext()
|
||||
|
||||
DeleteSpawnedVehicles()
|
||||
FreezeEntityPosition(playerPed, false)
|
||||
SetEntityVisible(playerPed, true)
|
||||
|
||||
ESX.Game.Teleport(playerPed, restoreCoords)
|
||||
elseif element3.value == "buy" then
|
||||
local newPlate = exports['esx_vehicleshop']:GeneratePlate()
|
||||
local vehicle = GetVehiclePedIsIn(playerPed, false)
|
||||
local props = ESX.Game.GetVehicleProperties(vehicle)
|
||||
props.plate = newPlate
|
||||
|
||||
ESX.TriggerServerCallback('esx_ambulancejob:buyJobVehicle', function (bought)
|
||||
if bought then
|
||||
ESX.ShowNotification(TranslateCap('vehicleshop_bought', element.name, ESX.Math.GroupDigits(element.price)))
|
||||
|
||||
isInShopMenu = false
|
||||
ESX.CloseContext()
|
||||
DeleteSpawnedVehicles()
|
||||
FreezeEntityPosition(playerPed, false)
|
||||
SetEntityVisible(playerPed, true)
|
||||
|
||||
ESX.Game.Teleport(playerPed, restoreCoords)
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap('vehicleshop_money'))
|
||||
ESX.CloseContext()
|
||||
end
|
||||
end, props, element.type)
|
||||
end
|
||||
end,function ()
|
||||
isInShopMenu = false
|
||||
ESX.CloseContext()
|
||||
|
||||
DeleteSpawnedVehicles()
|
||||
FreezeEntityPosition(playerPed, false)
|
||||
SetEntityVisible(playerPed, true)
|
||||
|
||||
ESX.Game.Teleport(playerPed, restoreCoords)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
sleep = 1500
|
||||
|
||||
if isInShopMenu then
|
||||
sleep = 0
|
||||
DisableControlAction(0, 75, true) -- Disable exit vehicle
|
||||
DisableControlAction(27, 75, true) -- Disable exit vehicle
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
function DeleteSpawnedVehicles()
|
||||
while #spawnedVehicles > 0 do
|
||||
local vehicle = spawnedVehicles[1]
|
||||
ESX.Game.DeleteVehicle(vehicle)
|
||||
table.remove(spawnedVehicles, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function WaitForVehicleToLoad(modelHash)
|
||||
modelHash = (type(modelHash) == 'number' and modelHash or joaat(modelHash))
|
||||
|
||||
if not HasModelLoaded(modelHash) then
|
||||
RequestModel(modelHash)
|
||||
|
||||
BeginTextCommandBusyspinnerOn('STRING')
|
||||
AddTextComponentSubstringPlayerName(TranslateCap('vehicleshop_awaiting_model'))
|
||||
EndTextCommandBusyspinnerOn(4)
|
||||
|
||||
while not HasModelLoaded(modelHash) do
|
||||
Wait(0)
|
||||
DisableAllControlActions(0)
|
||||
end
|
||||
|
||||
BusyspinnerOff()
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user