aether-framework/resources/[aether]/aether-character/client/vorschau.lua
Jerrit Fritzsche 11cc043b6c Charakterauswahl: Vorschau-Crash, Blackscreen und Debug beheben
- Vorschau (3D-Ped/Kamera) in einen eigenen, verzoegerten Thread mit
  Laufnummer-Schutz ausgelagert. Damit laeuft die vernetzte Ped-Erzeugung
  nicht mehr zeitgleich mit dem Spieler-Teleport der Auswahl ->
  behebt "SendNetPacket called from multiple threads" (Crash beim Oeffnen).
- Blackscreen nach dem Spawn behoben: der Fade wird nun allein vom Core
  gesteuert (Fade-out zu Beginn von spielerGeladen, Fade-in am Ende);
  SchliesseAuswahl blendet nicht mehr selbst -> kein Fade-Konflikt mehr.
- Temporaere [AETHER-DEBUG]-Ausgaben wieder entfernt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 20:47:10 +02:00

212 lines
7.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'
-- Laufnummer: entwertet noch laufende, verzoegerte Starts, wenn zwischendurch
-- beendet oder neu gestartet wird.
local aktuellerLauf = 0
-- ---------------------------------------------------------------------
-- 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)
aktuellerLauf = aktuellerLauf + 1
local lauf = aktuellerLauf
-- WICHTIG: Ped und Kamera in einem EIGENEN Thread mit kurzer Verzoegerung
-- erzeugen. Sonst laeuft die (vernetzte) Ped-Erzeugung im selben Moment wie
-- der Spieler-Ped-Teleport der Auswahl -> zwei Threads schreiben Netz-State
-- ("SendNetPacket ... called from multiple threads") -> Client-Crash.
CreateThread(function()
Wait(250)
if lauf ~= aktuellerLauf then return end -- inzwischen beendet/neu gestartet
-- 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
if lauf ~= aktuellerLauf then SetModelAsNoLongerNeeded(modell); 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)
-- Falls waehrenddessen beendet wurde: sofort wieder aufraeumen
if lauf ~= aktuellerLauf then Vorschau.Beenden() end
end)
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()
-- Noch laufende, verzoegerte Starts entwerten
aktuellerLauf = aktuellerLauf + 1
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