- 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>
262 lines
9.0 KiB
Lua
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)
|