aether-framework/resources/[aether]/aether-medical/client/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

262 lines
9.0 KiB
Lua

-- =====================================================================
-- aether-medical — Clientlogik
--
-- Öffnet die Kliniksoftware und vermittelt zwischen Oberfläche und
-- Server. Der Client führt nichts Medizinisches aus — er zeigt an,
-- meldet Tatsachen (Fahrzeugtelemetrie) und setzt Wegpunkte.
-- =====================================================================
local istOffen = false
local istGeladen = false
local imDienst = false
local function SendeAnNui(nachricht)
SendNuiMessage(json.encode(nachricht))
end
local function ZeigeSpielmeldung(text)
if not text or text == '' then return end
BeginTextCommandThefeedPost('STRING')
AddTextComponentSubstringPlayerName(text)
EndTextCommandThefeedPostTicker(false, true)
end
local function Schliessen()
if not istOffen then return end
istOffen = false
SetNuiFocus(false, false)
SendeAnNui({ aktion = 'schliessen' })
pcall(function() exports['aether-hud']:SetzeSichtbar(true) end)
end
-- ---------------------------------------------------------------------
-- Ereignisse vom Server
-- ---------------------------------------------------------------------
AddEventHandler('aether:client:bereit', function()
istGeladen = true
end)
--- Oberfläche mit vollem Zustand anzeigen
RegisterNetEvent('aether:medical:zeige', function(daten)
istOffen = true
imDienst = true
SetNuiFocus(true, true)
pcall(function() exports['aether-hud']:SetzeSichtbar(false) end)
daten.aktion = 'oeffnen'
SendeAnNui(daten)
end)
--- Anmeldebildschirm (noch nicht im Dienst)
RegisterNetEvent('aether:medical:anmeldung', function(daten)
istOffen = true
SetNuiFocus(true, true)
pcall(function() exports['aether-hud']:SetzeSichtbar(false) end)
daten.aktion = 'anmeldung'
SendeAnNui(daten)
end)
RegisterNetEvent('aether:medical:schliessen', function()
imDienst = false
Schliessen()
ZeigeSpielmeldung('Dienst beendet.')
end)
--- Antwort auf einen Modul-Aufruf
RegisterNetEvent('aether:medical:antwort', function(ruf, ergebnis, fehler)
SendeAnNui({ aktion = 'antwort', ruf = ruf, ergebnis = ergebnis, fehler = fehler })
end)
--- Dienstliste (wer ist im Dienst, für Dashboard und Leitstelle)
RegisterNetEvent('aether:medical:dienstliste', function(liste)
if istOffen then
SendeAnNui({ aktion = 'dienstliste', mitarbeiter = liste })
end
end)
--- Hinweis / Erinnerung
RegisterNetEvent('aether:medical:hinweis', function(hinweis)
if istOffen then
SendeAnNui({ aktion = 'hinweis', hinweis = hinweis })
else
-- Auch ausserhalb der Oberfläche erreichbar, damit Fristen nicht untergehen
local praefix = hinweis.prioritaet == 'kritisch' and '~r~' or
hinweis.prioritaet == 'hoch' and '~o~' or '~b~'
ZeigeSpielmeldung(praefix .. hinweis.titel .. '~s~' ..
(hinweis.text and ('\n' .. hinweis.text) or ''))
end
end)
RegisterNetEvent('aether:medical:fehler', function(text)
if istOffen then
SendeAnNui({ aktion = 'fehler', text = text })
else
ZeigeSpielmeldung(text)
end
end)
--- Es hat sich etwas an den Einsätzen geändert
RegisterNetEvent('aether:medical:einsatzAenderung', function()
if istOffen then
SendeAnNui({ aktion = 'einsatzAenderung' })
end
end)
--- Die Leitstelle bittet um Übernahme eines Einsatzes
RegisterNetEvent('aether:medical:einsatzZuweisung', function(daten)
PlaySoundFrontend(-1, 'Event_Start_Text', 'GTAO_FM_Events_Soundset', true)
if daten.position then
SetNewWaypoint(daten.position.x, daten.position.y)
end
if istOffen then
SendeAnNui({ aktion = 'einsatzZuweisung', daten = daten })
else
ZeigeSpielmeldung(('~b~Zuweisung von %s~s~\n%s — %s')
:format(daten.von, daten.nummer, daten.titel))
end
end)
-- ---------------------------------------------------------------------
-- Fahrzeug-Telemetrie
--
-- Meldet Tatsachen über das gefahrene Rettungsfahrzeug: Position,
-- Tankstand, Zustand. Der Server verwirft die Meldung, wenn das
-- Kennzeichen nicht zum Fuhrpark gehört.
-- ---------------------------------------------------------------------
CreateThread(function()
while true do
Wait(30000) -- alle 30 Sekunden genügt
if imDienst then
local ped = PlayerPedId()
if IsPedInAnyVehicle(ped, false) then
local fahrzeug = GetVehiclePedIsIn(ped, false)
-- Nur melden, wenn man selbst am Steuer sitzt
if GetPedInVehicleSeat(fahrzeug, -1) == ped then
local position = GetEntityCoords(fahrzeug)
TriggerServerEvent('aether:medical:fahrzeugMelden', {
kennzeichen = GetVehicleNumberPlateText(fahrzeug),
tank = GetVehicleFuelLevel(fahrzeug),
zustand = GetVehicleBodyHealth(fahrzeug),
x = position.x, y = position.y, z = position.z,
})
end
end
end
end
end)
-- ---------------------------------------------------------------------
-- Terminals in der Klinik
-- ---------------------------------------------------------------------
CreateThread(function()
for _, terminal in ipairs(Medizin.Config.Terminals) do
local blip = AddBlipForCoord(terminal.pos.x, terminal.pos.y, terminal.pos.z)
SetBlipSprite(blip, 61)
SetBlipColour(blip, 2)
SetBlipScale(blip, 0.8)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName('STRING')
AddTextComponentString('Klinik ' .. terminal.name)
EndTextCommandSetBlipName(blip)
end
while true do
local pause = 800
if istGeladen and not istOffen then
local position = GetEntityCoords(PlayerPedId())
for _, terminal in ipairs(Medizin.Config.Terminals) do
if #(position - terminal.pos) <= Medizin.Config.TerminalWeite then
pause = 0
SetTextFont(4)
SetTextScale(0.34, 0.34)
SetTextColour(255, 255, 255, 215)
SetTextCentre(true)
SetTextOutline()
SetTextEntry('STRING')
AddTextComponentString('[E] Klinik-PC ' .. terminal.name)
SetDrawOrigin(terminal.pos.x, terminal.pos.y, terminal.pos.z + 0.9, 0)
DrawText(0.0, 0.0)
ClearDrawOrigin()
if IsControlJustReleased(0, 38) then
TriggerServerEvent('aether:medical:oeffnen')
end
break
end
end
end
Wait(pause)
end
end)
--- Tablet jederzeit öffnen, sobald man im Dienst ist
RegisterCommand('kis', function()
if istGeladen and not istOffen then
TriggerServerEvent('aether:medical:oeffnen')
end
end, false)
RegisterKeyMapping('kis', 'Klinik-Tablet öffnen', 'keyboard', 'F6')
-- ---------------------------------------------------------------------
-- NUI-Callbacks
-- ---------------------------------------------------------------------
RegisterNUICallback('schliessen', function(_, rueckgabe)
Schliessen()
rueckgabe({ ok = true })
end)
--- Zentraler Modul-Aufruf: die Oberfläche fragt, der Server antwortet
RegisterNUICallback('aufruf', function(daten, rueckgabe)
TriggerServerEvent('aether:medical:aufruf',
tostring(daten.modul or ''), tostring(daten.aktion or ''),
daten.daten or {}, daten.ruf)
rueckgabe({ ok = true })
end)
RegisterNUICallback('dienstAntreten', function(daten, rueckgabe)
TriggerServerEvent('aether:medical:dienstAntreten', tostring(daten.funktion or 'station'))
rueckgabe({ ok = true })
end)
RegisterNUICallback('dienstBeenden', function(_, rueckgabe)
TriggerServerEvent('aether:medical:dienstBeenden')
rueckgabe({ ok = true })
end)
RegisterNUICallback('status', function(daten, rueckgabe)
TriggerServerEvent('aether:medical:status', tostring(daten.status or ''))
rueckgabe({ ok = true })
end)
RegisterNUICallback('funktion', function(daten, rueckgabe)
TriggerServerEvent('aether:medical:funktion', tostring(daten.funktion or ''))
rueckgabe({ ok = true })
end)
RegisterNUICallback('leitstelle', function(daten, rueckgabe)
TriggerServerEvent('aether:medical:leitstelle', tonumber(daten.quelle))
rueckgabe({ ok = true })
end)
--- Wegpunkt auf der Karte setzen (z. B. zu einem Einsatzort)
RegisterNUICallback('wegpunkt', function(daten, rueckgabe)
if daten.x and daten.y then
SetNewWaypoint(daten.x + 0.0, daten.y + 0.0)
ZeigeSpielmeldung('Wegpunkt gesetzt.')
end
rueckgabe({ ok = true })
end)
-- ---------------------------------------------------------------------
-- Aufräumen
-- ---------------------------------------------------------------------
AddEventHandler('onResourceStop', function(ressource)
if ressource ~= GetCurrentResourceName() then return end
SetNuiFocus(false, false)
end)