- 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>
242 lines
8.8 KiB
Lua
242 lines
8.8 KiB
Lua
-- =====================================================================
|
|
-- aether-medical — Server-Schnittstelle
|
|
--
|
|
-- Ein einziges Netz-Event bedient alle Module. Jede Anfrage durchläuft
|
|
-- Dienstprüfung und Rechteprüfung, bevor sie ein Modul erreicht.
|
|
-- =====================================================================
|
|
|
|
local Core = exports['aether-core']
|
|
|
|
--- Baut das Datenpaket für die Oberfläche
|
|
--- @param quelle number Server-ID
|
|
--- @param mitarbeiter table Mitarbeiter im Dienst
|
|
--- @return table
|
|
local function BaueTabletDaten(quelle, mitarbeiter)
|
|
return {
|
|
klinik = Medizin.Config.Klinik,
|
|
kuerzel = Medizin.Config.Kuerzel,
|
|
module = Kern.SichtbareModule(mitarbeiter),
|
|
mitarbeiter = {
|
|
name = mitarbeiter.name,
|
|
personalnummer = mitarbeiter.personalnummer,
|
|
rolle = mitarbeiter.rolle,
|
|
rolleName = mitarbeiter.rolleName,
|
|
station = mitarbeiter.station,
|
|
funktion = mitarbeiter.funktion,
|
|
status = mitarbeiter.status,
|
|
leitstelle = Dienst.Leitstelle() == quelle,
|
|
},
|
|
status = Medizin.Config.Dienststatus,
|
|
funktionen = Medizin.Config.Dienstfunktionen,
|
|
stationen = Medizin.Config.Stationen,
|
|
}
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Oberfläche öffnen
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:medical:oeffnen', function()
|
|
local quelle = source
|
|
|
|
local mitarbeiter = Dienst.Hole(quelle)
|
|
if not mitarbeiter then
|
|
-- Noch nicht im Dienst: Personalakte prüfen und Anmeldung anbieten
|
|
local charakterId = Core:HoleCharakterId(quelle)
|
|
if not charakterId then return end
|
|
|
|
local geprueft, fehler = Dienst.LadeMitarbeiter(quelle, charakterId)
|
|
if not geprueft then
|
|
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
|
|
return
|
|
end
|
|
|
|
-- Nur Funktionen anbieten, für die auch das Recht vorliegt
|
|
local waehlbar = {}
|
|
for _, eintrag in ipairs(Medizin.Config.Dienstfunktionen) do
|
|
if not eintrag.recht or Rechte.Hat(geprueft, eintrag.recht) then
|
|
waehlbar[#waehlbar + 1] = { id = eintrag.id, name = eintrag.name }
|
|
end
|
|
end
|
|
|
|
TriggerClientEvent('aether:medical:anmeldung', quelle, {
|
|
name = geprueft.name,
|
|
personalnummer = geprueft.personalnummer,
|
|
rolleName = geprueft.rolleName,
|
|
station = geprueft.station,
|
|
klinik = Medizin.Config.Klinik,
|
|
funktionen = waehlbar,
|
|
})
|
|
return
|
|
end
|
|
|
|
TriggerClientEvent('aether:medical:zeige', quelle, BaueTabletDaten(quelle, mitarbeiter))
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Dienst
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:medical:dienstAntreten', function(funktion)
|
|
local quelle = source
|
|
local mitarbeiter, fehler = Dienst.Antreten(quelle,
|
|
type(funktion) == 'string' and funktion or nil)
|
|
|
|
if not mitarbeiter then
|
|
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
|
|
return
|
|
end
|
|
|
|
TriggerClientEvent('aether:medical:zeige', quelle, BaueTabletDaten(quelle, mitarbeiter))
|
|
end)
|
|
|
|
RegisterNetEvent('aether:medical:dienstBeenden', function()
|
|
local quelle = source
|
|
Dienst.Beenden(quelle)
|
|
TriggerClientEvent('aether:medical:schliessen', quelle)
|
|
end)
|
|
|
|
RegisterNetEvent('aether:medical:status', function(status)
|
|
local quelle = source
|
|
if type(status) ~= 'string' then return end
|
|
Dienst.SetzeStatus(quelle, status)
|
|
end)
|
|
|
|
RegisterNetEvent('aether:medical:funktion', function(funktion)
|
|
local quelle = source
|
|
if type(funktion) ~= 'string' then return end
|
|
|
|
local erfolg, fehler = Dienst.SetzeFunktion(quelle, funktion)
|
|
if not erfolg and fehler then
|
|
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('aether:medical:leitstelle', function(zielQuelle)
|
|
local quelle = source
|
|
local erfolg, fehler = Dienst.SetzeLeitstelle(quelle,
|
|
type(zielQuelle) == 'number' and zielQuelle or nil)
|
|
if not erfolg and fehler then
|
|
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
|
|
end
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Zentraler Modul-Aufruf
|
|
--
|
|
-- Die Oberfläche schickt { modul, aktion, daten, ruf }. Der `ruf` ist
|
|
-- eine laufende Nummer, damit die Antwort zugeordnet werden kann.
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:medical:aufruf', function(modulId, aktion, daten, ruf)
|
|
local quelle = source
|
|
|
|
if type(modulId) ~= 'string' or type(aktion) ~= 'string' then return end
|
|
|
|
local mitarbeiter = Dienst.Hole(quelle)
|
|
if not mitarbeiter then
|
|
TriggerClientEvent('aether:medical:antwort', quelle, ruf, nil,
|
|
'Du bist nicht im Dienst.')
|
|
return
|
|
end
|
|
|
|
local ergebnis, fehler = Kern.FuehreAus(quelle, mitarbeiter, modulId, aktion, daten)
|
|
TriggerClientEvent('aether:medical:antwort', quelle, ruf, ergebnis, fehler)
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Eingehende Notrufe
|
|
--
|
|
-- Sie erzeugen einen Einsatz mit Status „offen". Zugewiesen wird
|
|
-- ausschliesslich von der Leitstelle — nie automatisch.
|
|
--
|
|
-- `EinsatzEingang` stellt das Einsatzmodul bereit. Solange es nicht
|
|
-- geladen ist, wird ein Notruf schlicht nicht angenommen, statt
|
|
-- irgendwo ins Leere zu laufen.
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Nimmt einen Notruf entgegen, sofern das Einsatzmodul geladen ist
|
|
--- @param daten table Einsatzdaten
|
|
--- @return number|nil Einsatz-ID
|
|
local function NotrufAnnehmen(daten)
|
|
if type(EinsatzEingang) ~= 'function' then
|
|
Aether.Fehler('Notruf verworfen: Das Einsatzmodul ist nicht geladen.')
|
|
return nil
|
|
end
|
|
return EinsatzEingang(daten)
|
|
end
|
|
|
|
--- Notruf aus dem Handy (112)
|
|
AddEventHandler('aether:phone:notruf', function(quelle, fraktionId, nummer)
|
|
if fraktionId ~= 'lsmc' and fraktionId ~= 'medic' and fraktionId ~= 'ems' then return end
|
|
|
|
local position = GetEntityCoords(GetPlayerPed(quelle))
|
|
NotrufAnnehmen({
|
|
titel = 'Notruf über Telefon',
|
|
kategorie = 'notruf',
|
|
prioritaet = 2,
|
|
ort = 'Position des Anrufers',
|
|
beschreibung = ('Eingehender Anruf von %s.'):format(nummer or 'unbekannt'),
|
|
position = { x = position.x, y = position.y, z = position.z },
|
|
quelle = 'notruf',
|
|
})
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- EXPORTS — Schnittstelle für externe Systeme
|
|
--
|
|
-- Damit lassen sich Notrufe und Hinweise von aussen einspeisen, ohne
|
|
-- dass am Kern etwas geändert werden muss. Keiner dieser Exports
|
|
-- behandelt, diagnostiziert oder entlässt jemanden.
|
|
-- =====================================================================
|
|
|
|
--- Meldet einen Notfall an die Klinik.
|
|
--- Er erscheint als offener Einsatz — zugewiesen wird er nie automatisch.
|
|
exports('MeldeNotfall', function(daten)
|
|
if type(daten) ~= 'table' or not daten.titel then return nil end
|
|
return NotrufAnnehmen(daten)
|
|
end)
|
|
|
|
--- Erstellt einen Hinweis für alle im Dienst
|
|
exports('MeldeHinweis', function(daten)
|
|
if type(daten) ~= 'table' or not daten.titel then return false end
|
|
Hinweise.Erstelle(nil, daten)
|
|
return true
|
|
end)
|
|
|
|
--- Ist ein Spieler als Mediziner im Dienst?
|
|
exports('IstImDienst', function(quelle)
|
|
return Dienst.Hole(quelle) ~= nil
|
|
end)
|
|
|
|
--- Wie viele Mediziner sind gerade im Dienst?
|
|
exports('AnzahlImDienst', function()
|
|
local anzahl = 0
|
|
for _ in pairs(Dienst.AlleImDienst()) do anzahl = anzahl + 1 end
|
|
return anzahl
|
|
end)
|
|
|
|
--- Liefert die Dienstdaten eines Mitarbeiters (ohne Rechtetabelle)
|
|
exports('HoleMitarbeiter', function(quelle)
|
|
local mitarbeiter = Dienst.Hole(quelle)
|
|
if not mitarbeiter then return nil end
|
|
return {
|
|
charakterId = mitarbeiter.charakterId,
|
|
name = mitarbeiter.name,
|
|
personalnummer = mitarbeiter.personalnummer,
|
|
rolle = mitarbeiter.rolle,
|
|
rolleName = mitarbeiter.rolleName,
|
|
station = mitarbeiter.station,
|
|
funktion = mitarbeiter.funktion,
|
|
status = mitarbeiter.status,
|
|
}
|
|
end)
|
|
|
|
--- Erlaubt fremden Ressourcen, ein eigenes Modul zu registrieren
|
|
exports('RegistriereModul', function(beschreibung)
|
|
return Kern.RegistriereModul(beschreibung)
|
|
end)
|
|
|
|
--- Erlaubt fremden Ressourcen, eine Aktion beizusteuern
|
|
exports('RegistriereAktion', function(modulId, aktion, recht, funktion)
|
|
Kern.RegistriereAktion(modulId, aktion, recht, funktion)
|
|
return true
|
|
end)
|