- 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>
88 lines
3.4 KiB
Lua
88 lines
3.4 KiB
Lua
-- =====================================================================
|
|
-- aether-admin / Modul: Weltsteuerung
|
|
--
|
|
-- Zeit, Wetter, Blackout und Bereinigung. Der Zustand wird an ALLE
|
|
-- Clients gespiegelt und neu verbundenen Spielern beim Laden geschickt,
|
|
-- damit die Welt für alle gleich aussieht.
|
|
-- =====================================================================
|
|
|
|
Admin = Admin or {}
|
|
|
|
-- Gemeinsamer Weltzustand (Standard beim Serverstart)
|
|
Admin.Welt = Admin.Welt or {
|
|
stunde = 12, minute = 0, wetter = 'CLEAR',
|
|
zeitFix = false, wetterFix = false, blackout = false,
|
|
}
|
|
|
|
local WETTER = {
|
|
['Klar'] = 'CLEAR', ['Bewölkt'] = 'CLOUDS', ['Regen'] = 'RAIN',
|
|
['Gewitter'] = 'THUNDER', ['Nebel'] = 'FOGGY', ['Schnee'] = 'XMAS',
|
|
['Sturm'] = 'OVERCAST',
|
|
}
|
|
|
|
--- Schickt den aktuellen Weltzustand an einen oder alle Clients
|
|
--- @param ziel number|nil Server-ID oder nil = alle
|
|
function Admin.SendeWelt(ziel)
|
|
TriggerClientEvent('aether-admin:client:welt', ziel or -1, Admin.Welt)
|
|
end
|
|
|
|
Admin.Kern.Registriere('welt', 'zeit', function(handelnder, daten)
|
|
local stunde = math.max(0, math.min(23, math.floor(tonumber(daten.stunde) or 12)))
|
|
Admin.Welt.stunde = stunde
|
|
Admin.Welt.minute = 0
|
|
if daten.fix ~= nil then Admin.Welt.zeitFix = daten.fix and true or false end
|
|
Admin.SendeWelt()
|
|
Admin.Log.Schreibe(handelnder, 'Zeit gesetzt', nil, ('%02d:00'):format(stunde))
|
|
return { ok = true, welt = Admin.Welt }
|
|
end)
|
|
|
|
Admin.Kern.Registriere('welt', 'wetter', function(handelnder, daten)
|
|
local anzeige = tostring(daten.wetter or 'Klar')
|
|
Admin.Welt.wetter = WETTER[anzeige] or 'CLEAR'
|
|
if daten.fix ~= nil then Admin.Welt.wetterFix = daten.fix and true or false end
|
|
Admin.SendeWelt()
|
|
Admin.Log.Schreibe(handelnder, 'Wetter gesetzt', nil, anzeige)
|
|
return { ok = true, welt = Admin.Welt }
|
|
end)
|
|
|
|
Admin.Kern.Registriere('welt', 'blackout', function(handelnder, daten)
|
|
Admin.Welt.blackout = not Admin.Welt.blackout
|
|
Admin.SendeWelt()
|
|
Admin.Log.Schreibe(handelnder, 'Blackout ' .. (Admin.Welt.blackout and 'an' or 'aus'))
|
|
return { ok = true, blackout = Admin.Welt.blackout }
|
|
end)
|
|
|
|
Admin.Kern.Registriere('welt', 'cleanup', function(handelnder, daten)
|
|
local art = tostring(daten.art or '')
|
|
local entfernt = 0
|
|
|
|
if art == 'veh' then
|
|
for _, veh in ipairs(GetAllVehicles() or {}) do
|
|
-- Nur leere Fahrzeuge entfernen (niemanden aus dem Sitz reißen)
|
|
if GetVehicleNumberOfPassengers(veh) == 0 and IsVehicleSeatFree(veh, -1) then
|
|
DeleteEntity(veh); entfernt = entfernt + 1
|
|
end
|
|
end
|
|
elseif art == 'ped' then
|
|
for _, ped in ipairs(GetAllPeds() or {}) do
|
|
if not IsPedAPlayer(ped) then DeleteEntity(ped); entfernt = entfernt + 1 end
|
|
end
|
|
elseif art == 'obj' then
|
|
for _, obj in ipairs(GetAllObjects() or {}) do
|
|
DeleteEntity(obj); entfernt = entfernt + 1
|
|
end
|
|
elseif art == 'ground' then
|
|
-- Herumliegende (tote) NPCs
|
|
for _, ped in ipairs(GetAllPeds() or {}) do
|
|
if not IsPedAPlayer(ped) and IsPedDeadOrDying(ped, true) then
|
|
DeleteEntity(ped); entfernt = entfernt + 1
|
|
end
|
|
end
|
|
else
|
|
return nil, 'Unbekannte Bereinigung.'
|
|
end
|
|
|
|
Admin.Log.Schreibe(handelnder, 'Bereinigt', art, entfernt .. ' entfernt')
|
|
return { ok = true, entfernt = entfernt }
|
|
end)
|