local ESX = nil CreateThread(function() while not ESX do Wait(10) ESX = exports['es_extended']:getSharedObject() end end) -- ============================================================ -- STATE -- ============================================================ local deathState = 'ALIVE' -- ALIVE | UNCONSCIOUS | BLEEDING_OUT local unconsciousTimer = 0 local bleedoutTimer = 0 local emsCallHoldTime = 0 local emsCalled = false local isReviving = false local isTreating = false local canGiveUp = false local giveUpTimer = 0 -- Tabelle mit bewusstlosen Spielern (vom Server synchronisiert, für ox_target) local DownedPlayers = {} -- ============================================================ -- HELPER: Ist der lokale Spieler EMS? -- (Muss VOR ox_target Registrierung stehen!) -- ============================================================ local function IsLocalPlayerEMS() if not ESX or not ESX.PlayerData or not ESX.PlayerData.job then return false end for _, jobName in ipairs(Config.EMSJobNames) do if ESX.PlayerData.job.name == jobName then return true end end return false end -- ============================================================ -- UTILS -- ============================================================ local function GetNearestHospital() local playerCoords = GetEntityCoords(PlayerPedId()) local nearest = nil local nearestDist = math.huge for _, loc in ipairs(Config.RespawnLocations) do local dist = #(playerCoords - vector3(loc.x, loc.y, loc.z)) if dist < nearestDist then nearestDist = dist nearest = loc end end return nearest or Config.RespawnLocations[1] end local function SetHudVisible(visible) DisplayRadar(visible) pcall(function() if visible then exports['l2_hud']:ShowHUD() else exports['l2_hud']:HideHUD() end end) end local function LoadAnimDict(dict) RequestAnimDict(dict) local timeout = 0 while not HasAnimDictLoaded(dict) and timeout < 1000 do Wait(10) timeout = timeout + 10 end end local function PlayDeadAnimation(ped) if not IsPedRagdoll(ped) then SetPedToRagdoll(ped, -1, -1, 0, false, false, false) end end local function PlayAnimation(dict, anim, duration, flag) if isTreating then return false end isTreating = true local ped = PlayerPedId() LoadAnimDict(dict) TaskPlayAnim(ped, dict, anim, 8.0, -8.0, duration, flag or 1, 0, false, false, false) local startTime = GetGameTimer() while (GetGameTimer() - startTime) < duration do Wait(100) if not DoesEntityExist(ped) or IsPedDeadOrDying(ped, true) then isTreating = false return false end end ClearPedTasks(ped) RemoveAnimDict(dict) isTreating = false return true end -- Wiederbeleben: CPR Animation (auf dem Boden knien, ganzer Körper) local function PlayReviveAnimation(duration) return PlayAnimation('mini@cpr@char_a@cpr_str', 'cpr_pumpchest', duration, 1) end -- Heilen / Bandage: Verband-Animation (im Stehen, nur Oberkörper) local function PlayHealAnimation(duration) return PlayAnimation('anim@heists@narcotics@funding@gang_idle', 'gang_chatting_idle01', duration, 49) end -- GTA Health-System: 0=tot, 100=minimum sichtbar, 200=voll -- Diese Funktion rechnet Prozent in GTA-HP um (Bereich 100-200) local function PercentToHealth(percent) local health = 100 + math.floor(100 * percent) if health < 110 then health = 110 end -- Sicherheitspuffer über "dying" Grenze if health > 200 then health = 200 end return health end -- ============================================================ -- NUI COMMUNICATION -- ============================================================ local function ShowDeathscreen(state, timer) SendNUIMessage({ action = 'show', state = state, timer = timer, locale = Config.Locale }) end local function UpdateNUI(state, timer) SendNUIMessage({ action = 'update', state = state, timer = timer }) end local function HideDeathscreen() SendNUIMessage({ action = 'hide' }) end -- ============================================================ -- DEATH STATE MANAGEMENT -- ============================================================ local function SetUnconsciousState() if deathState ~= 'ALIVE' or isReviving then return end deathState = 'UNCONSCIOUS' unconsciousTimer = Config.UnconsciousTime emsCalled = false canGiveUp = false giveUpTimer = 0 local ped = PlayerPedId() if IsPedDeadOrDying(ped, true) then -- Ped ist bereits tot - muss resurrectet werden local coords = GetEntityCoords(ped) NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false) Wait(0) ped = PlayerPedId() SetEntityAlpha(ped, 0, false) -- Unsichtbar während Übergang end SetEntityHealth(ped, 200) SetEntityInvincible(ped, true) SetPedToRagdoll(ped, -1, -1, 0, false, false, false) -- Kurz warten dann wieder sichtbar (Ragdoll hat bereits gestartet) CreateThread(function() Wait(100) SetEntityAlpha(PlayerPedId(), 255, false) end) SetHudVisible(false) ShowDeathscreen('UNCONSCIOUS', unconsciousTimer) TriggerServerEvent('mercyv-deathscreen:server:updateState', 'UNCONSCIOUS') end local function SetBleedingOutState() deathState = 'BLEEDING_OUT' bleedoutTimer = Config.BleedoutTime UpdateNUI('BLEEDING_OUT', bleedoutTimer) TriggerServerEvent('mercyv-deathscreen:server:updateState', 'BLEEDING_OUT') end local function ResetToAlive(skipResurrect, reviveHealthPercent) isReviving = true unconsciousTimer = 0 bleedoutTimer = 0 emsCallHoldTime = 0 emsCalled = false canGiveUp = false giveUpTimer = 0 local ped = PlayerPedId() -- Ragdoll / Animation stoppen SetPedCanRagdoll(ped, false) ClearPedTasksImmediately(ped) SetPedCanRagdoll(ped, true) if not skipResurrect then NetworkResurrectLocalPlayer(GetEntityCoords(ped), GetEntityHeading(ped), true, false) local health = PercentToHealth(reviveHealthPercent or Config.ReviveHealthPercent or 0.5) SetEntityHealth(ped, health) ClearPedBloodDamage(ped) end SetEntityInvincible(ped, false) HideDeathscreen() deathState = 'ALIVE' -- HUD-Restore in eigenem Thread damit es garantiert ausgeführt wird CreateThread(function() Wait(1500) SetHudVisible(true) isReviving = false end) end local function DoRespawn() TriggerServerEvent('mercyv-deathscreen:server:respawn') end -- ============================================================ -- SERVER CALLBACKS -- ============================================================ RegisterNetEvent('mercyv-deathscreen:client:doRespawn') AddEventHandler('mercyv-deathscreen:client:doRespawn', function() isReviving = true local hospital = GetNearestHospital() -- Beim Respawn MUSS resurrectet werden (Teleport zum Krankenhaus) NetworkResurrectLocalPlayer(hospital.x, hospital.y, hospital.z, hospital.heading, true, false) Wait(300) local ped = PlayerPedId() SetEntityCoords(ped, hospital.x, hospital.y, hospital.z, false, false, false, true) SetEntityHeading(ped, hospital.heading) SetEntityHealth(ped, 200) -- Volle HP beim Respawn ClearPedBloodDamage(ped) SetPedCanRagdoll(ped, false) SetEntityInvincible(ped, false) ClearPedTasksImmediately(ped) SetPedCanRagdoll(ped, true) HideDeathscreen() deathState = 'ALIVE' unconsciousTimer = 0 bleedoutTimer = 0 emsCallHoldTime = 0 emsCalled = false canGiveUp = false giveUpTimer = 0 TriggerServerEvent('mercyv-deathscreen:server:updateState', 'ALIVE') -- HUD-Restore in eigenem Thread damit es garantiert ausgeführt wird CreateThread(function() Wait(500) SetHudVisible(true) isReviving = false end) end) -- ============================================================ -- DOWNED PLAYERS SYNC -- ============================================================ RegisterNetEvent('mercyv-deathscreen:client:syncDownedPlayers') AddEventHandler('mercyv-deathscreen:client:syncDownedPlayers', function(downedList) DownedPlayers = downedList or {} end) -- ============================================================ -- ESX EVENT HANDLERS -- ============================================================ AddEventHandler('esx:onPlayerDeath', function(data) SetUnconsciousState() end) RegisterNetEvent('esx:playerLoaded') AddEventHandler('esx:playerLoaded', function(xPlayer) ESX.PlayerData = xPlayer ResetToAlive() end) RegisterNetEvent('esx:setJob') AddEventHandler('esx:setJob', function(job) if ESX.PlayerData then ESX.PlayerData.job = job end end) -- ============================================================ -- EIGENE TOD-ERKENNUNG (ohne esx_ambulancejob) -- ============================================================ CreateThread(function() while true do if isReviving then Wait(500) elseif deathState == 'ALIVE' then local ped = PlayerPedId() local health = GetEntityHealth(ped) if IsPedDeadOrDying(ped, true) or health <= 101 then SetUnconsciousState() end Wait(0) else -- Im Death-State: Falls GTA den Ped nochmal killt, erneut resurrecten local ped = PlayerPedId() if IsPedDeadOrDying(ped, true) then local coords = GetEntityCoords(ped) NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false) Wait(0) ped = PlayerPedId() SetEntityAlpha(ped, 0, false) SetEntityHealth(ped, 200) SetEntityInvincible(ped, true) SetPedToRagdoll(ped, -1, -1, 0, false, false, false) CreateThread(function() Wait(100) SetEntityAlpha(PlayerPedId(), 255, false) end) end Wait(1000) end end end) -- ============================================================ -- EIGENES REVIVE SYSTEM -- ============================================================ RegisterNetEvent('mercyv-deathscreen:client:revived') AddEventHandler('mercyv-deathscreen:client:revived', function() if deathState == 'ALIVE' then return end ResetToAlive(false, Config.ReviveHealthPercent) TriggerServerEvent('mercyv-deathscreen:server:updateState', 'ALIVE') ESX.ShowNotification(Config.Locale.revived) end) -- ============================================================ -- OX_TARGET: Wundenbehandlung auf lebende Spieler (nur EMS) -- ============================================================ CreateThread(function() while not ESX do Wait(100) end local ok, err = pcall(function() exports.ox_target:addGlobalPlayer({ { name = 'mercyv_revive', icon = 'fas fa-heart-pulse', label = Config.Locale.reviveTargetLabel or 'Wiederbeleben', distance = Config.ReviveDistance or 3.0, canInteract = function(entity) if isTreating then return false end local targetPlayer = NetworkGetPlayerIndexFromPed(entity) if targetPlayer == -1 then return false end local targetServerId = GetPlayerServerId(targetPlayer) -- Nur bei am Boden liegenden Spielern anzeigen if not DownedPlayers[targetServerId] then return false end if IsLocalPlayerEMS() then return true end return Config.CitizenRevive end, onSelect = function(data) local targetPlayer = NetworkGetPlayerIndexFromPed(data.entity) if targetPlayer == -1 then return end local targetServerId = GetPlayerServerId(targetPlayer) if not targetServerId or targetServerId <= 0 then return end local success = PlayReviveAnimation(Config.ReviveAnimDuration or 5000) if success then TriggerServerEvent('mercyv-deathscreen:server:revivePlayer', targetServerId) end end, }, { name = 'mercyv_heal_big', icon = 'fas fa-kit-medical', label = Config.Locale.healBigLabel or 'Große Wunden behandeln', distance = Config.ReviveDistance or 3.0, canInteract = function(entity) if isTreating then return false end if not IsLocalPlayerEMS() then return false end local targetPlayer = NetworkGetPlayerIndexFromPed(entity) if targetPlayer == -1 then return false end local targetServerId = GetPlayerServerId(targetPlayer) if DownedPlayers[targetServerId] then return false end return true end, onSelect = function(data) local targetPlayer = NetworkGetPlayerIndexFromPed(data.entity) if targetPlayer == -1 then return end local targetServerId = GetPlayerServerId(targetPlayer) if not targetServerId or targetServerId <= 0 then return end local success = PlayHealAnimation(Config.HealBigDuration or 8000) if success then TriggerServerEvent('mercyv-deathscreen:server:healPlayer', targetServerId, 'big') end end, }, { name = 'mercyv_heal_small', icon = 'fas fa-bandage', label = Config.Locale.healSmallLabel or 'Kleine Wunden behandeln', distance = Config.ReviveDistance or 3.0, canInteract = function(entity) if isTreating then return false end if not IsLocalPlayerEMS() then return false end local targetPlayer = NetworkGetPlayerIndexFromPed(entity) if targetPlayer == -1 then return false end local targetServerId = GetPlayerServerId(targetPlayer) if DownedPlayers[targetServerId] then return false end return true end, onSelect = function(data) local targetPlayer = NetworkGetPlayerIndexFromPed(data.entity) if targetPlayer == -1 then return end local targetServerId = GetPlayerServerId(targetPlayer) if not targetServerId or targetServerId <= 0 then return end local success = PlayHealAnimation(Config.HealSmallDuration or 5000) if success then TriggerServerEvent('mercyv-deathscreen:server:healPlayer', targetServerId, 'small') end end, }, }) end) if not ok then print('[mercyv-deathscreen] ox_target Heal fehlgeschlagen: ' .. tostring(err)) end end) -- Wundenbehandlung: Client bekommt HP-Update RegisterNetEvent('mercyv-deathscreen:client:healed') AddEventHandler('mercyv-deathscreen:client:healed', function(newHealth, healType) local ped = PlayerPedId() SetEntityHealth(ped, newHealth) ClearPedBloodDamage(ped) ESX.ShowNotification(Config.Locale.healedByEMS or 'Du wurdest behandelt') end) -- ============================================================ -- SELBSTHEILUNG (Bandage) -- ============================================================ RegisterNetEvent('mercyv-deathscreen:client:selfHeal') AddEventHandler('mercyv-deathscreen:client:selfHeal', function() if deathState ~= 'ALIVE' then return end if isTreating then return end local success = PlayHealAnimation(Config.SelfHealDuration or 4000) if success then local ped = PlayerPedId() local currentHealth = GetEntityHealth(ped) local maxHealth = GetEntityMaxHealth(ped) local newHealth = currentHealth + (Config.SelfHealAmount or 50) if newHealth > maxHealth then newHealth = maxHealth end SetEntityHealth(ped, newHealth) ClearPedBloodDamage(ped) ESX.ShowNotification(Config.Locale.selfHealUsed or 'Du hast dich mit einer Bandage verarztet') end end) -- ============================================================ -- MAIN TIMER THREAD -- ============================================================ CreateThread(function() while true do if deathState == 'UNCONSCIOUS' then unconsciousTimer = unconsciousTimer - 1 giveUpTimer = giveUpTimer + 1 UpdateNUI('UNCONSCIOUS', unconsciousTimer) if unconsciousTimer <= 0 then SetBleedingOutState() end if not canGiveUp and giveUpTimer >= (Config.GiveUpTime or 60) then canGiveUp = true SendNUIMessage({ action = 'enableGiveUp' }) end elseif deathState == 'BLEEDING_OUT' then bleedoutTimer = bleedoutTimer - 1 giveUpTimer = giveUpTimer + 1 UpdateNUI('BLEEDING_OUT', bleedoutTimer) if bleedoutTimer <= 0 then DoRespawn() end if not canGiveUp and giveUpTimer >= (Config.GiveUpTime or 60) then canGiveUp = true SendNUIMessage({ action = 'enableGiveUp' }) end end Wait(1000) end end) -- ============================================================ -- INPUT & ANIMATION THREAD -- ============================================================ CreateThread(function() while true do if deathState ~= 'ALIVE' and not isReviving then local ped = PlayerPedId() DisableAllControlActions(0) PlayDeadAnimation(ped) -- Aufgeben (ausbluten) per E-Taste → direkt Respawn if canGiveUp and (deathState == 'UNCONSCIOUS' or deathState == 'BLEEDING_OUT') then if IsDisabledControlJustPressed(0, Config.RespawnKey) then DoRespawn() end end -- EMS rufen per G-Taste if (deathState == 'UNCONSCIOUS' or deathState == 'BLEEDING_OUT') and not emsCalled then if IsDisabledControlPressed(0, Config.EMSCallKey) then emsCallHoldTime = emsCallHoldTime + 1 -- Balken oder Fortschritt könnte hier visualisiert werden (ca. 1-2 Sek gedrückt halten) if emsCallHoldTime >= 60 then emsCalled = true emsCallHoldTime = 0 local coords = GetEntityCoords(ped) -- Straßenname ermitteln local streetHash, crossingHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z, 0, 0) local streetName = GetStreetNameFromHashKey(streetHash) -- CD_DISPATCH INTEGRATION TriggerServerEvent('cd_dispatch:AddNotification', { job_table = {'ambulance'}, coords = coords, title = '10-15 - Bewusstlose Person', message = 'Notruf: Verletzte Person bei ' .. tostring(streetName), flash = 0, unique_id = tostring(math.random(1111, 9999)), sound = 1, blip = { sprite = 153, scale = 1.2, colour = 1, flashes = true, text = 'Notruf: Bewusstlose Person', time = 5, radius = 0, } }) SendNUIMessage({ action = 'emsCalled' }) ESX.ShowNotification('Der Rettungsdienst wurde alarmiert.') end else emsCallHoldTime = 0 end end Wait(0) else Wait(500) end end end) -- ============================================================ -- CLEANUP -- ============================================================ AddEventHandler('onResourceStop', function(resourceName) if resourceName ~= GetCurrentResourceName() then return end if deathState ~= 'ALIVE' then local ped = PlayerPedId() SetPedCanRagdoll(ped, false) SetEntityInvincible(ped, false) ClearPedTasksImmediately(ped) SetPedCanRagdoll(ped, true) SetHudVisible(true) HideDeathscreen() end end) -- ============================================================ -- MEDICAL NPC SYSTEM (AUTO-MEDIC) -- ============================================================ local MedicNPC = { model = `s_m_m_doctor_01`, -- Der Skin des Arztes coords = vector4(-327.2433, -588.2013, 31.7755, 211.5617), -- Z leicht gesenkt für Bodenhaftung } CreateThread(function() -- NPC Erstellen RequestModel(MedicNPC.model) while not HasModelLoaded(MedicNPC.model) do Wait(10) end local npcEntity = CreatePed(4, MedicNPC.model, MedicNPC.coords.x, MedicNPC.coords.y, MedicNPC.coords.z, MedicNPC.coords.w, false, true) SetEntityHeading(npcEntity, MedicNPC.coords.w) FreezeEntityPosition(npcEntity, true) SetEntityInvincible(npcEntity, true) SetBlockingOfNonTemporaryEvents(npcEntity, true) -- ox_target Optionen für den NPC exports.ox_target:addLocalEntity(npcEntity, { { name = 'medic_npc_heal', icon = 'fas fa-kit-medical', label = 'Sich selbst heilen (Kostenlos)', canInteract = function() return deathState == 'ALIVE' and GetEntityHealth(PlayerPedId()) < 200 end, onSelect = function() TriggerServerEvent('mercyv-deathscreen:server:npcHealSelf') end }, { name = 'medic_npc_revive', icon = 'fas fa-heart-pulse', label = 'Person in der Nähe wiederbeleben', canInteract = function() -- Prüfen ob jemand am Boden liegt in der Nähe des Spielers local coords = GetEntityCoords(PlayerPedId()) local players = ESX.Game.GetPlayersInArea(coords, 5.0) for _, player in ipairs(players) do local serverId = GetPlayerServerId(player) if DownedPlayers[serverId] and player ~= PlayerId() then return true end end return false end, onSelect = function() local coords = GetEntityCoords(PlayerPedId()) local players = ESX.Game.GetPlayersInArea(coords, 5.0) local closestPlayer = nil local shortestDist = 5.0 for _, player in ipairs(players) do local serverId = GetPlayerServerId(player) if DownedPlayers[serverId] and player ~= PlayerId() then closestPlayer = serverId break end end if closestPlayer then TriggerServerEvent('mercyv-deathscreen:server:npcReviveOther', closestPlayer) else ESX.ShowNotification('Keine bewusstlose Person in der Nähe!') end end } }) end)