- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark, Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI) - aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource, an aether-core gekoppelt (Rechte über aether_users.admin_group), mit WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU) - Prototypen admin/ vervollständigt - SQL-Schema aether_admin.sql - Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
512 lines
20 KiB
Lua
512 lines
20 KiB
Lua
-- =====================================================================
|
|
-- Aether Garage — Serverlogik
|
|
-- Verwaltet Fahrzeugbesitz, Ein- und Ausparken. Der Client darf ein
|
|
-- Fahrzeug erst erzeugen, nachdem der Server es freigegeben hat.
|
|
-- =====================================================================
|
|
|
|
local Core = exports['aether-core']
|
|
|
|
-- Fahrzeuge, die gerade ausgeparkt werden duerfen: [quelle] = kennzeichen
|
|
local freigaben = {}
|
|
|
|
-- Aktuell in der Welt stehende Fahrzeuge: [kennzeichen] = Entity-Handle
|
|
-- Sie werden vom Server erzeugt und ueberleben dadurch auch, wenn kein
|
|
-- Spieler in der Naehe ist.
|
|
local aktiveFahrzeuge = {}
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Fahrzeuge in der Welt erzeugen und entfernen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Erzeugt ein Fahrzeug serverseitig an einer Position
|
|
--- @param datensatz table Zeile aus aether_vehicles
|
|
--- @param x number
|
|
--- @param y number
|
|
--- @param z number
|
|
--- @param heading number
|
|
--- @return number|nil Entity-Handle
|
|
local function ErzeugeFahrzeug(datensatz, x, y, z, heading)
|
|
-- Bereits vorhandenes Fahrzeug mit diesem Kennzeichen zuerst entfernen
|
|
local vorhandenes = aktiveFahrzeuge[datensatz.plate]
|
|
if vorhandenes and DoesEntityExist(vorhandenes) then
|
|
DeleteEntity(vorhandenes)
|
|
end
|
|
|
|
local fahrzeug = CreateVehicle(GetHashKey(datensatz.model), x, y, z, heading or 0.0, true, true)
|
|
if not fahrzeug or fahrzeug == 0 then return nil end
|
|
|
|
-- Warten, bis die Entitaet wirklich existiert
|
|
local versuche = 0
|
|
while not DoesEntityExist(fahrzeug) and versuche < 50 do
|
|
Wait(10)
|
|
versuche = versuche + 1
|
|
end
|
|
if not DoesEntityExist(fahrzeug) then return nil end
|
|
|
|
local props = {}
|
|
if datensatz.props then
|
|
local erfolg, entschluesselt = pcall(json.decode, datensatz.props)
|
|
if erfolg and type(entschluesselt) == 'table' then props = entschluesselt end
|
|
end
|
|
|
|
-- Alles, was nur clientseitig gesetzt werden kann, ueber den State Bag
|
|
-- weitergeben. Der Client wendet es an, sobald das Fahrzeug bei ihm
|
|
-- auftaucht (siehe client/main.lua).
|
|
Entity(fahrzeug).state:set('aether', {
|
|
kennzeichen = datensatz.plate,
|
|
farbe = props.farbe,
|
|
tank = datensatz.fuel,
|
|
motor = datensatz.engine,
|
|
karosserie = datensatz.body,
|
|
}, true)
|
|
|
|
aktiveFahrzeuge[datensatz.plate] = fahrzeug
|
|
return fahrzeug
|
|
end
|
|
|
|
--- Entfernt ein Fahrzeug aus der Welt
|
|
--- @param kennzeichen string Kennzeichen
|
|
local function EntferneFahrzeug(kennzeichen)
|
|
local fahrzeug = aktiveFahrzeuge[kennzeichen]
|
|
if fahrzeug and DoesEntityExist(fahrzeug) then
|
|
DeleteEntity(fahrzeug)
|
|
end
|
|
aktiveFahrzeuge[kennzeichen] = nil
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Hilfsfunktionen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liefert die Charakter-ID eines Spielers
|
|
local function CharakterVon(quelle)
|
|
return Core:HoleCharakterId(quelle)
|
|
end
|
|
|
|
--- Prueft, ob ein Spieler Zugang zu einer Garage hat.
|
|
--- Haus-Garagen werden spaeter ueber `aether-immobilien` geprueft.
|
|
--- @param quelle number Server-ID
|
|
--- @param garageId string Kennung der Garage
|
|
--- @return boolean
|
|
local function HatZugang(quelle, garageId)
|
|
local garage = Garage.Garagen[garageId]
|
|
if not garage then return false end
|
|
if garage.typ == 'oeffentlich' then return true end
|
|
|
|
-- Optionale Abfrage beim Immobilien-Modul
|
|
local erfolg, ergebnis = pcall(function()
|
|
return exports['aether-immobilien']:HatGaragenZugang(quelle, garageId)
|
|
end)
|
|
if erfolg and type(ergebnis) == 'boolean' then return ergebnis end
|
|
|
|
-- Modul noch nicht vorhanden: Haus-Garagen bleiben verschlossen
|
|
return false
|
|
end
|
|
|
|
--- Laedt alle Fahrzeuge eines Charakters
|
|
--- @param charakterId number Charakter-ID
|
|
--- @return table Liste von Fahrzeugen
|
|
local function HoleFahrzeuge(charakterId)
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT plate, model, props, garage, state, fuel, engine, body, mileage
|
|
FROM aether_vehicles WHERE owner = ?
|
|
]], { charakterId }) or {}
|
|
|
|
local liste = {}
|
|
for _, zeile in ipairs(zeilen) do
|
|
local props = {}
|
|
if zeile.props then
|
|
local erfolg, entschluesselt = pcall(json.decode, zeile.props)
|
|
if erfolg and type(entschluesselt) == 'table' then props = entschluesselt end
|
|
end
|
|
|
|
liste[#liste + 1] = {
|
|
kennzeichen = zeile.plate,
|
|
modell = zeile.model,
|
|
name = props.name or zeile.model,
|
|
klasse = props.klasse or 'Fahrzeug',
|
|
typ = props.typ or 'sedan',
|
|
farbe = props.farbe or '#20232a',
|
|
plateStyle = props.plateStyle or 'sa',
|
|
garage = zeile.garage,
|
|
status = zeile.state, -- in | out | impound
|
|
tank = math.floor(zeile.fuel or 0),
|
|
motor = math.floor((zeile.engine or 1000) / 10),
|
|
karosserie = math.floor((zeile.body or 1000) / 10),
|
|
km = zeile.mileage or 0,
|
|
}
|
|
end
|
|
return liste
|
|
end
|
|
|
|
--- Baut den Zustand fuer die Oberflaeche
|
|
--- @param quelle number Server-ID
|
|
--- @param standort string Garage, an der der Spieler steht
|
|
--- @return table|nil
|
|
local function BaueZustand(quelle, standort)
|
|
local charakterId = CharakterVon(quelle)
|
|
if not charakterId then return nil end
|
|
|
|
-- Nur zugaengliche Garagen anzeigen
|
|
local garagen = {}
|
|
for garageId, garage in pairs(Garage.Garagen) do
|
|
if HatZugang(quelle, garageId) then
|
|
garagen[#garagen + 1] = {
|
|
id = garageId, name = garage.name, typ = garage.typ,
|
|
}
|
|
end
|
|
end
|
|
|
|
return {
|
|
garagen = garagen,
|
|
fahrzeuge = HoleFahrzeuge(charakterId),
|
|
standort = standort,
|
|
gebuehr = Garage.AUSLOESE_GEBUEHR,
|
|
}
|
|
end
|
|
|
|
--- Schickt den Zustand an einen Spieler
|
|
local function SendeZustand(quelle, standort, hinweis)
|
|
local zustand = BaueZustand(quelle, standort)
|
|
if not zustand then return end
|
|
zustand.hinweis = hinweis
|
|
TriggerClientEvent('aether:garage:zustand', quelle, zustand)
|
|
end
|
|
|
|
--- Lehnt eine Aktion ab
|
|
local function Ablehnen(quelle, grund)
|
|
TriggerClientEvent('aether:garage:fehler', quelle, grund)
|
|
end
|
|
|
|
--- Liest ein Fahrzeug und prueft den Besitz
|
|
--- @param charakterId number Charakter-ID
|
|
--- @param kennzeichen string Kennzeichen
|
|
--- @return table|nil
|
|
local function HoleEigenes(charakterId, kennzeichen)
|
|
local treffer = MySQL.query.await(
|
|
'SELECT * FROM aether_vehicles WHERE plate = ? AND owner = ?',
|
|
{ kennzeichen, charakterId })
|
|
return treffer and treffer[1] or nil
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Ausgeparkte Fahrzeuge beim Serverstart wiederherstellen
|
|
-- ---------------------------------------------------------------------
|
|
CreateThread(function()
|
|
Wait(2000) -- warten, bis Datenbank und Spielwelt bereit sind
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT * FROM aether_vehicles
|
|
WHERE state = 'out' AND pos_x IS NOT NULL
|
|
]]) or {}
|
|
|
|
local wiederhergestellt = 0
|
|
for _, zeile in ipairs(zeilen) do
|
|
local entitaet = ErzeugeFahrzeug(zeile, zeile.pos_x, zeile.pos_y, zeile.pos_z, zeile.heading)
|
|
if entitaet then
|
|
wiederhergestellt = wiederhergestellt + 1
|
|
else
|
|
-- Konnte es nicht erzeugt werden (z. B. Modell entfernt),
|
|
-- wandert es zurueck in seine Garage statt verloren zu gehen.
|
|
MySQL.update("UPDATE aether_vehicles SET state = 'in' WHERE plate = ?", { zeile.plate })
|
|
Aether.Fehler(('Fahrzeug %s konnte nicht gesetzt werden — zurueck in die Garage.')
|
|
:format(zeile.plate))
|
|
end
|
|
Wait(50) -- Erzeugung entzerren
|
|
end
|
|
|
|
if wiederhergestellt > 0 then
|
|
Aether.Info(('%d ausgeparkte Fahrzeuge wiederhergestellt.'):format(wiederhergestellt))
|
|
end
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Position der ausgeparkten Fahrzeuge regelmaessig sichern
|
|
-- ---------------------------------------------------------------------
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(30000) -- alle 30 Sekunden
|
|
|
|
for kennzeichen, fahrzeug in pairs(aktiveFahrzeuge) do
|
|
if DoesEntityExist(fahrzeug) then
|
|
local position = GetEntityCoords(fahrzeug)
|
|
MySQL.update([[
|
|
UPDATE aether_vehicles SET pos_x = ?, pos_y = ?, pos_z = ?, heading = ?
|
|
WHERE plate = ?
|
|
]], { position.x, position.y, position.z, GetEntityHeading(fahrzeug), kennzeichen })
|
|
else
|
|
-- Entitaet ist verschwunden (z. B. zerstoert): Eintrag aufraeumen
|
|
aktiveFahrzeuge[kennzeichen] = nil
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
--- Beim Stoppen der Ressource die letzten Positionen sichern
|
|
AddEventHandler('onResourceStop', function(ressource)
|
|
if ressource ~= GetCurrentResourceName() then return end
|
|
|
|
for kennzeichen, fahrzeug in pairs(aktiveFahrzeuge) do
|
|
if DoesEntityExist(fahrzeug) then
|
|
local position = GetEntityCoords(fahrzeug)
|
|
MySQL.update.await([[
|
|
UPDATE aether_vehicles SET pos_x = ?, pos_y = ?, pos_z = ?, heading = ?
|
|
WHERE plate = ?
|
|
]], { position.x, position.y, position.z, GetEntityHeading(fahrzeug), kennzeichen })
|
|
end
|
|
end
|
|
Aether.Info('Fahrzeugpositionen gesichert.')
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Zustand eines fahrenden Fahrzeugs uebernehmen (vom Client gemeldet)
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:garage:zustandMelden', function(kennzeichen, zustand)
|
|
if type(kennzeichen) ~= 'string' or type(zustand) ~= 'table' then return end
|
|
if not aktiveFahrzeuge[kennzeichen] then return end -- nur bekannte Fahrzeuge
|
|
|
|
local tank = math.max(0, math.min(100, tonumber(zustand.tank) or 100))
|
|
local motor = math.max(0, math.min(1000, tonumber(zustand.motor) or 1000))
|
|
local body = math.max(0, math.min(1000, tonumber(zustand.karosserie) or 1000))
|
|
|
|
MySQL.update('UPDATE aether_vehicles SET fuel = ?, engine = ?, body = ? WHERE plate = ?',
|
|
{ tank, motor, body, kennzeichen })
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Startfahrzeug fuer neue Charaktere
|
|
-- ---------------------------------------------------------------------
|
|
AddEventHandler('aether:server:spielerGeladen', function(quelle, spieler)
|
|
if not Garage.STARTFAHRZEUG then return end
|
|
|
|
local vorhanden = MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM aether_vehicles WHERE owner = ?', { spieler.charakterId })
|
|
if (vorhanden or 0) > 0 then return end
|
|
|
|
local kennzeichen = Garage.ErzeugeKennzeichen()
|
|
MySQL.insert.await([[
|
|
INSERT INTO aether_vehicles (plate, owner, model, props, garage, state)
|
|
VALUES (?, ?, ?, ?, ?, 'in')
|
|
]], {
|
|
kennzeichen, spieler.charakterId, Garage.STARTFAHRZEUG.modell,
|
|
json.encode({ name = 'Blista', klasse = 'Kleinwagen', typ = 'compact',
|
|
farbe = '#3563a8', plateStyle = 'sa' }),
|
|
Garage.STARTFAHRZEUG.garage,
|
|
})
|
|
Aether.Info(('Startfahrzeug %s fuer Charakter #%d angelegt.')
|
|
:format(kennzeichen, spieler.charakterId))
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Garage oeffnen
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:garage:oeffnen', function(garageId)
|
|
local quelle = source
|
|
if not CharakterVon(quelle) then return end
|
|
if type(garageId) ~= 'string' or not Garage.Garagen[garageId] then return end
|
|
|
|
if not HatZugang(quelle, garageId) then
|
|
Ablehnen(quelle, 'Zu dieser Garage hast du keinen Zugang.')
|
|
return
|
|
end
|
|
|
|
-- Entfernungspruefung
|
|
local position = GetEntityCoords(GetPlayerPed(quelle))
|
|
if #(position - Garage.Garagen[garageId].position) > (Garage.INTERAKTION_WEITE + 3.0) then
|
|
Ablehnen(quelle, 'Du bist nicht an dieser Garage.')
|
|
return
|
|
end
|
|
|
|
local zustand = BaueZustand(quelle, garageId)
|
|
if zustand then
|
|
TriggerClientEvent('aether:garage:zeige', quelle, zustand)
|
|
end
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Ausparken
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:garage:ausparken', function(kennzeichen, standort)
|
|
local quelle = source
|
|
local charakterId = CharakterVon(quelle)
|
|
if not charakterId then return end
|
|
if type(kennzeichen) ~= 'string' or type(standort) ~= 'string' then return end
|
|
|
|
local garage = Garage.Garagen[standort]
|
|
if not garage or not HatZugang(quelle, standort) then return end
|
|
|
|
local fahrzeug = HoleEigenes(charakterId, kennzeichen)
|
|
if not fahrzeug then Ablehnen(quelle, 'Das ist nicht dein Fahrzeug.') return end
|
|
|
|
if fahrzeug.state == 'impound' then
|
|
Ablehnen(quelle, 'Das Fahrzeug ist beschlagnahmt.')
|
|
return
|
|
end
|
|
if fahrzeug.state == 'out' then
|
|
Ablehnen(quelle, 'Das Fahrzeug steht bereits draußen.')
|
|
return
|
|
end
|
|
|
|
-- Standort-Regel: nur an der Garage, wo das Fahrzeug wirklich steht
|
|
if fahrzeug.garage ~= standort then
|
|
local wo = Garage.Garagen[fahrzeug.garage]
|
|
Ablehnen(quelle, ('Das Fahrzeug steht in %s.'):format(wo and wo.name or fahrzeug.garage))
|
|
return
|
|
end
|
|
|
|
-- Fahrzeug serverseitig erzeugen, damit es unabhaengig vom Spieler
|
|
-- (und ueber Neustarts hinweg) in der Welt bleibt
|
|
local spawn = garage.spawn
|
|
local entitaet = ErzeugeFahrzeug(fahrzeug, spawn.x, spawn.y, spawn.z, spawn.w)
|
|
if not entitaet then
|
|
Ablehnen(quelle, 'Fahrzeug konnte nicht erzeugt werden.')
|
|
return
|
|
end
|
|
|
|
MySQL.update.await([[
|
|
UPDATE aether_vehicles
|
|
SET state = 'out', pos_x = ?, pos_y = ?, pos_z = ?, heading = ?
|
|
WHERE plate = ?
|
|
]], { spawn.x, spawn.y, spawn.z, spawn.w, kennzeichen })
|
|
|
|
freigaben[quelle] = kennzeichen
|
|
|
|
-- Dem Client sagen, in welches Fahrzeug er einsteigen soll
|
|
TriggerClientEvent('aether:garage:einsteigen', quelle,
|
|
NetworkGetNetworkIdFromEntity(entitaet), kennzeichen)
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Einparken
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:garage:einparken', function(kennzeichen, standort, zustand)
|
|
local quelle = source
|
|
local charakterId = CharakterVon(quelle)
|
|
if not charakterId then return end
|
|
if type(kennzeichen) ~= 'string' or type(standort) ~= 'string' then return end
|
|
|
|
local garage = Garage.Garagen[standort]
|
|
if not garage or not HatZugang(quelle, standort) then return end
|
|
|
|
local fahrzeug = HoleEigenes(charakterId, kennzeichen)
|
|
if not fahrzeug then Ablehnen(quelle, 'Das ist nicht dein Fahrzeug.') return end
|
|
|
|
-- Entfernung des Spielers zur Garage pruefen
|
|
local position = GetEntityCoords(GetPlayerPed(quelle))
|
|
if #(position - garage.position) > Garage.EINPARK_WEITE then
|
|
Ablehnen(quelle, 'Du bist zu weit von der Garage entfernt.')
|
|
return
|
|
end
|
|
|
|
-- Zustandswerte vom Client uebernehmen, aber begrenzen
|
|
zustand = type(zustand) == 'table' and zustand or {}
|
|
local tank = math.max(0, math.min(100, tonumber(zustand.tank) or fahrzeug.fuel or 100))
|
|
local motor = math.max(0, math.min(1000, tonumber(zustand.motor) or fahrzeug.engine or 1000))
|
|
local body = math.max(0, math.min(1000, tonumber(zustand.karosserie) or fahrzeug.body or 1000))
|
|
|
|
-- Kilometerstand: gefahrene Meter aufaddieren (nie verringern).
|
|
-- Nach oben begrenzt, damit manipulierte Werte nichts kaputtmachen.
|
|
local meter = math.max(0, math.min(1000000, math.floor(tonumber(zustand.meter) or 0)))
|
|
local km = (fahrzeug.mileage or 0) + math.floor(meter / 1000)
|
|
|
|
MySQL.update.await([[
|
|
UPDATE aether_vehicles
|
|
SET state = 'in', garage = ?, fuel = ?, engine = ?, body = ?, mileage = ?,
|
|
pos_x = NULL, pos_y = NULL, pos_z = NULL, heading = NULL
|
|
WHERE plate = ?
|
|
]], { standort, tank, motor, body, km, kennzeichen })
|
|
|
|
-- Fahrzeug aus der Welt nehmen (serverseitig, damit es bei allen verschwindet)
|
|
EntferneFahrzeug(kennzeichen)
|
|
|
|
freigaben[quelle] = nil
|
|
SendeZustand(quelle, standort, ('Fahrzeug in %s eingelagert.'):format(garage.name))
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Beschlagnahmtes Fahrzeug ausloesen
|
|
-- ---------------------------------------------------------------------
|
|
RegisterNetEvent('aether:garage:ausloesen', function(kennzeichen, standort)
|
|
local quelle = source
|
|
local charakterId = CharakterVon(quelle)
|
|
if not charakterId then return end
|
|
if type(kennzeichen) ~= 'string' or type(standort) ~= 'string' then return end
|
|
if not Garage.Garagen[standort] or not HatZugang(quelle, standort) then return end
|
|
|
|
local fahrzeug = HoleEigenes(charakterId, kennzeichen)
|
|
if not fahrzeug or fahrzeug.state ~= 'impound' then return end
|
|
|
|
if not Core:EntferneGeld(quelle, 'bargeld', Garage.AUSLOESE_GEBUEHR, 'Fahrzeug ausgelöst') then
|
|
Ablehnen(quelle, ('Du brauchst %d € in bar.'):format(Garage.AUSLOESE_GEBUEHR))
|
|
return
|
|
end
|
|
|
|
MySQL.update.await(
|
|
'UPDATE aether_vehicles SET state = ?, garage = ? WHERE plate = ?',
|
|
{ 'in', standort, kennzeichen })
|
|
|
|
SendeZustand(quelle, standort, ('Fahrzeug für %d € ausgelöst.'):format(Garage.AUSLOESE_GEBUEHR))
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Aufraeumen
|
|
-- ---------------------------------------------------------------------
|
|
AddEventHandler('playerDropped', function()
|
|
freigaben[source] = nil
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- EXPORTS
|
|
-- =====================================================================
|
|
|
|
--- Gibt einem Charakter ein Fahrzeug
|
|
--- @param charakterId number Charakter-ID
|
|
--- @param modell string Spawnname des Modells
|
|
--- @param garageId string Garage, in der es steht
|
|
--- @param props table|nil Anzeigedaten (Name, Klasse, Farbe ...)
|
|
--- @return string|nil Kennzeichen
|
|
exports('GebeFahrzeug', function(charakterId, modell, garageId, props)
|
|
if type(charakterId) ~= 'number' or type(modell) ~= 'string' then return nil end
|
|
|
|
local kennzeichen = Garage.ErzeugeKennzeichen()
|
|
MySQL.insert.await([[
|
|
INSERT INTO aether_vehicles (plate, owner, model, props, garage, state)
|
|
VALUES (?, ?, ?, ?, ?, 'in')
|
|
]], {
|
|
kennzeichen, charakterId, modell,
|
|
json.encode(props or { name = modell }),
|
|
garageId or 'legion',
|
|
})
|
|
return kennzeichen
|
|
end)
|
|
|
|
--- Beschlagnahmt ein Fahrzeug (fuer das Polizei-CAD)
|
|
--- @param kennzeichen string Kennzeichen
|
|
--- @return boolean Erfolg
|
|
exports('Beschlagnahmen', function(kennzeichen)
|
|
if type(kennzeichen) ~= 'string' then return false end
|
|
MySQL.update.await(
|
|
"UPDATE aether_vehicles SET state = 'impound' WHERE plate = ?", { kennzeichen })
|
|
return true
|
|
end)
|
|
|
|
--- Sucht den Besitzer eines Kennzeichens (fuer die Kennzeichenabfrage im CAD)
|
|
--- @param kennzeichen string Kennzeichen
|
|
--- @return table|nil { charakterId, vorname, nachname, modell }
|
|
exports('HoleHalter', function(kennzeichen)
|
|
local treffer = MySQL.query.await([[
|
|
SELECT v.owner, v.model, c.firstname, c.lastname
|
|
FROM aether_vehicles v
|
|
JOIN aether_characters c ON c.id = v.owner
|
|
WHERE v.plate = ?
|
|
]], { kennzeichen })
|
|
|
|
if not treffer or not treffer[1] then return nil end
|
|
return {
|
|
charakterId = treffer[1].owner,
|
|
vorname = treffer[1].firstname,
|
|
nachname = treffer[1].lastname,
|
|
modell = treffer[1].model,
|
|
}
|
|
end)
|