aether-framework/resources/[aether]/aether-death/server/main.lua
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark,
  Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI)
- aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource,
  an aether-core gekoppelt (Rechte über aether_users.admin_group), mit
  WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU)
- Prototypen admin/ vervollständigt
- SQL-Schema aether_admin.sql
- Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:55:32 +02:00

250 lines
8.7 KiB
Lua

-- =====================================================================
-- Aether Death — Serverlogik
-- Der Server fuehrt den Verblutungs-Countdown und entscheidet ueber
-- Wiederbelebung, Aufgeben und Krankenhausaufenthalt.
-- =====================================================================
local Core = exports['aether-core']
-- Verletzte Spieler: [quelle] = { seit, restBleed, sperre, emsGerufen, position }
local verletzte = {}
-- ---------------------------------------------------------------------
-- Hilfsfunktionen
-- ---------------------------------------------------------------------
local function CharakterVon(quelle)
return Core:HoleCharakterId(quelle)
end
--- Schickt den aktuellen Zustand an den verletzten Spieler
--- @param quelle number Server-ID
local function SendeZustand(quelle)
local eintrag = verletzte[quelle]
if not eintrag then return end
TriggerClientEvent('aether:death:zustand', quelle, {
restBleed = eintrag.restBleed,
respawnSperre = eintrag.sperre,
emsGerufen = eintrag.emsGerufen,
bleedoutMax = Tod.BLEEDOUT,
})
end
--- Beendet den Verletzt-Zustand eines Spielers
--- @param quelle number Server-ID
local function BeendeVerletzung(quelle)
verletzte[quelle] = nil
local charakterId = CharakterVon(quelle)
if charakterId then
MySQL.update('UPDATE aether_characters SET is_dead = 0 WHERE id = ?', { charakterId })
end
end
-- ---------------------------------------------------------------------
-- Spieler geht zu Boden
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:death:gefallen', function()
local quelle = source
local charakterId = CharakterVon(quelle)
if not charakterId then return end
if verletzte[quelle] then return end -- schon verletzt
local position = GetEntityCoords(GetPlayerPed(quelle))
verletzte[quelle] = {
seit = os.time(),
restBleed = Tod.BLEEDOUT,
sperre = Tod.RESPAWN_SPERRE,
emsGerufen = false,
position = { x = position.x, y = position.y, z = position.z },
}
MySQL.update('UPDATE aether_characters SET is_dead = 1 WHERE id = ?', { charakterId })
TriggerClientEvent('aether:death:zeige', quelle, {
restBleed = Tod.BLEEDOUT,
respawnSperre = Tod.RESPAWN_SPERRE,
bleedoutMax = Tod.BLEEDOUT,
})
-- Andere Module informieren (z. B. Job beenden, HUD anpassen)
TriggerEvent('aether:death:spielerGefallen', quelle)
end)
-- ---------------------------------------------------------------------
-- Rettungsdienst rufen
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:death:emsRufen', function()
local quelle = source
local eintrag = verletzte[quelle]
if not eintrag or eintrag.emsGerufen then return end
eintrag.emsGerufen = true
local person = Core:HoleSpielerInfo(quelle)
local name = person and (person.vorname .. ' ' .. person.nachname) or 'Unbekannt'
-- Notruf weiterreichen. Ein Rettungsdienst-Modul kann diesen Event
-- abfangen und daraus einen Einsatz erzeugen.
TriggerEvent('aether:death:notruf', quelle, {
name = name,
position = eintrag.position,
})
SendeZustand(quelle)
TriggerClientEvent('aether:death:hinweis', quelle, 'Notruf abgesetzt. Warte auf Hilfe.')
end)
-- ---------------------------------------------------------------------
-- Aufgeben: im Krankenhaus aufwachen
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:death:aufgeben', function()
local quelle = source
local eintrag = verletzte[quelle]
if not eintrag then return end
-- Sperrzeit pruefen: nicht sofort aufgeben koennen
if eintrag.sperre > 0 then
TriggerClientEvent('aether:death:hinweis', quelle,
('Noch %d Sekunden, bevor du aufgeben kannst.'):format(eintrag.sperre))
return
end
local klinik = Tod.NaechstesKrankenhaus(
vector3(eintrag.position.x, eintrag.position.y, eintrag.position.z))
-- Behandlungskosten abbuchen (erst Bargeld, dann Konto)
local bezahlt = Core:EntferneGeld(quelle, 'bargeld', Tod.KRANKENHAUS_KOSTEN, 'Behandlung Krankenhaus')
if not bezahlt then
bezahlt = Core:EntferneGeld(quelle, 'konto', Tod.KRANKENHAUS_KOSTEN, 'Behandlung Krankenhaus')
end
BeendeVerletzung(quelle)
TriggerClientEvent('aether:death:krankenhaus', quelle, {
klinik = klinik.name,
position = { x = klinik.pos.x, y = klinik.pos.y, z = klinik.pos.z, w = klinik.pos.w },
leben = Tod.LEBEN_NACH_KLINIK,
kosten = bezahlt and Tod.KRANKENHAUS_KOSTEN or 0,
bezahlt = bezahlt,
grund = 'aufgegeben',
})
end)
-- ---------------------------------------------------------------------
-- Countdown: Verbluten und Sperrzeit
-- ---------------------------------------------------------------------
CreateThread(function()
while true do
Wait(1000)
for quelle, eintrag in pairs(verletzte) do
if eintrag.sperre > 0 then
eintrag.sperre = eintrag.sperre - 1
end
eintrag.restBleed = eintrag.restBleed - 1
-- Zeit abgelaufen: Der Spieler verblutet und wacht in der Klinik auf
if eintrag.restBleed <= 0 then
local klinik = Tod.NaechstesKrankenhaus(
vector3(eintrag.position.x, eintrag.position.y, eintrag.position.z))
local bezahlt = Core:EntferneGeld(quelle, 'bargeld', Tod.KRANKENHAUS_KOSTEN, 'Behandlung Krankenhaus')
if not bezahlt then
bezahlt = Core:EntferneGeld(quelle, 'konto', Tod.KRANKENHAUS_KOSTEN, 'Behandlung Krankenhaus')
end
BeendeVerletzung(quelle)
TriggerClientEvent('aether:death:krankenhaus', quelle, {
klinik = klinik.name,
position = { x = klinik.pos.x, y = klinik.pos.y, z = klinik.pos.z, w = klinik.pos.w },
leben = Tod.LEBEN_NACH_KLINIK,
kosten = bezahlt and Tod.KRANKENHAUS_KOSTEN or 0,
bezahlt = bezahlt,
grund = 'verblutet',
})
else
SendeZustand(quelle)
end
end
end
end)
-- ---------------------------------------------------------------------
-- Position des Verletzten aktuell halten (er kann noch kriechen)
-- ---------------------------------------------------------------------
CreateThread(function()
while true do
Wait(5000)
for quelle, eintrag in pairs(verletzte) do
local ped = GetPlayerPed(quelle)
if ped and ped ~= 0 then
local position = GetEntityCoords(ped)
eintrag.position = { x = position.x, y = position.y, z = position.z }
end
end
end
end)
-- ---------------------------------------------------------------------
-- Aufraeumen
-- ---------------------------------------------------------------------
AddEventHandler('playerDropped', function()
verletzte[source] = nil
end)
-- =====================================================================
-- EXPORTS — fuer den Rettungsdienst
-- =====================================================================
--- Belebt einen verletzten Spieler wieder.
--- Wird vom EMS aufgerufen; prueft die Entfernung des Helfers.
--- @param quelle number Server-ID des Verletzten
--- @param helfer number|nil Server-ID des Sanitaeters (nil = ohne Pruefung)
--- @return boolean Erfolg
exports('Wiederbeleben', function(quelle, helfer)
local eintrag = verletzte[quelle]
if not eintrag then return false end
-- Entfernungspruefung, wenn ein Helfer angegeben ist
if helfer then
local hier = GetEntityCoords(GetPlayerPed(helfer))
local dort = GetEntityCoords(GetPlayerPed(quelle))
if #(hier - dort) > Tod.WIEDERBELEBUNG_WEITE then
return false
end
end
BeendeVerletzung(quelle)
TriggerClientEvent('aether:death:wiederbelebt', quelle, {
leben = Tod.LEBEN_NACH_HILFE,
})
return true
end)
--- Ist ein Spieler gerade verletzt am Boden?
--- @param quelle number Server-ID
--- @return boolean
exports('IstVerletzt', function(quelle)
return verletzte[quelle] ~= nil
end)
--- Liefert alle verletzten Spieler mit Position (fuer die EMS-Karte)
--- @return table
exports('HoleVerletzte', function()
local liste = {}
for quelle, eintrag in pairs(verletzte) do
liste[#liste + 1] = {
quelle = quelle,
position = eintrag.position,
emsGerufen = eintrag.emsGerufen,
restBleed = eintrag.restBleed,
}
end
return liste
end)