-- ===================================================================== -- 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) 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 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)