- 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>
196 lines
6.5 KiB
Lua
196 lines
6.5 KiB
Lua
-- =====================================================================
|
||
-- Aether HUD — Clientlogik
|
||
-- Versorgt die Oberflaeche mit Status, Geld, Voice und Fahrzeugwerten.
|
||
-- Es wird nur gesendet, wenn sich ein Wert tatsaechlich geaendert hat.
|
||
-- =====================================================================
|
||
|
||
-- Ist der Spieler fertig geladen? (vorher kein HUD zeigen)
|
||
local istGeladen = false
|
||
-- Soll das HUD sichtbar sein? (z. B. bei offenen Menues ausblendbar)
|
||
local istSichtbar = true
|
||
|
||
-- Zuletzt gesendete Werte, um unnoetige Nachrichten zu vermeiden
|
||
local letzterStatus = {}
|
||
local letztesFahrzeug = {}
|
||
local letzteVoice = {}
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Hilfsfunktionen
|
||
-- ---------------------------------------------------------------------
|
||
|
||
--- Schickt eine Nachricht an die Oberflaeche
|
||
--- @param nachricht table Zu sendende Daten
|
||
local function SendeAnHud(nachricht)
|
||
SendNuiMessage(json.encode(nachricht))
|
||
end
|
||
|
||
--- Prueft, ob sich mindestens ein Feld gegenueber dem Zwischenspeicher geaendert hat
|
||
--- @param neu table Neue Werte
|
||
--- @param alt table Zwischenspeicher (wird aktualisiert)
|
||
--- @return boolean true, wenn sich etwas geaendert hat
|
||
local function HatSichGeaendert(neu, alt)
|
||
local geaendert = false
|
||
for schluessel, wert in pairs(neu) do
|
||
if alt[schluessel] ~= wert then
|
||
alt[schluessel] = wert
|
||
geaendert = true
|
||
end
|
||
end
|
||
return geaendert
|
||
end
|
||
|
||
--- Rechnet die Lebensenergie (100–200) in Prozent (0–100) um.
|
||
--- GTA zaehlt Leben ab 100; darunter ist der Spieler tot.
|
||
--- @param ped number Ped-Handle
|
||
--- @return number Prozentwert 0–100
|
||
local function LebenInProzent(ped)
|
||
local leben = GetEntityHealth(ped)
|
||
if leben <= 100 then return 0 end
|
||
return Aether.Begrenze((leben - 100) / 100 * 100, 0, 100)
|
||
end
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Ereignisse vom Core
|
||
-- ---------------------------------------------------------------------
|
||
|
||
--- Spieler ist im Spiel: HUD mit den Startwerten fuellen
|
||
AddEventHandler('aether:client:bereit', function(daten)
|
||
istGeladen = true
|
||
|
||
SendeAnHud({
|
||
aktion = 'geld',
|
||
bargeld = daten.bargeld,
|
||
konto = daten.konto,
|
||
schwarzgeld = daten.schwarzgeld,
|
||
-- Schwarzgeld nur zeigen, wenn welches vorhanden ist
|
||
schwarzgeldSichtbar = (daten.schwarzgeld or 0) > 0,
|
||
})
|
||
|
||
SendeAnHud({
|
||
aktion = 'status',
|
||
leben = LebenInProzent(PlayerPedId()),
|
||
hunger = daten.hunger,
|
||
durst = daten.durst,
|
||
ausdauer = daten.ausdauer,
|
||
})
|
||
|
||
SendeAnHud({ aktion = 'sichtbar', an = true })
|
||
end)
|
||
|
||
--- Geld hat sich geaendert
|
||
AddEventHandler('aether:client:geldGeaendert', function(geld)
|
||
SendeAnHud({
|
||
aktion = 'geld',
|
||
bargeld = geld.bargeld,
|
||
konto = geld.konto,
|
||
schwarzgeld = geld.schwarzgeld,
|
||
schwarzgeldSichtbar = (geld.schwarzgeld or 0) > 0,
|
||
})
|
||
end)
|
||
|
||
--- Hunger/Durst/Ausdauer haben sich geaendert
|
||
AddEventHandler('aether:client:zustandGeaendert', function(zustand)
|
||
SendeAnHud({
|
||
aktion = 'status',
|
||
hunger = zustand.hunger,
|
||
durst = zustand.durst,
|
||
ausdauer = zustand.ausdauer,
|
||
})
|
||
end)
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Laufende Erfassung: Leben und Ausdauer
|
||
-- ---------------------------------------------------------------------
|
||
CreateThread(function()
|
||
while true do
|
||
Wait(500)
|
||
if istGeladen then
|
||
local ped = PlayerPedId()
|
||
|
||
-- Ausdauer aus dem Sprint-Vorrat ableiten (100 = ausgeruht)
|
||
local ausdauer = 100.0 - GetPlayerSprintStaminaRemaining(PlayerId())
|
||
|
||
local neu = {
|
||
leben = math.floor(LebenInProzent(ped)),
|
||
ausdauer = math.floor(Aether.Begrenze(ausdauer, 0, 100)),
|
||
}
|
||
|
||
if HatSichGeaendert(neu, letzterStatus) then
|
||
neu.aktion = 'status'
|
||
SendeAnHud(neu)
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Laufende Erfassung: Voice
|
||
-- ---------------------------------------------------------------------
|
||
CreateThread(function()
|
||
while true do
|
||
Wait(200)
|
||
if istGeladen then
|
||
-- Sprechen erkennen (funktioniert mit dem eingebauten Mumble-Voice)
|
||
local redet = NetworkIsPlayerTalking(PlayerId())
|
||
|
||
-- Reichweite aus der Mumble-Einstellung ableiten
|
||
local reichweite = MumbleGetTalkerProximity() or 0.0
|
||
local stufe = 1 -- 0 = Fluestern, 1 = Normal, 2 = Rufen
|
||
if reichweite <= 3.5 then stufe = 0
|
||
elseif reichweite >= 12.0 then stufe = 2 end
|
||
|
||
local neu = { redet = redet, voiceStufe = stufe }
|
||
if HatSichGeaendert(neu, letzteVoice) then
|
||
neu.aktion = 'voice'
|
||
SendeAnHud(neu)
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Laufende Erfassung: Fahrzeug
|
||
-- Im Fahrzeug haeufiger abfragen, damit der Tacho fluessig laeuft.
|
||
-- ---------------------------------------------------------------------
|
||
CreateThread(function()
|
||
while true do
|
||
local pause = 500
|
||
|
||
if istGeladen then
|
||
local daten = Fahrzeug.HoleDaten()
|
||
|
||
if daten then
|
||
pause = 100 -- fluessige Tachoanzeige
|
||
if HatSichGeaendert(daten, letztesFahrzeug) then
|
||
daten.aktion = 'fahrzeug'
|
||
SendeAnHud(daten)
|
||
end
|
||
elseif letztesFahrzeug.imFahrzeug ~= false then
|
||
-- Gerade ausgestiegen: Fahrzeuganzeige ausblenden
|
||
letztesFahrzeug = { imFahrzeug = false }
|
||
SendeAnHud({ aktion = 'fahrzeug', imFahrzeug = false, tempo = 0 })
|
||
end
|
||
end
|
||
|
||
Wait(pause)
|
||
end
|
||
end)
|
||
|
||
-- =====================================================================
|
||
-- EXPORTS — andere Module koennen das HUD aus- und einblenden
|
||
-- Beispiel: exports['aether-hud']:SetzeSichtbar(false)
|
||
-- =====================================================================
|
||
|
||
--- Blendet das HUD ein oder aus
|
||
--- @param an boolean true = sichtbar
|
||
exports('SetzeSichtbar', function(an)
|
||
istSichtbar = an and true or false
|
||
SendeAnHud({ aktion = 'sichtbar', an = istSichtbar })
|
||
end)
|
||
|
||
--- Ist das HUD gerade sichtbar?
|
||
--- @return boolean
|
||
exports('IstSichtbar', function()
|
||
return istSichtbar
|
||
end)
|