X-Garage einrichten
Garagensystem für ESX Legacy und QBCore mit Live-Vorschau des Fahrzeugs, Zweitschlüsseln, Fahrzeughandel, Stellplatz-Wirtschaft und einer Verwahrstelle mit Akte. Diese Anleitung führt dich vom Download bis zum eingerichteten Server.
Voraussetzungen
- FiveM Server mit aktuellem Artifact
es_extended(ESX Legacy) oderqb-coreoxmysql- Eine Fahrzeugtabelle, standardmäßig
owned_vehicles
Optional, wird beim Start automatisch erkannt:
- Tank:
LegacyFuel,ox_fuel,ps-fuel,cdn-fuel,lc_fueloder die native Tankanzeige - Schlüssel:
qb-vehiclekeys,wasabi_carlock,qs-vehiclekeys,mk_vehiclekeys - Interaktion:
ox_target,qb-targetoder die eingebauten Marker
Installation
- Lade
x-garageaus dem Cfx.re Portal herunter. - Entpacke den Ordner nach
resources/[x-solution]/x-garage. - Trage die Resource in deiner
server.cfgein, nach dem Framework und nach oxmysql:
ensure oxmysql
ensure es_extended
ensure x-garage
- Server starten. Läuft alles, bleibt die Konsole still. Willst du beim Einrichten sehen, welches Framework erkannt wurde, welche Spalten gefunden wurden und wie viele Garagen geladen sind, setze
Config.Debug = true.
Die Tabellen legt x-garage beim ersten Start selbst an. Wenn du lieber manuell importierst, liegt alles in sql/install.sql.
Die Fahrzeugtabelle
x-garage arbeitet mit deiner bestehenden Fahrzeugtabelle und braucht dort vier Angaben:
| Wert | Bedeutung |
|---|---|
| Besitzer | Wem das Fahrzeug gehört |
| Kennzeichen | Das Nummernschild |
| Garage | In welcher Garage das Fahrzeug steht, als Text |
| Zustand | Ob es eingeparkt ist, und was die Auslösegebühr beträgt |
Du musst dafür nichts von Hand eintragen. Beim Start liest x-garage dein Tabellenschema und findet die passenden Spalten selbst, egal ob sie bei dir garage_id oder garage heißen, vehicle oder mods, owner oder citizenid. Mit Config.Debug = true schreibt es in die Konsole, was es gefunden hat.
Fehlt eine Spalte komplett, legt x-garage sie an. Das betrifft in der Regel nur impound. Diese Spalte hält die Auslösegebühr in Dollar, nicht bloß ein Ja oder Nein, damit jedes Fahrzeug seine eigene Gebühr tragen kann. Hat deine Tabelle bereits ein impounded-Feld, werden bestehende Beschlagnahmungen einmalig mit Config.Impound.price übernommen, damit niemand über Nacht sein Auto zurückbekommt.
Willst du die Erkennung übergehen, trägst du die echten Namen fest in Config.Database.columns ein. Was dort steht und in der Tabelle existiert, gewinnt immer.
Bedienung
Spieler gehen zu einem Garagen-NPC und drücken E, oder klicken ihn per Target an. Zum Einparken fährt man auf den markierten Platz und drückt H.
In der Oberfläche steht links das ausgewählte Fahrzeug wirklich im Spiel: Ziehen mit der Maus dreht es, das Mausrad zoomt. Rechts liegen Suche, Filter und die Liste, unten links die Aktionen für das gewählte Fahrzeug.
| Befehl | Wer | Wirkung |
|---|---|---|
/garagecreator |
Admin mit ACE x-garage.creator |
Garagen-Editor öffnen |
/impound |
Polizei | Fahrzeug in der Nähe beschlagnahmen |
/freigeben |
Polizei | Fahrzeug ohne Gebühr freigeben |
/abschleppen |
Polizei | Abschleppauftrag für das Fahrzeug in der Nähe erstellen |
/abschleppauftraege |
Mechaniker | Offene Aufträge auf der Karte markieren |
/verfall |
Polizei oder Konsole | Nicht abgeholte Fahrzeuge verfallen lassen |
Die Namen aller Befehle stehen in Config.Commands und in den jeweiligen Config-Blöcken.
Wichtige Einstellungen
Anbindungen
Config.Providers = {
framework = 'auto',
fuel = 'auto',
keys = 'auto',
target = 'auto'
}
auto prüft beim Start, welche Resources laufen. Trage hier nur dann etwas fest ein, wenn mehrere gleichzeitig laufen oder die Erkennung danebenliegt.
Datenbank
Config.Database = {
vehicles = 'owned_vehicles',
garages = 'x_garages',
columns = {
owner = 'owner',
plate = 'plate',
props = 'vehicle',
stored = 'stored',
garage = 'garage_id',
impound = 'impound',
label = 'label'
},
sharedGarageId = 'A'
}
sharedGarageId ist der Wert, den Fahrzeuge tragen, die in jeder Garage abrufbar sein sollen. Neu gekaufte Fahrzeuge aus vielen Autohäusern landen mit genau diesem Wert in der Tabelle.
Live-Vorschau
Config.Preview = {
enabled = true,
distance = 6.2,
minDistance = 3.4,
maxDistance = 11.0,
height = 1.15,
pitch = -11.0,
startHeading = 138.0,
autoRotate = true,
autoSpeed = 5.5,
dragSpeed = 0.42,
zoomStep = 0.7,
fadeTime = 300,
hidePlayer = true
}
Das Vorschaufahrzeug wird nur für den Spieler selbst erzeugt, nicht über das Netzwerk. Andere sehen es nicht, es kollidiert mit nichts und verschwindet, sobald die Oberfläche zugeht. Gespawnt wird es auf dem ersten Ausparkplatz der Garage.
Setzt du enabled = false, bleibt alles andere unverändert, die Kamera bleibt einfach beim Spieler.
hidePlayer blendet den eigenen Charakter aus, solange die Vorschau läuft, damit er nicht ins Bild ragt.
Besitz und Handel
Config.Ownership = {
nickname = { enabled = true, maxLength = 24 },
favorites = { enabled = true },
keys = { enabled = true, max = 4, distance = 4.0 },
trade = {
enabled = true,
distance = 4.0,
taxPercent = 5,
minPrice = 0,
maxPrice = 5000000,
timeout = 45000,
account = 'bank',
payoutAccount = 'bank'
},
plate = {
enabled = true,
price = 2500,
account = 'bank',
minLength = 2,
maxLength = 8,
pattern = '^[A-Z0-9 ]+$',
blocked = { 'POLICE', 'ADMIN', 'EMS' }
}
}
Zweitschlüssel gibst du an eine Person, die neben dir steht. Sie sieht das Fahrzeug danach in ihrer eigenen Garage und kann es aus- und wieder einparken. Verkaufen, umschreiben, Kennzeichen ändern und weitere Schlüssel vergeben bleibt beim Besitzer. Du kannst einen Schlüssel jederzeit entziehen, die Person wird darüber informiert.
Verkauf läuft über ein Angebot: Du wählst eine Person in der Nähe und einen Preis, sie bekommt eine Karte mit Fahrzeug und Preis und muss zustimmen. Erst dann wird gezahlt. Der Server prüft beim Annehmen noch einmal Abstand, Besitz und Kontostand, taxPercent geht vom Erlös des Verkäufers ab. Ein Preis von 0 verschenkt das Fahrzeug. Beim Besitzerwechsel verfallen alle Zweitschlüssel.
Kennzeichen werden gegen price geändert. Der Server lehnt ab, wenn das Wunschkennzeichen schon vergeben ist, nicht zum pattern passt oder einen Eintrag aus blocked enthält. Nummernschilder im Fahrzeug-JSON werden mitgeändert, damit nichts auseinanderläuft.
Spitznamen und Favoriten gelten pro Person. Wer nur einen Zweitschlüssel hat, kann sich das Fahrzeug also eigenständig benennen, ohne dem Besitzer hineinzureden.
Stellplätze, Miete und Standgebühr
Config.Economy = {
enabled = true,
account = 'bank',
access = {
mode = 'free',
freeSlots = 2,
rentPrice = 3500,
rentDays = 7,
buyPrice = 85000,
blockStoreWhenExpired = true
},
slots = {
enabled = true,
price = 12000,
priceGrowth = 1.35,
max = 8
},
storageFee = {
enabled = true,
perDay = 60,
graceDays = 3,
maxFee = 25000
}
}
access.mode bestimmt, wie Spieler an eine Garage kommen:
| Modus | Bedeutung |
|---|---|
free |
Jeder darf einparken, es gibt nichts zu kaufen |
rent |
Stellplatz wird für rentDays Tage gemietet und kann verlängert werden |
buy |
Stellplatz wird einmalig gekauft und bleibt |
Ohne Freischaltung stehen dem Spieler nur freeSlots Plätze zur Verfügung, und mit blockStoreWhenExpired kann er gar nicht einparken. Ausparken geht immer, auch bei abgelaufener Miete. Sonst wären Fahrzeuge im Zweifel eingesperrt, und das ärgert Spieler mehr als es Geld einbringt.
Einzelne Garagen überschreiben den globalen Modus, indem du im Config-Eintrag Access = 'buy' setzt:
{
Visible = true,
Type = 'car',
Label = 'Rockford Hills',
Access = 'buy',
Position = vector3(365.21, 295.6, 103.46),
...
}
slots erlaubt es, zusätzliche Plätze zu kaufen. priceGrowth verteuert jeden weiteren Platz: bei 12000 und Faktor 1.35 kostet der erste 12.000, der zweite 16.200, der dritte 21.870.
storageFee sammelt eine Standgebühr an, solange ein Fahrzeug in der Garage steht. Fällig wird sie beim Ausparken, nicht per Lastschrift im Hintergrund. graceDays sind die ersten Tage, die frei bleiben, maxFee deckelt den Betrag, damit ein Urlaub niemanden ruiniert. Die offene Gebühr steht in der Oberfläche am Fahrzeug.
Verwahrstelle
Config.Impound = {
price = 500,
jobs = { 'police', 'sheriff' },
allowPayFromBank = true,
deleteOnImpound = true,
history = { enabled = true, keepDays = 60 },
release = { enabled = true, jobs = { 'police', 'sheriff' }, command = 'freigeben' },
towing = {
enabled = true,
jobs = { 'mechanic', 'towtruck' },
reward = 850,
account = 'bank',
command = 'abschleppen',
listCommand = 'abschleppauftraege',
maxOpen = 25
},
forfeit = { enabled = false, days = 21, command = 'verfall' },
...
}
Beim Beschlagnahmen gibt der Beamte Gebühr und Grund an. Beides landet in einer Akte: Kennzeichen, Beamter, Grund, Zeitpunkt und Gebühr. Der Besitzer sieht diese Akte in der Verwahrstelle, sieht also, warum sein Fahrzeug dort steht.
Freigabe über /freigeben oder den Knopf in der Oberfläche gibt ein Fahrzeug ohne Gebühr frei. Nur Jobs aus release.jobs können das.
Abschleppaufträge verbinden Polizei und Mechaniker: Ein Beamter stellt sich neben ein liegengebliebenes Fahrzeug und tippt /abschleppen. Mechaniker mit einem Job aus towing.jobs bekommen eine Meldung und markieren sich die offenen Aufträge per /abschleppauftraege auf der Karte. Bringen sie das Fahrzeug zur Verwahrstelle, drücken sie dort E und bekommen reward ausgezahlt.
Verfall ist standardmäßig aus, und das aus gutem Grund: /verfall löscht Fahrzeuge, die länger als forfeit.days in der Verwahrstelle stehen, endgültig aus der Datenbank. Schalte das nur ein, wenn deine Spieler wissen, dass es diese Frist gibt.
Konsolenausgaben
Config.Debug = false
Im Normalbetrieb bleibt x-garage still. In der Konsole erscheinen nur zwei Arten von Meldungen: echte Fehler und Warnungen, die dein Eingreifen brauchen, etwa eine fehlende Spalte oder eine Strukturänderung an deiner Datenbank.
Setzt du Config.Debug = true, kommen die Diagnosemeldungen dazu: welches Framework erkannt wurde, welche Spalten gefunden wurden, wie viele Garagen geladen sind, welches Kennzeichen zu welchem Datenbankeintrag aufgelöst wurde und warum ein Einparkversuch abgelehnt wurde. Genau die brauchst du beim Einrichten und bei der Fehlersuche, im laufenden Betrieb wären sie nur Rauschen.
Der Schalter wirkt sofort nach einem restart x-garage.
Discord-Log
Config.Webhook = ''
Trägst du hier eine Webhook-URL ein, bekommst du Einträge für Einparken, Ausparken, Beschlagnahmen, Auslösen, Freigeben, Verlegen, Zweitschlüssel, Verkäufe, Kennzeichenwechsel, Käufe und Mieten sowie Abschleppaufträge.
Garagen anlegen
Zwanzig Garagen, ein Hangar und ein Bootssteg sind vorkonfiguriert. Eigene legst du auf zwei Wegen an.
Im Spiel: /garagecreator öffnet den Editor. Du setzt Garagen-Punkt, NPC und Ausparkplätze mit einer Freecam, vergibst Name, Typ, Plätze und Rechte und speicherst. Diese Garagen liegen in x_garages und brauchen keinen Neustart. Zugriff regelst du über die ACE-Berechtigung:
add_ace group.admin x-garage.creator allow
In der Config: Ein Eintrag in Config.Garages sieht so aus:
{
Visible = true,
Type = 'car',
Label = 'Legion Square',
Position = vector3(220.14, -800.16, 30.72),
PedPosition = vector4(215.46, -808.54, 30.75, 248.17),
Model = `s_m_m_armoured_01`,
SpawnPosition = vector4(229.34, -801.47, 30.56, 161.85),
Interior = 'large',
Slots = 10,
AllowedJobs = {},
CompanyJob = nil
}
Type ist car, air oder boat und bestimmt, welche Fahrzeugklassen hineinpassen. AllowedJobs beschränkt den Zugang auf bestimmte Jobs. CompanyJob macht daraus eine Firmengarage: Alle Spieler mit diesem Job teilen sich dieselben Fahrzeuge.
Für mehrere Ausparkplätze nutzt du SpawnPositions als Liste statt SpawnPosition.
Interior verbindet die Garage mit einer begehbaren Halle aus Config.Interiors. Der Spieler betritt sie über den Knopf in der Oberfläche, bekommt einen eigenen Routing-Bucket und sieht seine Fahrzeuge dort real stehen.
Exports
Für eigene Skripte und Anbindungen an MDT oder Tablet:
-- Server
exports['x-garage']:getGarageOfPlate('XH 3120')
exports['x-garage']:hasVehicleKey(identifier, 'XH 3120')
exports['x-garage']:grantVehicleKey(identifier, 'XH 3120', 'Lena Fischer')
exports['x-garage']:getGarageAccess(identifier, '1')
exports['x-garage']:getStorageFee('XH 3120')
exports['x-garage']:getImpoundHistory('XH 3120')
exports['x-garage']:createTowJob('XH 3120', 'Unfall auf der Route 68', coords)
-- Client
exports['x-garage']:isInsideGarage()
exports['x-garage']:getGarages()
Häufige Fragen
Nach dem Update kommt "attempt to call a nil value"
FiveM liest die Dateiliste einer Resource nur beim refresh neu ein. Ein restart allein kennt neu hinzugekommene Dateien nicht. Führe einmal refresh und danach restart x-garage aus.
Fahrzeuge tauchen in keiner Garage auf
Setze Config.Debug = true, starte neu und schau in der Konsole nach, welche Spalten x-garage erkannt hat, und prüfe in der Tabelle, was dort steht. Fahrzeuge, die als eingeparkt markiert sind, aber auf eine Garage zeigen, die es nicht gibt, sind unsichtbar. Setze den Garagenwert auf das, was in Config.Database.sharedGarageId steht, dann erscheinen sie in jeder Garage.
"Das ist nicht dein Fahrzeug" beim Einparken
x-garage sucht das Kennzeichen erst genau so, wie es im Fahrzeug steht, und danach ohne Leerzeichen und Bindestriche. Damit findet es auch Einträge, die dein Autohaus als XH 3120 gespeichert hat, während das Fahrzeug XH3120 liefert.
Bleibt die Meldung, setze Config.Debug = true und starte die Resource neu. Dann steht in der Konsole eine Zeile wie:
[x-garage] Einparken abgelehnt: XH 3120 gehoert "char1:abc...", angefragt von "char1:def..."
Stimmen die beiden Werte nicht überein, gehört das Fahrzeug in der Datenbank wirklich jemand anderem. Steht dort nil, ist die Besitzerspalte leer, was bei manchen Autohäusern nach dem Kauf vorkommt. Steht in der Konsole stattdessen, dass das Kennzeichen gar nicht in der Tabelle steht, wurde das Fahrzeug nie gespeichert.
"Unknown column" beim Öffnen der Garage
Das kommt vor, wenn du die Spaltennamen in Config.Database.columns fest eingetragen hast und sie nicht zur Tabelle passen. Setze sie auf die Standardwerte zurück und lass die automatische Erkennung arbeiten.
Das Vorschaufahrzeug steht schief oder im Boden
Die Vorschau nutzt den ersten Ausparkplatz der Garage. Steht der halb in einer Mauer, sieht die Vorschau entsprechend aus. Setze den Platz im Editor neu oder korrigiere die SpawnPosition in der Config.
Die Vorschau kostet zu viele Bilder pro Sekunde
Config.Preview.autoRotate = false spart am meisten, weil die Kamera dann nur bei Eingaben neu berechnet wird. Ganz abschalten kannst du sie mit Config.Preview.enabled = false.
Ein Spieler kann nicht mehr einparken
Sehr wahrscheinlich ist die Miete abgelaufen. Die Oberfläche zeigt das oben im Panel an. Entweder verlängert er, oder du setzt Config.Economy.access.blockStoreWhenExpired = false, dann bleibt die Miete eine reine Slot-Frage.
Standgebühren sollen nicht rückwirkend gelten
Die Gebühr zählt ab dem Zeitpunkt, an dem ein Fahrzeug eingeparkt wird. Fahrzeuge, die schon vor der Installation in der Garage standen, haben keinen Eintrag und kosten nichts, bis sie einmal neu eingeparkt werden.
Der Verkauf schlägt mit "zu weit auseinander" fehl
Käufer und Verkäufer müssen innerhalb von Config.Ownership.trade.distance Metern stehen, und zwar beim Senden und beim Annehmen. Das verhindert Verkäufe quer über die Karte.
Firmengaragen zeigen Fahrzeuge doppelt
Ein Fahrzeug gehört entweder einem Spieler oder trägt die garage_id der Firmengarage. Steht in owner ein Spieler und in garage_id die Firmengarage, sieht es beides. Entscheide dich pro Fahrzeug für einen Weg.
Support
Kommst du nicht weiter, melde dich auf Discord. Halte bitte die Serverkonsole vom Start und deine Resource-Version bereit.
Etwas unklar geblieben?
Schreib uns auf Discord, wir ergänzen die Anleitung.