diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 00000000..988e98eb --- /dev/null +++ b/client/main.lua @@ -0,0 +1,360 @@ +local firstSpawn, PlayerLoaded = true, false + +isDead = false +ESX = nil + +Citizen.CreateThread(function() + while ESX == nil do + TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + Citizen.Wait(0) + end + + while ESX.GetPlayerData().job == nil do + Citizen.Wait(100) + end + + PlayerLoaded = true + ESX.PlayerData = ESX.GetPlayerData() +end) + +RegisterNetEvent('esx:playerLoaded') +AddEventHandler('esx:playerLoaded', function(xPlayer) + ESX.PlayerData = xPlayer + PlayerLoaded = true +end) + +RegisterNetEvent('esx:setJob') +AddEventHandler('esx:setJob', function(job) + ESX.PlayerData.job = job +end) + +AddEventHandler('esx:onPlayerSpawn', function() + isDead = false + + if firstSpawn then + firstSpawn = false + + if Config.AntiCombatLog then + while not PlayerLoaded do + Citizen.Wait(5000) + end + + ESX.TriggerServerCallback('esx_ambulancejob:getDeathStatus', function(shouldDie) + if shouldDie then + Citizen.Wait(10000) + SetEntityHealth(PlayerPedId(), 0) + end + end) + end + end +end) + +-- Create blips +Citizen.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(_U('blip_hospital')) + EndTextCommandSetBlipName(blip) + end +end) + +-- Disable most inputs when dead +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + + if isDead then + DisableAllControlActions(0) + EnableControlAction(0, 47, true) + EnableControlAction(0, 245, true) + EnableControlAction(0, 38, true) + else + Citizen.Wait(500) + end + end +end) + +function OnPlayerDeath() + isDead = true + ESX.UI.Menu.CloseAll() + TriggerServerEvent('esx_ambulancejob:setDeathStatus', true) + + StartDeathTimer() + StartDistressSignal() + + StartScreenEffect('DeathFailOut', 0, false) +end + +RegisterNetEvent('esx_ambulancejob:useItem') +AddEventHandler('esx_ambulancejob:useItem', function(itemName) + ESX.UI.Menu.CloseAll() + + 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) + + Citizen.Wait(500) + while IsEntityPlayingAnim(playerPed, lib, anim, 3) do + Citizen.Wait(0) + DisableAllControlActions(0) + end + + TriggerEvent('esx_ambulancejob:heal', 'big', true) + ESX.ShowNotification(_U('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) + + Citizen.Wait(500) + while IsEntityPlayingAnim(playerPed, lib, anim, 3) do + Citizen.Wait(0) + DisableAllControlActions(0) + end + + TriggerEvent('esx_ambulancejob:heal', 'small', true) + ESX.ShowNotification(_U('used_bandage')) + end) + end +end) + +function StartDistressSignal() + Citizen.CreateThread(function() + local timer = Config.BleedoutTimer + + while timer > 0 and isDead do + Citizen.Wait(0) + timer = timer - 30 + + SetTextFont(4) + SetTextScale(0.45, 0.45) + SetTextColour(185, 185, 185, 255) + SetTextDropshadow(0, 0, 0, 0, 255) + SetTextDropShadow() + SetTextOutline() + BeginTextCommandDisplayText('STRING') + AddTextComponentSubstringPlayerName(_U('distress_send')) + EndTextCommandDisplayText(0.175, 0.805) + + if IsControlJustReleased(0, 47) then + SendDistressSignal() + break + end + end + end) +end + +function SendDistressSignal() + local playerPed = PlayerPedId() + local coords = GetEntityCoords(playerPed) + + ESX.ShowNotification(_U('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) + + Citizen.CreateThread(function() + -- early respawn timer + while earlySpawnTimer > 0 and isDead do + Citizen.Wait(1000) + + if earlySpawnTimer > 0 then + earlySpawnTimer = earlySpawnTimer - 1 + end + end + + -- bleedout timer + while bleedoutTimer > 0 and isDead do + Citizen.Wait(1000) + + if bleedoutTimer > 0 then + bleedoutTimer = bleedoutTimer - 1 + end + end + end) + + Citizen.CreateThread(function() + local text, timeHeld + + -- early respawn timer + while earlySpawnTimer > 0 and isDead do + Citizen.Wait(0) + text = _U('respawn_available_in', secondsToClock(earlySpawnTimer)) + + DrawGenericTextThisFrame() + + SetTextEntry('STRING') + AddTextComponentString(text) + DrawText(0.5, 0.8) + end + + -- bleedout timer + while bleedoutTimer > 0 and isDead do + Citizen.Wait(0) + text = _U('respawn_bleedout_in', secondsToClock(bleedoutTimer)) + + if not Config.EarlyRespawnFine then + text = text .. _U('respawn_bleedout_prompt') + + if IsControlPressed(0, 38) and timeHeld > 60 then + RemoveItemsAfterRPDeath() + break + end + elseif Config.EarlyRespawnFine and canPayFine then + text = text .. _U('respawn_bleedout_fine', ESX.Math.GroupDigits(Config.EarlyRespawnFineAmount)) + + if IsControlPressed(0, 38) and timeHeld > 60 then + TriggerServerEvent('esx_ambulancejob:payFine') + RemoveItemsAfterRPDeath() + break + end + end + + if IsControlPressed(0, 38) then + timeHeld = timeHeld + 1 + else + timeHeld = 0 + end + + DrawGenericTextThisFrame() + + SetTextEntry('STRING') + AddTextComponentString(text) + DrawText(0.5, 0.8) + end + + if bleedoutTimer < 1 and isDead then + RemoveItemsAfterRPDeath() + end + end) +end + +function RemoveItemsAfterRPDeath() + TriggerServerEvent('esx_ambulancejob:setDeathStatus', false) + + Citizen.CreateThread(function() + DoScreenFadeOut(800) + + while not IsScreenFadedOut() do + Citizen.Wait(10) + end + + ESX.TriggerServerCallback('esx_ambulancejob:removeItemsAfterRPDeath', function() + local formattedCoords = { + x = Config.RespawnPoint.coords.x, + y = Config.RespawnPoint.coords.y, + z = Config.RespawnPoint.coords.z + } + + ESX.SetPlayerData('loadout', {}) + RespawnPed(PlayerPedId(), formattedCoords, Config.RespawnPoint.heading) + + StopScreenEffect('DeathFailOut') + DoScreenFadeIn(800) + end) + end) +end + +function RespawnPed(ped, coords, heading) + SetEntityCoordsNoOffset(ped, coords.x, coords.y, coords.z, false, false, false, true) + NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, heading, true, false) + SetPlayerInvincible(ped, false) + ClearPedBloodDamage(ped) + + 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 + Citizen.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) + + StopScreenEffect('DeathFailOut') + DoScreenFadeIn(800) +end) + +-- Load unloaded IPLs +if Config.LoadIpl then + RequestIpl('Coroner_Int_on') -- Morgue +end