- 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>
157 lines
5.5 KiB
Lua
157 lines
5.5 KiB
Lua
-- =====================================================================
|
|
-- Aether Character — Clientlogik
|
|
-- Zeigt die Charakterauswahl, verwaltet die Kamera und meldet die
|
|
-- Auswahl an den Server. Der Core uebernimmt danach das Spawnen.
|
|
-- =====================================================================
|
|
|
|
-- Ist die Auswahl gerade offen?
|
|
local auswahlOffen = false
|
|
|
|
-- Ort, an dem der Spieler waehrend der Auswahl geparkt wird
|
|
local PARKPLATZ = { x = -1037.7, y = -2737.6, z = 20.2 }
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Auswahl oeffnen / schliessen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Oeffnet die Charakterauswahl mit der Liste vom Server
|
|
--- @param charaktere table Liste der Charaktere
|
|
--- @param maxSlots number Maximale Anzahl Charaktere
|
|
local function OeffneAuswahl(charaktere, maxSlots)
|
|
auswahlOffen = true
|
|
|
|
-- Eigenen Spieler unsichtbar parken — sichtbar ist nur der Vorschau-Ped
|
|
local ped = PlayerPedId()
|
|
SetEntityVisible(ped, false, false)
|
|
FreezeEntityPosition(ped, true)
|
|
SetEntityCoordsNoOffset(ped, PARKPLATZ.x, PARKPLATZ.y, PARKPLATZ.z, false, false, false)
|
|
|
|
-- 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')
|
|
|
|
-- Ladebildschirm beenden, damit die Auswahl sichtbar wird
|
|
ShutdownLoadingScreen()
|
|
ShutdownLoadingScreenNui()
|
|
DoScreenFadeIn(800)
|
|
|
|
SetNuiFocus(true, true)
|
|
SendNuiMessage(json.encode({
|
|
aktion = 'oeffnen',
|
|
charaktere = charaktere,
|
|
maxSlots = maxSlots,
|
|
}))
|
|
end
|
|
|
|
--- Schliesst die Auswahl und gibt die Steuerung frei
|
|
local function SchliesseAuswahl()
|
|
if not auswahlOffen then return end
|
|
auswahlOffen = false
|
|
|
|
SendNuiMessage(json.encode({ aktion = 'schliessen' }))
|
|
SetNuiFocus(false, false)
|
|
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
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Ereignisse vom Server
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Server fordert die Charakterauswahl an
|
|
RegisterNetEvent('aether:character:zeigeAuswahl', function(charaktere, maxSlots)
|
|
OeffneAuswahl(charaktere, maxSlots)
|
|
end)
|
|
|
|
--- Aktualisierte Liste nach Erstellen oder Loeschen
|
|
RegisterNetEvent('aether:character:liste', function(charaktere, maxSlots, hinweis)
|
|
SendNuiMessage(json.encode({
|
|
aktion = 'liste',
|
|
charaktere = charaktere,
|
|
maxSlots = maxSlots,
|
|
hinweis = hinweis,
|
|
}))
|
|
end)
|
|
|
|
--- Server lehnt eine Aktion ab (z. B. Slot voll, ungueltiger Name)
|
|
RegisterNetEvent('aether:character:fehler', function(text)
|
|
SendNuiMessage(json.encode({ aktion = 'fehler', text = text }))
|
|
end)
|
|
|
|
--- Charakter wurde gewaehlt: Auswahl sofort schliessen, damit der Core
|
|
--- ungestoert spawnen kann (Netz-Event kommt gleichzeitig beim Core an)
|
|
RegisterNetEvent('aether:client:spielerGeladen', function()
|
|
if auswahlOffen then
|
|
SchliesseAuswahl()
|
|
end
|
|
end)
|
|
|
|
--- Der Core hat den Ped gesetzt und gespawnt: jetzt das Aussehen anwenden.
|
|
--- Eigenes Event, damit wir nicht mit dem Modellwechsel des Cores kollidieren.
|
|
AddEventHandler('aether:client:bereit', function(daten)
|
|
if daten and daten.aussehen then
|
|
AetherAussehen.Anwenden(PlayerPedId(), daten.aussehen)
|
|
end
|
|
|
|
local ped = PlayerPedId()
|
|
FreezeEntityPosition(ped, false)
|
|
SetEntityVisible(ped, true, false)
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- NUI-Callbacks (Aktionen aus der Oberflaeche)
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Aussehen hat sich geaendert: sofort auf den Vorschau-Ped uebertragen
|
|
RegisterNUICallback('vorschau', function(daten, rueckgabe)
|
|
if daten.aussehen then
|
|
Vorschau.Anwenden(daten.aussehen)
|
|
end
|
|
if daten.modus then
|
|
Vorschau.SetzeModus(daten.modus)
|
|
end
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
--- Ansicht wechseln (Gesicht / Koerper)
|
|
RegisterNUICallback('vorschauModus', function(daten, rueckgabe)
|
|
Vorschau.SetzeModus(daten.modus or 'koerper')
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
--- Vorschau-Ped drehen
|
|
RegisterNUICallback('vorschauDrehen', function(daten, rueckgabe)
|
|
Vorschau.Drehen(tonumber(daten.grad) or 0.0)
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
--- Spieler waehlt einen Charakter zum Spielen
|
|
RegisterNUICallback('spielen', function(daten, rueckgabe)
|
|
TriggerServerEvent('aether:character:spielen', daten.charakterId)
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
--- Spieler legt einen neuen Charakter an
|
|
RegisterNUICallback('erstellen', function(daten, rueckgabe)
|
|
TriggerServerEvent('aether:character:erstellen', daten)
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
--- Spieler loescht einen Charakter
|
|
RegisterNUICallback('loeschen', function(daten, rueckgabe)
|
|
TriggerServerEvent('aether:character:loeschen', daten.charakterId)
|
|
rueckgabe({ ok = true })
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Aufraeumen beim Stoppen der Ressource
|
|
-- ---------------------------------------------------------------------
|
|
AddEventHandler('onResourceStop', function(ressource)
|
|
if ressource ~= GetCurrentResourceName() then return end
|
|
SetNuiFocus(false, false)
|
|
Vorschau.Beenden()
|
|
FreezeEntityPosition(PlayerPedId(), false)
|
|
SetEntityVisible(PlayerPedId(), true, false)
|
|
end)
|