From 11cc043b6cdfe1b0dd18f9ae5f9cfa34a4f93825 Mon Sep 17 00:00:00 2001 From: Jerrit Fritzsche Date: Mon, 27 Jul 2026 20:47:10 +0200 Subject: [PATCH] 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 --- .../[aether]/aether-character/client/main.lua | 8 +- .../aether-character/client/vorschau.lua | 74 ++++++++++++------- .../[aether]/aether-character/server/main.lua | 5 +- .../[aether]/aether-core/client/main.lua | 5 ++ .../[aether]/aether-core/server/main.lua | 3 - 5 files changed, 56 insertions(+), 39 deletions(-) diff --git a/resources/[aether]/aether-character/client/main.lua b/resources/[aether]/aether-character/client/main.lua index 225dc43..41c2a6d 100644 --- a/resources/[aether]/aether-character/client/main.lua +++ b/resources/[aether]/aether-character/client/main.lua @@ -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 -- --------------------------------------------------------------------- diff --git a/resources/[aether]/aether-character/client/vorschau.lua b/resources/[aether]/aether-character/client/vorschau.lua index 601a0a6..0457c83 100644 --- a/resources/[aether]/aether-character/client/vorschau.lua +++ b/resources/[aether]/aether-character/client/vorschau.lua @@ -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,37 +82,53 @@ end --- @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) + aktuellerLauf = aktuellerLauf + 1 + local lauf = aktuellerLauf - 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 + -- 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 - -- 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 + -- Umgebung laden, damit der Ped nicht durch den Boden faellt + RequestCollisionAtCoord(VORSCHAU_POS.x, VORSCHAU_POS.y, VORSCHAU_POS.z) - SetEntityHeading(vorschauPed, 0.0) -- schaut Richtung Kamera - FreezeEntityPosition(vorschauPed, true) - SetEntityInvincible(vorschauPed, true) - SetBlockingOfNonTemporaryEvents(vorschauPed, true) - SetModelAsNoLongerNeeded(modell) + 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 - if aussehen then - AetherAussehen.Anwenden(vorschauPed, aussehen) - 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 - -- Kamera erzeugen und aktivieren - vorschauKamera = CreateCam('DEFAULT_SCRIPTED_CAMERA', true) - SetzeKameraAuf(modus or 'koerper', false) - SetCamActive(vorschauKamera, true) - RenderScriptCams(true, false, 0, true, true) + 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. @@ -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) diff --git a/resources/[aether]/aether-character/server/main.lua b/resources/[aether]/aether-character/server/main.lua index f9dfcd2..8f5aa17 100644 --- a/resources/[aether]/aether-character/server/main.lua +++ b/resources/[aether]/aether-character/server/main.lua @@ -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) -- --------------------------------------------------------------------- diff --git a/resources/[aether]/aether-core/client/main.lua b/resources/[aether]/aether-core/client/main.lua index bb24632..e256ab9 100644 --- a/resources/[aether]/aether-core/client/main.lua +++ b/resources/[aether]/aether-core/client/main.lua @@ -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) diff --git a/resources/[aether]/aether-core/server/main.lua b/resources/[aether]/aether-core/server/main.lua index 7701f71..e8ece2c 100644 --- a/resources/[aether]/aether-core/server/main.lua +++ b/resources/[aether]/aether-core/server/main.lua @@ -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