aether-framework/resources/[aether]/aether-core/server/main.lua
Jerrit Fritzsche 0440de16e5 debug: Ausgaben in der Charakterauswahl-Kette zur Fehlersuche
Temporaere [AETHER-DEBUG]-Prints in spielerBereit (core),
charakterAuswahlNoetig (character-server) und OeffneAuswahl (client),
um zu lokalisieren, wo die Auswahl bei AutoCharakter=false stoppt.
Werden nach der Diagnose wieder entfernt.

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

309 lines
12 KiB
Lua

-- =====================================================================
-- Aether Core — Serverlogik
-- Verbindungsaufbau, Laden/Speichern der Spieler und Exports fuer Module.
-- =====================================================================
Aether = Aether or {}
-- ---------------------------------------------------------------------
-- Hilfsfunktionen
-- ---------------------------------------------------------------------
--- Liest die Rockstar-Lizenz eines verbundenen Spielers aus
--- @param quelle number Server-ID
--- @return string|nil Lizenz (ohne Praefix) oder nil
local function HoleLizenz(quelle)
local lizenz = GetPlayerIdentifierByType(quelle, 'license')
if not lizenz then return nil end
return lizenz:gsub('license:', '')
end
-- ---------------------------------------------------------------------
-- Verbindungsaufbau (Warteschlange / Bannpruefung)
-- ---------------------------------------------------------------------
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local quelle = source
deferrals.defer()
Wait(0)
deferrals.update('Aether — Verbindung wird geprueft ...')
local lizenz = HoleLizenz(quelle)
if not lizenz then
deferrals.done('Keine gueltige Rockstar-Lizenz gefunden. Bitte starte FiveM neu.')
return
end
-- Benutzer anlegen oder laden
local benutzer = Aether.DB.HoleOderErstelleBenutzer(lizenz, name)
if not benutzer then
deferrals.done('Datenbankfehler beim Laden deines Profils. Bitte melde dich beim Team.')
return
end
-- Bannpruefung
if benutzer.is_banned == 1 then
local text = benutzer.ban_reason or 'Kein Grund angegeben'
if benutzer.ban_until then
deferrals.done(('Du bist bis %s gesperrt.\nGrund: %s'):format(benutzer.ban_until, text))
else
deferrals.done(('Du bist dauerhaft gesperrt.\nGrund: %s'):format(text))
end
return
end
deferrals.update('Aether — Willkommen zurueck!')
Wait(200)
deferrals.done()
end)
-- ---------------------------------------------------------------------
-- Charakter in das Spiel laden
-- ---------------------------------------------------------------------
--- Erzeugt das Spielerobjekt aus einem Charakter-Datensatz und spawnt den Spieler
--- @param quelle number Server-ID
--- @param lizenz string Rockstar-Lizenz
--- @param charakter table Datensatz aus aether_characters
--- @return table Spielerobjekt
local function LadeUndSpawne(quelle, lizenz, charakter)
local spieler = Aether.ErzeugeSpieler(quelle, lizenz, charakter)
Aether.Info(('Spieler geladen: %s %s (ID %d, Charakter #%d)')
:format(spieler.vorname, spieler.nachname, quelle, spieler.charakterId))
-- Daten an den Client schicken, damit er spawnen kann
TriggerClientEvent('aether:client:spielerGeladen', quelle, spieler:HoleDaten())
-- Andere Ressourcen informieren (z. B. HUD, Inventar)
TriggerEvent('aether:server:spielerGeladen', quelle, spieler)
return spieler
end
-- ---------------------------------------------------------------------
-- Spieler ist bereit (wird vom Client ausgeloest)
-- ---------------------------------------------------------------------
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)
if Aether.Spieler[quelle] then return end
-- Standardweg: `aether-character` zeigt die Charakterauswahl
if not Config.AutoCharakter then
print('[AETHER-DEBUG] spielerBereit: triggere charakterAuswahlNoetig')
TriggerEvent('aether:server:charakterAuswahlNoetig', quelle)
return
end
-- Notbetrieb ohne aether-character: Startcharakter anlegen und direkt laden
local charaktere = Aether.DB.HoleCharaktere(lizenz)
if #charaktere == 0 then
local spielerName = GetPlayerName(quelle)
local neueId = Aether.DB.ErstelleCharakter(lizenz, {
firstname = spielerName:sub(1, 32),
lastname = 'Neuling',
})
Aether.Info(('Startcharakter #%d fuer %s angelegt.'):format(neueId, spielerName))
charaktere = Aether.DB.HoleCharaktere(lizenz)
end
LadeUndSpawne(quelle, lizenz, charaktere[1])
end)
-- ---------------------------------------------------------------------
-- Position und Zustand vom Client uebernehmen
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:server:aktualisierePosition', function(position)
local spieler = Aether.Spieler[source]
if not spieler or type(position) ~= 'table' then return end
spieler.position = position
end)
RegisterNetEvent('aether:server:aktualisiereZustand', function(zustand)
local spieler = Aether.Spieler[source]
if not spieler or type(zustand) ~= 'table' then return end
-- Nur bekannte Felder uebernehmen (Client darf nichts Beliebiges setzen)
if zustand.leben then spieler.leben = zustand.leben end
if zustand.hunger then spieler.hunger = Aether.Begrenze(zustand.hunger, 0, 100) end
if zustand.durst then spieler.durst = Aether.Begrenze(zustand.durst, 0, 100) end
if zustand.ausdauer then spieler.ausdauer = Aether.Begrenze(zustand.ausdauer, 0, 100) end
end)
-- ---------------------------------------------------------------------
-- Verbindungsabbruch: speichern und aufraeumen
-- ---------------------------------------------------------------------
AddEventHandler('playerDropped', function(grund)
local quelle = source
local spieler = Aether.Spieler[quelle]
if not spieler then return end
spieler:Speichere()
Aether.Info(('Spieler gespeichert und entfernt: %s %s (Grund: %s)')
:format(spieler.vorname, spieler.nachname, grund))
TriggerEvent('aether:server:spielerEntfernt', quelle, spieler)
Aether.Spieler[quelle] = nil
end)
-- ---------------------------------------------------------------------
-- Automatisches Speichern aller Spieler
-- ---------------------------------------------------------------------
CreateThread(function()
while true do
Wait(Config.SpeicherIntervall)
local anzahl = 0
for _, spieler in pairs(Aether.Spieler) do
spieler:Speichere()
anzahl = anzahl + 1
end
if anzahl > 0 then
Aether.Debug(('Automatisch gespeichert: %d Spieler.'):format(anzahl))
end
end
end)
-- ---------------------------------------------------------------------
-- Hunger und Durst langsam senken
-- ---------------------------------------------------------------------
CreateThread(function()
while true do
Wait(Config.Zustand.tickIntervall)
for _, spieler in pairs(Aether.Spieler) do
spieler:SetzeZustand('hunger', spieler.hunger - Config.Zustand.hungerProTick)
spieler:SetzeZustand('durst', spieler.durst - Config.Zustand.durstProTick)
-- Bei leerem Magen oder Durst Schaden zufuegen
if spieler.hunger <= Config.Zustand.schadenAbWert
or spieler.durst <= Config.Zustand.schadenAbWert then
TriggerClientEvent('aether:client:erleideSchaden', spieler.quelle,
Config.Zustand.schadenProTick)
end
end
end
end)
-- ---------------------------------------------------------------------
-- Beim Stoppen der Ressource alles sichern (z. B. bei /restart)
-- ---------------------------------------------------------------------
AddEventHandler('onResourceStop', function(ressource)
if ressource ~= GetCurrentResourceName() then return end
for _, spieler in pairs(Aether.Spieler) do
spieler:Speichere()
end
Aether.Info('Alle Spieler vor dem Stoppen gespeichert.')
end)
-- =====================================================================
-- EXPORTS — Schnittstelle fuer alle anderen Aether-Module
-- Beispiel: local spieler = exports['aether-core']:HoleSpieler(source)
-- =====================================================================
--- Liefert das Spielerobjekt zu einer Server-ID
exports('HoleSpieler', function(quelle)
return Aether.Spieler[quelle]
end)
--- Liefert alle aktiven Spielerobjekte
exports('HoleAlleSpieler', function()
return Aether.Spieler
end)
--- Laedt einen gewaehlten Charakter und spawnt den Spieler.
--- Wird von `aether-character` aufgerufen, nachdem der Spieler gewaehlt hat.
--- @param quelle number Server-ID
--- @param charakterId number Charakter-ID
--- @return boolean Erfolg
exports('LadeCharakter', function(quelle, charakterId)
local lizenz = HoleLizenz(quelle)
if not lizenz then return false end
-- Doppeltes Laden verhindern
if Aether.Spieler[quelle] then return false end
local charakter = Aether.DB.HoleCharakter(charakterId)
if not charakter then
Aether.Fehler(('Charakter #%s nicht gefunden.'):format(tostring(charakterId)))
return false
end
-- Sicherheitspruefung: Der Charakter muss dem Spieler gehoeren
if charakter.license ~= lizenz then
Aether.Fehler(('Spieler %d wollte fremden Charakter #%d laden.'):format(quelle, charakterId))
return false
end
LadeUndSpawne(quelle, lizenz, charakter)
return true
end)
--- Liefert das Spielerobjekt zu einer Charakter-ID
exports('HoleSpielerNachCharakter', function(charakterId)
for _, spieler in pairs(Aether.Spieler) do
if spieler.charakterId == charakterId then return spieler end
end
return nil
end)
--- Liefert die Server-ID zu einer Charakter-ID, falls der Spieler online ist.
--- Bewusst schlank: Exports serialisieren, ganze Spielerobjekte waeren teuer.
--- @param charakterId number Charakter-ID
--- @return number|nil Server-ID oder nil
exports('HoleQuelleNachCharakter', function(charakterId)
for quelle, spieler in pairs(Aether.Spieler) do
if spieler.charakterId == charakterId then return quelle end
end
return nil
end)
--- Liefert die Charakter-ID eines Spielers (oder nil)
--- @param quelle number Server-ID
--- @return number|nil
exports('HoleCharakterId', function(quelle)
local spieler = Aether.Spieler[quelle]
return spieler and spieler.charakterId or nil
end)
--- Liefert die Daten eines Spielers als einfache Tabelle.
--- Wichtig: Exports serialisieren — Methoden ueberleben die Grenze nicht,
--- daher hier bewusst nur reine Werte.
--- @param quelle number Server-ID
--- @return table|nil
exports('HoleSpielerInfo', function(quelle)
local spieler = Aether.Spieler[quelle]
if not spieler then return nil end
return spieler:HoleDaten()
end)
--- Aendert einen Zustandswert um einen Betrag (positiv oder negativ)
--- @param quelle number Server-ID
--- @param art string 'hunger' | 'durst' | 'ausdauer'
--- @param aenderung number Betrag
--- @return boolean Erfolg
exports('AendereZustand', function(quelle, art, aenderung)
local spieler = Aether.Spieler[quelle]
if not spieler then return false end
if art ~= 'hunger' and art ~= 'durst' and art ~= 'ausdauer' then return false end
spieler:SetzeZustand(art, (spieler[art] or 0) + aenderung)
return true
end)
--- Fuegt einem Spieler Geld hinzu
exports('FuegeGeldHinzu', function(quelle, art, betrag, grund)
local spieler = Aether.Spieler[quelle]
if not spieler then return false end
return spieler:FuegeGeldHinzu(art, betrag, grund)
end)
--- Zieht einem Spieler Geld ab (mit Deckungspruefung)
exports('EntferneGeld', function(quelle, art, betrag, grund)
local spieler = Aether.Spieler[quelle]
if not spieler then return false end
return spieler:EntferneGeld(art, betrag, grund)
end)