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>
This commit is contained in:
Jerrit Fritzsche 2026-07-27 20:47:10 +02:00
parent cad096e7a6
commit 11cc043b6c
5 changed files with 56 additions and 39 deletions

View File

@ -18,8 +18,6 @@ local PARKPLATZ = { x = -1037.7, y = -2737.6, z = 20.2 }
--- @param charaktere table Liste der Charaktere
--- @param maxSlots number Maximale Anzahl Charaktere
local function OeffneAuswahl(charaktere, maxSlots)
print(('[AETHER-DEBUG] client OeffneAuswahl: %d Charaktere')
:format(charaktere and #charaktere or -1))
auswahlOffen = true
-- Eigenen Spieler unsichtbar parken — sichtbar ist nur der Vorschau-Ped
@ -31,7 +29,6 @@ local function OeffneAuswahl(charaktere, maxSlots)
-- 3D-Vorschau mit dem Aussehen des ersten Charakters starten
local erstes = charaktere and charaktere[1] or nil
Vorschau.Starten(erstes and erstes.aussehen or nil, 'koerper')
print('[AETHER-DEBUG] client OeffneAuswahl: Vorschau gestartet, beende Ladebildschirm')
-- Ladebildschirm beenden, damit die Auswahl sichtbar wird
ShutdownLoadingScreen()
@ -53,10 +50,9 @@ local function SchliesseAuswahl()
SendNuiMessage(json.encode({ aktion = 'schliessen' }))
SetNuiFocus(false, false)
DoScreenFadeOut(400)
Wait(400)
Vorschau.Beenden()
-- Bewusst KEIN Fade hier: der Core blendet nach dem Spawn ein. Ein Fade
-- an dieser Stelle kollidiert mit dem Core-Fade -> Blackscreen.
end
-- ---------------------------------------------------------------------

View File

@ -22,6 +22,9 @@ 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
@ -79,6 +82,17 @@ end
--- @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)
@ -88,6 +102,7 @@ function Vorschau.Starten(aussehen, modus)
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,
@ -110,6 +125,10 @@ function Vorschau.Starten(aussehen, modus)
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.
@ -167,6 +186,9 @@ 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)

View File

@ -111,13 +111,10 @@ end
-- Auswahl anzeigen (wird vom Core ausgeloest)
-- ---------------------------------------------------------------------
AddEventHandler('aether:server:charakterAuswahlNoetig', function(quelle)
print(('[AETHER-DEBUG] character-server: charakterAuswahlNoetig empfangen fuer %s')
:format(tostring(quelle)))
local lizenz = HoleLizenz(quelle)
if not lizenz then print('[AETHER-DEBUG] character-server: keine Lizenz!'); return end
if not lizenz then return end
TriggerClientEvent('aether:character:zeigeAuswahl', quelle,
BaueListe(lizenz), Config.MaxCharaktere)
print('[AETHER-DEBUG] character-server: zeigeAuswahl an Client gesendet')
end)
-- ---------------------------------------------------------------------

View File

@ -52,6 +52,11 @@ end)
RegisterNetEvent('aether:client:spielerGeladen', function(daten)
Aether.SpielerDaten = daten
-- Waehrend des Spawns schwarz halten (versteckt Modellwechsel und den
-- Schnitt von der Auswahl-Kamera). Der Fade wird am Ende wieder geoeffnet
-- — der Core steuert den Fade allein, damit es keinen Konflikt gibt.
DoScreenFadeOut(0)
-- Passendes Standardmodell laden (bis der Charakter-Ersteller migriert ist)
local modellName = Config.StandardPed[daten.geschlecht] or Config.StandardPed.m
local modell = GetHashKey(modellName)

View File

@ -87,8 +87,6 @@ end
RegisterNetEvent('aether:server:spielerBereit', function()
local quelle = source
local lizenz = HoleLizenz(quelle)
print(('[AETHER-DEBUG] spielerBereit: quelle=%s lizenz=%s AutoCharakter=%s')
:format(tostring(quelle), lizenz and 'ok' or 'nil', tostring(Config.AutoCharakter)))
if not lizenz then return end
-- Bereits geladen? Dann nichts tun (schuetzt vor doppeltem Laden)
@ -96,7 +94,6 @@ RegisterNetEvent('aether:server:spielerBereit', function()
-- Standardweg: `aether-character` zeigt die Charakterauswahl
if not Config.AutoCharakter then
print('[AETHER-DEBUG] spielerBereit: triggere charakterAuswahlNoetig')
TriggerEvent('aether:server:charakterAuswahlNoetig', quelle)
return
end