- 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>
190 lines
6.6 KiB
Lua
190 lines
6.6 KiB
Lua
-- =====================================================================
|
|
-- Aether Character — 3D-Vorschau
|
|
-- Spawnt einen lokalen Ped als lebende Vorschau und richtet eine Kamera
|
|
-- darauf. Die NUI liegt mit einem transparenten Bereich darueber.
|
|
-- =====================================================================
|
|
|
|
Vorschau = {}
|
|
|
|
-- Ort, an dem der Vorschau-Ped steht (ruhige Ecke am Spawnbereich)
|
|
local VORSCHAU_POS = { x = -1040.0, y = -2748.0, z = 21.36 }
|
|
|
|
-- Kameraeinstellungen je Ansicht
|
|
-- abstand = Entfernung vor dem Ped, hoehe = Kamerahoehe, ziel = Blickpunkt am Ped
|
|
local MODI = {
|
|
gesicht = { abstand = 1.05, hoehe = 0.68, ziel = 0.68, sicht = 28.0 },
|
|
koerper = { abstand = 2.90, hoehe = 0.25, ziel = 0.20, sicht = 40.0 },
|
|
}
|
|
|
|
-- Laufender Zustand der Vorschau
|
|
local vorschauPed = nil -- Handle des Vorschau-Peds
|
|
local vorschauKamera= nil -- Handle der Kamera
|
|
local aktuellesModell = nil -- Aktuell geladenes Ped-Modell (fuer Geschlechtswechsel)
|
|
local aktuelleDrehung = 0.0 -- Zusaetzliche Drehung durch den Spieler
|
|
local aktuellerModus = 'koerper'
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Interne Helfer
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Laedt ein Ped-Modell und wartet, bis es bereit ist
|
|
--- @param modellName string Name des Modells
|
|
--- @return number|nil Modell-Hash oder nil bei Fehlschlag
|
|
local function LadeModell(modellName)
|
|
local modell = GetHashKey(modellName)
|
|
RequestModel(modell)
|
|
|
|
local versuche = 0
|
|
while not HasModelLoaded(modell) and versuche < 100 do
|
|
Wait(50)
|
|
versuche = versuche + 1
|
|
end
|
|
|
|
if not HasModelLoaded(modell) then return nil end
|
|
return modell
|
|
end
|
|
|
|
--- Setzt die Kamera auf die gewuenschte Ansicht
|
|
--- @param modus string 'gesicht' | 'koerper'
|
|
--- @param weich boolean|nil Sanft ueberblenden statt springen
|
|
local function SetzeKameraAuf(modus, weich)
|
|
if not vorschauKamera then return end
|
|
|
|
local einstellung = MODI[modus] or MODI.koerper
|
|
aktuellerModus = modus
|
|
|
|
-- Kamera steht noerdlich vor dem Ped; der Ped schaut sie an.
|
|
-- Fest im Raum verankert, damit das Drehen des Peds die Kamera nicht mitbewegt.
|
|
SetCamCoord(vorschauKamera,
|
|
VORSCHAU_POS.x,
|
|
VORSCHAU_POS.y + einstellung.abstand,
|
|
VORSCHAU_POS.z + einstellung.hoehe)
|
|
|
|
PointCamAtCoord(vorschauKamera,
|
|
VORSCHAU_POS.x, VORSCHAU_POS.y, VORSCHAU_POS.z + einstellung.ziel)
|
|
|
|
SetCamFov(vorschauKamera, einstellung.sicht)
|
|
|
|
if weich then
|
|
-- Kurze Ueberblendung fuer einen weichen Wechsel
|
|
SetCamActiveWithInterp(vorschauKamera, vorschauKamera, 400, 1, 1)
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Oeffentliche Schnittstelle
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Startet die Vorschau: erzeugt Ped und Kamera
|
|
--- @param aussehen table|nil Startaussehen (bestimmt auch das Geschlecht)
|
|
--- @param modus string|nil Startansicht ('gesicht' | 'koerper')
|
|
function Vorschau.Starten(aussehen, modus)
|
|
-- Umgebung laden, damit der Ped nicht durch den Boden faellt
|
|
RequestCollisionAtCoord(VORSCHAU_POS.x, VORSCHAU_POS.y, VORSCHAU_POS.z)
|
|
|
|
local modellName = AetherAussehen.HoleModell(aussehen and aussehen.geschlecht)
|
|
local modell = LadeModell(modellName)
|
|
if not modell then
|
|
print('^1[Aether]^7 Vorschau-Modell konnte nicht geladen werden: ' .. modellName)
|
|
return
|
|
end
|
|
|
|
-- Ped nur lokal erzeugen (nicht im Netzwerk sichtbar)
|
|
vorschauPed = CreatePed(4, modell, VORSCHAU_POS.x, VORSCHAU_POS.y, VORSCHAU_POS.z,
|
|
0.0, false, false)
|
|
aktuellesModell = modellName
|
|
aktuelleDrehung = 0.0
|
|
|
|
SetEntityHeading(vorschauPed, 0.0) -- schaut Richtung Kamera
|
|
FreezeEntityPosition(vorschauPed, true)
|
|
SetEntityInvincible(vorschauPed, true)
|
|
SetBlockingOfNonTemporaryEvents(vorschauPed, true)
|
|
SetModelAsNoLongerNeeded(modell)
|
|
|
|
if aussehen then
|
|
AetherAussehen.Anwenden(vorschauPed, aussehen)
|
|
end
|
|
|
|
-- Kamera erzeugen und aktivieren
|
|
vorschauKamera = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
|
|
SetzeKameraAuf(modus or 'koerper', false)
|
|
SetCamActive(vorschauKamera, true)
|
|
RenderScriptCams(true, false, 0, true, true)
|
|
end
|
|
|
|
--- Uebertraegt ein Aussehen auf den Vorschau-Ped.
|
|
--- Wechselt das Geschlecht, wird der Ped neu erzeugt.
|
|
--- @param aussehen table Aussehen aus dem Ersteller
|
|
function Vorschau.Anwenden(aussehen)
|
|
if not aussehen then return end
|
|
|
|
-- Geschlechtswechsel erfordert ein anderes Modell
|
|
local gewuenschtesModell = AetherAussehen.HoleModell(aussehen.geschlecht)
|
|
if gewuenschtesModell ~= aktuellesModell then
|
|
local drehung = aktuelleDrehung
|
|
local modus = aktuellerModus
|
|
|
|
if vorschauPed and DoesEntityExist(vorschauPed) then
|
|
DeletePed(vorschauPed)
|
|
end
|
|
|
|
local modell = LadeModell(gewuenschtesModell)
|
|
if not modell then return end
|
|
|
|
vorschauPed = CreatePed(4, modell, VORSCHAU_POS.x, VORSCHAU_POS.y, VORSCHAU_POS.z,
|
|
0.0, false, false)
|
|
aktuellesModell = gewuenschtesModell
|
|
|
|
FreezeEntityPosition(vorschauPed, true)
|
|
SetEntityInvincible(vorschauPed, true)
|
|
SetBlockingOfNonTemporaryEvents(vorschauPed, true)
|
|
SetModelAsNoLongerNeeded(modell)
|
|
|
|
-- Vorherige Drehung und Ansicht wiederherstellen
|
|
aktuelleDrehung = drehung
|
|
SetEntityHeading(vorschauPed, aktuelleDrehung)
|
|
SetzeKameraAuf(modus, false)
|
|
end
|
|
|
|
if vorschauPed and DoesEntityExist(vorschauPed) then
|
|
AetherAussehen.Anwenden(vorschauPed, aussehen)
|
|
end
|
|
end
|
|
|
|
--- Wechselt zwischen Gesichts- und Koerperansicht
|
|
--- @param modus string 'gesicht' | 'koerper'
|
|
function Vorschau.SetzeModus(modus)
|
|
SetzeKameraAuf(modus, true)
|
|
end
|
|
|
|
--- Dreht den Vorschau-Ped um einen Winkel
|
|
--- @param grad number Drehung in Grad (positiv = nach rechts)
|
|
function Vorschau.Drehen(grad)
|
|
if not vorschauPed or not DoesEntityExist(vorschauPed) then return end
|
|
aktuelleDrehung = (aktuelleDrehung + (grad or 0.0)) % 360.0
|
|
SetEntityHeading(vorschauPed, aktuelleDrehung)
|
|
end
|
|
|
|
--- Beendet die Vorschau und raeumt Ped und Kamera auf
|
|
function Vorschau.Beenden()
|
|
if vorschauKamera then
|
|
RenderScriptCams(false, false, 0, true, true)
|
|
DestroyCam(vorschauKamera, false)
|
|
vorschauKamera = nil
|
|
end
|
|
|
|
if vorschauPed and DoesEntityExist(vorschauPed) then
|
|
DeletePed(vorschauPed)
|
|
end
|
|
|
|
vorschauPed = nil
|
|
aktuellesModell = nil
|
|
aktuelleDrehung = 0.0
|
|
end
|
|
|
|
--- Ist gerade eine Vorschau aktiv?
|
|
--- @return boolean
|
|
function Vorschau.IstAktiv()
|
|
return vorschauPed ~= nil
|
|
end
|