EmergencyOS Docs

EmergencyOS — Dokumentation

Willkommen in der offiziellen Entwickler- und Betreiber-Dokumentation des EmergencyOS-Ökosystems. Hier findest du zu jedem FiveM-Script die vollständige Anleitung: Installation, Voraussetzungen, Datenbank- und Panel-Sync, jede Config-Option und jeden Export mit Code-Beispiel.

Das Ökosystem

EmergencyOS besteht aus zwei Hälften, die über eine sichere HTTP-Bridge zusammenspielen:

  • Web-Panel (emergencyos.de) — die Fach-Anwendung (Akten, Fahndungen, Tickets, LiveMap, Statistiken). Läuft im Browser und als Tablet-Oberfläche im Spiel.
  • FiveM-Ressourcen — die Ingame-Scripts auf deinem GTA-V-Server, die sich mit dem Panel synchronisieren.

Produkte im Überblick

Produkt Zweck Exports
EmergencyOS Hauptsystem: Tablet/MDT für Polizei, Medic, Justiz & Feuerwehr 19
DispatchOS Leitstelle / Dispatch / MDT mit umfangreicher Export-API 23
LicenseOS Führerschein- & Fahrschulsystem 4
Ancomox Garage Multi-Framework-Garagensystem 2
Vehicle Shop Fahrzeug-Shop / Autohaus Events

Wähle links in der Sidebar ein Produkt — jede Seite ist gleich aufgebaut: Überblick → Voraussetzungen → Installation → Datenbank & Sync → Konfiguration → Exports → Events & Commands → Troubleshooting.

Allgemeine Voraussetzungen

  • Aktueller FiveM-Server (Artifacts)
  • oxmysql als Datenbank-Layer
  • Framework: ESX, QBCore / Qbox oder Standalone (je nach Script frei wählbar)
  • Ein EmergencyOS-Panel-Konto mit API-Key (für die panel-gebundenen Scripts EmergencyOS & LicenseOS)

So funktioniert die Panel-Anbindung

Jedes panel-gebundene Script verbindet sich beim Start automatisch mit dem Web-Panel — Server-IP und Port müssen nirgendwo eingetragen werden:

  1. Im Panel unter Einstellungen → Datenbank Sync (bzw. FiveM-Bridge) einen API-Key erzeugen.
  2. Den Key in die jeweilige config.lua / api_config.lua eintragen (SVConfig.API.key bzw. Config.ApiKey).
  3. Ressource neu starten — der Server registriert sich selbst am Panel (IP/Port automatisch). Fertig.

Tipp: Die genauen Schritte, Endpunkte und Fehlerbilder stehen bei jedem Produkt in den Abschnitten „Installation" und „Datenbank & Panel-Sync".

Konventionen in dieser Doku

  • Code in dieser Schrift = exakte Datei-, Config- oder Exportnamen aus dem Quellcode.
  • Tabellen listen Key · Standard · Beschreibung — die Standardwerte sind die echten Defaults aus dem ausgelieferten Code.

Support

Bei Fragen: Discord (discord.gg/2pKru3N) oder per E-Mail an info@ancomox.com.

EmergencyOS — Entwickler-Dokumentation

Überblick

EmergencyOS ist die Ingame-Extension (Tablet/MDT) zum EmergencyOS-Web-Panel (emergencyos.de). Das Tablet ist die Bedienoberfläche im Spiel; die eigentliche Fach-App (Akten, Fahndungen, Tickets, LiveMap, Statistiken) läuft als Web-Panel, mit dem sich der Spielserver über eine HTTP-Bridge synchronisiert.

Zielgruppe / Fraktionen: Polizei/Behörden (LSPD, FIB, LSSD/Sheriff), Justiz (DOJ), Rettungsdienst (LSMD/Medic) und Feuerwehr (LSFD). Die Abteilungs-Sichtbarkeit ist strikt getrennt. Standard-Whitelist im Code: police, ambulance, fib, justiz, admin.

Kernfeatures (aus CHANGELOG/README): - MDT/Tablet mit Multi-Window, freiem Resizing, Dark-Design, Rich-Text-Editor (Quill, Auto-Save), Aktenvorlagen. - Personen- & Fahrzeugakten, Fahndungssystem (Wanted/BOLO) — intern & öffentlich; öffentliche Fahndungen laufen als DUI-Carousel auf Welt-Bildschirmen (Config.WantedBillboards). - Digital Citation / Ticketsystem — inszeniertes Strafzettel-Ausstellen mit Beweisfoto (Evidence Cam → Discord), animiertem Beleg, Bürgerportal /strafzettel zum Einsehen & Bezahlen. - Bußgeldrechner mit dynamischen Modifikatoren (Geständig −20 %, Wiederholungstäter +50 %). - Sofort-Halterabfrage (F9 / /runplate) mit HUD und BOLO-Treffer. - App „Dienst & Statistik" — automatische Dienstzeit-Erfassung nach Job, Live-Dienststand, Leaderboard, Team-Übersicht, Dienstpflicht/Wochen-Soll, AFK-Schutz, optionaler Discord-Wochenbericht. - Verhaftungs-Anbindung für eigene Jail-Scripts (RecordArrest), Dispatch-Anbindung (Vanguard Dispatch Gateway). - Panic Button (10-13), interner Monitor (Atlas-Feed auf Wachen-Screens), KI-gestützte Tatberichte, Beweismittel-Verwaltung, FIB „Nexus Netzwerk". - Framework-agnostisch: ESX, QBCore oder STANDALONE (Custom) über die Data-Provider-API.

Voraussetzungen

Aus fxmanifest.lua (fx_version 'bodacious', game 'gta5', lua54 'yes'):

Voraussetzung Pflicht Hinweis
oxmysql Ja (harte Abhängigkeit) Muss vor EmergencyOS starten.
EmergencyOS-Panel-Konto Ja Instanz + API-Key auf emergencyos.de (ab Free-Version kostenlos).
Framework Optional es_extended (ESX) oder qb-core (QBCore) für Auto-Integration; sonst STANDALONE + Data Provider.
ox_lib Optional Standard-Benachrichtigungen (ox_lib:notify).
screenshot-basic Optional Nötig für Panel-Screenshots + Evidence Cam der Ticket-Zeremonie.
ox_target Optional Nur wenn Config.TargetSystem = "ox_target".
vanguard_dispatch Optional Dispatch-Gateway + Panic-Harmonie.
Billing/Notify (kontextabhängig) Optional esx_billing, okokBilling, qb-phone, okokNotify, esx_addonaccount (Society-Payout), LB-Phone, okokBanking, LicenseOS.

Es gibt keine expliziten dependency-Zeilen im Manifest außer @oxmysql.

Installation

  1. Ordnername: Die Resource muss exakt EmergencyOS heißen (Groß-/Kleinschreibung beachten — der Code nutzt durchgängig exports['EmergencyOS'] und GetCurrentResourceName()).
  2. Panel-Konto/Instanz: Auf emergencyos.de anmelden, Instanz anlegen. Im Panel unter Einstellungen → Datenbank Sync einen API-Key erzeugen (nur der Besitzer-Account sieht diesen Button). Panel-DB-Migrationen/-Dateien laut Keymaster/Panel einspielen.
  3. API-Key eintragen: In config/api_config.lua den Key in SVConfig.API.key setzen (siehe nächster Abschnitt).
  4. Framework wählen: In config/config.lua Config.Framework auf "ESX", "QBCORE" oder "STANDALONE" setzen; bei STANDALONE zusätzlich Data Provider registrieren (siehe README_CUSTOM_FRAMEWORK).
  5. server.cfg — Startreihenfolge (oxmysql zuerst, Framework davor): cfg ensure oxmysql ensure es_extended # oder qb-core (bei STANDALONE entfällt das) ensure EmergencyOS
  6. SQL-Migrationen einmalig ausführen (Dienstzeit-Spalten, Dienstpflicht, Ticket-Zuordnung, optional Patrol-Index; Duty-Tabelle emos_duty_stats).
  7. Server starten. Beim Start meldet sich der Server automatisch am Panel an (Handshake). Erfolg: [EmergencyOS] v… bereit | Panel verbunden (Instanz <id>) — Sync aktiv.

Datenbank & Panel-Sync

Die Synchronisation läuft über eine bidirektionale HTTP-Bridge (server/api_endpoint.lua, server/sync_bridge.lua). Der Spielserver ruft das Panel unter https://emergencyos.de/api/… auf; das Panel ruft umgekehrt den FiveM-HTTP-Endpoint des Servers auf (SetHttpHandler, Autorisierung per Api-Key).

Einrichtung (neuer Weg, ab v3-Handshake)

  1. Key erstellen: Panel → Einstellungen → Datenbank Sync → API-Key (nur Besitzer-Account). Neue Keys tragen das Präfix eos_.
  2. Key eintragen in config/api_config.luaSVConfig.API.key.
  3. Resource neu starten — fertig. Server-IP/Port müssen nirgendwo eingetragen werden: der Server registriert sich beim Start selbst am Panel und schickt beim Handshake Resource-Name und Server-Port (GetConvar('netPort', '30120')) mit; das Panel registriert IP/Port/Resource-Name automatisch als Push-Rückkanal.

Handshake-Logik (api_endpoint.lua)

  • Self-Service-Key (eos_…): Anmeldung nur mit dem Key. Header: Api-Key, Resource-Name, Server-Port, optional Server-Ip (nur wenn SVConfig.API.serverIp gesetzt). Ziel: GET https://emergencyos.de/api/getInstance → liefert die numerische Instance-ID (GlobalInstanceId).
  • Legacy-Key (ohne eos_, Ancomox-Bestandskunden): zusätzlich Instance-Username + Instance-Password (aus SVConfig.API.username/password) erforderlich. Fehlen sie, bricht der Handshake mit klarer Konsolenmeldung ab (kein weiterer Versuch bis Neustart).
  • Retry/Backoff: Bei nicht erreichbarem Panel automatische Wiederholung 10s → 30s → 60s → 2min → 5min. HTTP 400 (unvollständige Daten) stoppt dauerhaft mit Anleitung; 401/403 (Key falsch/abgelaufen) zeigt einmal einen Hinweis und versucht danach leise alle 10 min erneut.
  • Re-Handshake: Nach Erst-Erfolg meldet sich der Server stündlich erneut an (registriert IP/Port bei Server-Umzug automatisch neu).
  • Nach Verbindung wird nach 5 s die animierte Fahndungs-Rotation gestartet (api/wantedCarousel.php?instance=<id>).

Sicherheit des eingehenden Endpoints

  • Nur POST mit gültigem Key (Header Authorization: Bearer <key> oder Api-Key), Vergleich in konstanter Zeit (SecureCompare).
  • Rate-Limit SVConfig.ApiRateLimit (Default 120/10 s) → HTTP 429; Body-Limit SVConfig.ApiMaxBodyBytes (Default 524288 = 512 KB) → HTTP 413; Watchdog-Timeout SVConfig.ApiTimeoutMs (Default 15000 ms) → {"error":"timeout"}.
  • Jede Action läuft in eigenem Citizen-Thread; Actions können execute(data)->string (sync) oder executeAsync(data, done) definieren.

Genutzte Panel-Endpoints (ausgehend)

api/getInstance (Handshake), api/getWanteds.php (BOLO-Pull), api/mdtLookup.php (MDT), api/wantedCarousel.php, api/billPaid (via sendAPIReqest), api/getOpenTickets.php, api/setTicketPaid.php (Bürgerportal), version.json (Update-Check).

Eingehende Actions (Panel → Server)

U. a. syncWanteds (BOLO-Push), getDispatchState, dispatchCreateCall, dispatchAssign, dispatchClose, createBill, liveConsoleLog sowie die Data-Provider-Actions (getVehicle, searchPlayers, getPlayer, searchVehicles, trackPhone, getJobPlayerPositions).

BOLO/Fahndungs-Cache (sync_bridge.lua)

RAM-Cache mit 3 Quellen (Priorität absteigend): 1) Push (Actions['syncWanteds']), 2) Pull (api/getWanteds.php, Intervall SVConfig.BoloPullInterval, Default 300000 ms), 3) DEV-Fallback (direkte DB app_wanteds/app_vehicleRegister, nur wenn SVConfig.BoloDevFallback == true). MDT-Lookups werden SVConfig.MdtCacheTTL ms (Default 90000) gecacht, mit Live-Fallback auf die Data-Provider-Actions.

Konfiguration

config/config.lua (Client & Shared)

Key Standard Beschreibung
Config.Framework "ESX" Framework: "ESX", "QBCORE" oder "STANDALONE" (Custom, via Data Provider).
Config.TargetSystem "none" Target-System für Interaktionen; unterstützt "ox_target", sonst eigene Marker-/Tasten-Logik.
Config.Locale "en" Sprache der Tablet-Meldungen: "en" oder "de" (siehe locales.lua).
Config.ScreenshotWebhook "https://discord.com/api/webhooks/" Discord-Webhook für Panel-Screenshots + Evidence Cam. Leer/Platzhalter = deaktiviert.
Config.Prop "prop_cs_tablet" Handobjekt beim geöffneten Tablet.
Config.Animation.dict "amb@world_human_tourist_map@male@base" Anim-Dictionary der Haltung (auch für Ticket-Zeremonie).
Config.Animation.anim "base" Anim-Name.
Config.EnablePanicButton true Panic Button (10-13) aktivieren.
Config.PanicButtonCommand "panicbutton" Command-Name (/panicbutton).
Config.PanicButtonKey "P" Standard-Taste (GTA-Settings, frei belegbar).
Config.ServerPraefix "TestServerInstance346" Präfix für lokal gespeicherte Zugangsdaten (KVP). Pro Instanz eindeutig wählen!
Config.AutoFill.AutoFillPW true Gespeicherte Zugangsdaten ins Login eintragen.
Config.AutoFill.AutoLogin true Zusätzlich automatisch einloggen.
Config.AutoFill.AutoFillLogout true Daten auch nach Logout wieder eintragen.
Config.EnableParallax true Parallax-/Tiefeneffekt der Oberfläche (kosmetisch).
ShouldPadBeLocked() return false Funktion: eigene Zusatzbedingung; true = Tablet gesperrt.
Config.LockPadWhileDead true Tablet sperren, solange der Spieler tot ist.
Config.PlayerDeadEvent "esx:onPlayerDeath" Framework-Event Tod (bei Custom anpassen).
Config.PlayerReviveEvent "esx_ambulancejob:revive" Framework-Event Wiederbelebung.
Config.PlayerSpawnedEvent "playerSpawned" Framework-Event Spawn.
Config.EnableJobLock true true = nur Whitelist-Jobs dürfen das Tablet öffnen.
Config.WhitelistedJobs {"police","ambulance","fib","justiz","admin"} Jobs mit Tablet-Zugriff; steuert auch F9-Halterabfrage, Atlas-LiveLog-Empfang, Beamtenerkennung, serverseitige Event-Prüfung.
Config.UseKeyMapping true Tablet per Taste/Command öffnen (RegisterKeyMapping).
Config.DefaultKeybind "MULTIPLY" Standard-Taste (GTA-Bezeichner, hier Numpad *).
Config.KeyBindText "Open EmergencyOS Tablet" Beschriftung in den GTA-Tastatureinstellungen.
Config.NeedItem false true = Item im Inventar nötig.
Config.PadItems {"tablet","othertablet"} Item-Namen, die als Tablet gelten.
Config.UsePadOnFoot true Tablet zu Fuß nutzbar.
Config.OnFootFullscreen false Zu Fuß im Vollbild öffnen.
Config.UsePadInCar true Tablet im Fahrzeug nutzbar.
Config.PadInCarFullscreen true Im Fahrzeug im Vollbild öffnen.
Config.OpenKey "E" Interaktionstaste an Positionen/Props (Anzeige-Text).
Config.PlayerJobChange "esx:setJob" Framework-Event bei Jobwechsel (aktualisiert Whitelist live).
Config.EnablePositions true Feste Terminal-Positionen aktivieren.
Config.OpenDistance 2.0 Interaktionsdistanz zur Position (m).
Config.MarkerDrawDistance 5.0 Ab dieser Distanz wird der Marker gezeichnet (m).
Config.Positions 1 Beispiel (position01, Mission Row) Terminal-Liste. Pro Eintrag: position (vector3), jobs, helpnotify, fullscreen, marker{enabled,type,moveUpDown,rotate,color{r,g,b,t},size{x,y,z}}.
Config.EnableProps true Welt-Props als Terminals aktivieren.
Config.PropOpenDistance 2.0 Interaktionsdistanz zum Prop (m).
Config.Props prop_park_ticket_01, prop_monitor_01a, prop_monitor_01b (alle jobs={"police"}) Prop-Modelle als Terminal (jobs/helpnotify/fullscreen).
Config.WantedBillboardProximity true Öffentliche Fahndungen nur in der Nähe rendern.
Config.WantedBillboardDistance 30.0 Render-Distanz der Fahndungs-Billboards (m).
Config.WantedBillboards 4 Einträge (prop_tv_flat_01/03, prop_trev_tv_01, vw_prop_vw_cinema_tv_01, je tex="script_rt_tvscreen") Welt-Screens für die öffentliche Fahndungs-Rotation (DUI). Pro Eintrag dict, tex.
Config.InternalMonitorJobs {"police","fib"} Jobs, deren Umgebung interne Monitore aktiviert.
Config.InternalBillboards 1 Eintrag (prop_tv_flat_michael, tex="script_rt_tvscreen") Screen-Modelle für den internen Atlas-Feed.
Config.InternalMonitorProximity (nicht in Default-Config) Wird in cl_internal_monitor.lua gelesen. USE_PROXIMITY ist nur true, wenn der Wert exakt false ist — sonst wird immer gerendert.
Config.InternalMonitorDistance 25.0 (Fallback) Render-Distanz interner Monitore (m).
EMOSClientNotify(title,text) leer Funktion: eigenes Notify-System einhängen (leer = Standard/ox_lib).
Config.DebugLiveLog false Jeden Atlas-LiveLog zusätzlich in F8 ausgeben.
Config.TicketFx.enabled true Ingame-Inszenierung beim Ticket-Ausstellen.
Config.TicketFx.handoverDistance 8.0 Max. Distanz Beamter↔Ziel (m) für Vor-Ort-Modus; weiter = Zustellung „per Post".
Config.TicketFx.evidenceCam true Beweisfoto im Blitz-Moment in ScreenshotWebhook posten (braucht screenshot-basic).
Config.PlateCheck.enabled true Sofort-Halterabfrage aktivieren (Client + Server).
Config.PlateCheck.defaultKey 'F9' Standard-Taste (frei belegbar; alternativ /runplate).
Config.CitizenTickets.enabled true Bürgerportal (offene Strafzettel) aktivieren; deaktiviert auch die Anlaufstellen.
Config.CitizenTickets.command 'strafzettel' Chat-Command, der das Portal öffnet.
Config.CitizenTickets.defaultKey '' Optionaler zusätzlicher Keybind ('' = nur Command).
Config.CitizenTickets.payFromBank true true = erst Bank, dann Bargeld; false = nur Bargeld.
Config.CitizenTickets.payoutSociety 'society_police' ESX-Society (esx_addonaccount), in die Bußgelder fließen; leer = kein Auto-Payout.
Config.CitizenTickets.Payout (auskommentiert) Funktion function(amount, ticket) — überschreibt payoutSociety komplett.
Config.CitizenTickets.CloseFrameworkBill (auskommentiert) Funktion function(billId) — zugehörige Framework-Rechnung schließen.
Config.CitizenTickets.interactionKey 38 GTA-Control-ID der Interaktionstaste (38 = E).
Config.CitizenTickets.interactionDistance 2.2 Interaktionsdistanz an Anlaufstellen (m).
Config.CitizenTickets.Locations 1 Beispiel (Mission Row Empfang) Bürgerbüro-Punkte. Pro Eintrag: coords (vector4), ped (Model oder false), scenario, label, blip{enabled,sprite,color,scale,label}. NPCs spawnen ab 45 m, despawnen ab 65 m.
Config.DutyStats.enabled true App „Dienst & Statistik" aktivieren.
Config.DutyStats.onDutyJobs {'police','fib','ambulance'} Jobs, die als „im Dienst" zählen (getrennt von WhitelistedJobs; Off-Duty-Jobs weglassen).
Config.DutyStats.jobCheckInterval 15 Sek.: wie oft der Server den Job prüft (min. 5).
Config.DutyStats.flushInterval 120 Sek. bis zur Persistierung in die Spiel-DB (min. 30).
Config.DutyStats.trackActivity true Festnahmen/Panic/Scans/Halterabfragen mitzählen.
Config.DutyStats.leaderboardLimit 25 Leaderboard-Länge (min. 5).
Config.DutyStats.notifyOnChange true Kurze Info + Atlas-Log bei Dienstwechsel.
Config.DutyStats.hud false Dezentes Live-HUD (IM DIENST + Schichtzeit).
Config.DutyStats.hudPosition {x=0.015, y=0.62} HUD-Position.

Weitere Config.DutyStats-Optionen (im Code unterstützt, in der Default-Config nicht ausgeschrieben — bei Bedarf ergänzen):

Key Standard Beschreibung
Config.DutyStats.afkPause true AFK-Schutz aktiv (keine Dienstzeit bei Untätigkeit).
Config.DutyStats.afkSeconds 480 (min. 60) AFK-Schwelle in Sekunden (8 Min).
Config.DutyStats.offDutySuffix "_offduty" Suffix, das Off-Duty-Jobs erkennt.
Config.DutyStats.jobFaction {} Mapping Jobname → Fraktion, falls abweichend (z. B. { ambulance='medic', bcso='sheriff' }).
Config.DutyStats.GetCharName (nicht gesetzt) Funktion function(src) — eigener RP-Name-Hook.
Config.DutyStats.livePushInterval 60 (min. 30) Sek. zwischen Live-Dienststand-Pushes.
Config.DutyStats.statsPushInterval 300 (min. 60) Sek. zwischen Statistik-Pushes.
Config.DutyStats.statsMaxRows 1000 (min. 50) Zeilen-Obergrenze pro Statistik-Push.

config/api_config.lua (Server)

Key Standard Beschreibung
SVConfig.UseAPI true Master-Schalter für die gesamte Panel-Bridge (aus = API/Sync/Dispatch/BOLO inaktiv).
SVConfig.DebugLog false true = ausführliche Diagnose (EOSLog); false = nur Statuszeile + Warnungen/Fehler.
SVConfig.API.key "eos_1f9b0482bb23f6459bbec0bfe2452643ec4bc4e0" (Beispiel) API-Key aus dem Panel (Einstellungen → Datenbank Sync). eos_… = Self-Service.
SVConfig.API.serverIp "" Optionales IP-Override für Multi-IP-Server (leer = Panel erkennt automatisch).
SVConfig.API.username "" Nur Legacy (Ancomox-Key ohne eos_): Besitzer-Username.
SVConfig.API.password "" Nur Legacy: Besitzer-Passwort.
SVConfig.BoloPullInterval 300000 BOLO-Pull-Fallback (ms); 0 = aus. „recommended not to change".
SVConfig.MdtCacheTTL 90000 MDT-Lookup-RAM-Cache (ms).
SVConfig.LicenseSource "auto" Führerschein-Quelle: "auto", "licenseos", "framework", "off". Immer live aus der Spielserver-DB.
SVConfig.UseBilling true Rechnungserstellung (createTicketBill) aktivieren.
SVConfig.BillingTrigger "EmergencyOS:billPayed" Event-Name, das bezahlte Rechnungen ans Panel meldet (ruft billPaid).
SVConfig.ApiRateLimit 120 (nicht in Default-Config; Fallback in api_endpoint.lua) Requests/10 s.
SVConfig.ApiMaxBodyBytes 524288 (Fallback) Max. Request-Body (512 KB).
SVConfig.ApiTimeoutMs 15000 (Fallback) Watchdog-Timeout pro Action (ms).
SVConfig.BoloDevFallback (nicht gesetzt → aus) true = direkter DB-Zugriff auf app_wanteds/app_vehicleRegister als BOLO-Fallback (nur Dev/geteilte DB).
createTicketBill(targetIdentifier, billSender, billFraction, billText, billAmount) eingebaut Funktion: erzeugt Rechnung; unterstützt esx_billing, okokBilling, qb-phone; für Custom Banking anpassen. Gibt billId zurück.
EOSLog(msg) eingebaut Log-Helfer (nur bei DebugLog).

config/cl_integrations.lua (Client, Framework-Bridge-Stubs)

Diese Datei enthält Stub-Funktionen, die bei Custom-Frameworks überschrieben werden können. Standardwerte:

Funktion Standard-Verhalten Beschreibung
FrameworkGetPlayerJob() return playerJobName Liefert den aktuellen Client-Jobnamen.
FrameworkDoesPlayerHaveItem(item) return true Item-Besitzprüfung (Standard: immer true).
EMOSHelpNotify() leer Hook für eigene Help-Notify-Anzeige.
HelpNotifyClose() leer Hook zum Schließen der Help-Notify.

Exports

exports['EmergencyOS']:<Name>(...). Insgesamt 19 registrierte Exports (5 Client, 14 Server).

Client-Exports (client/client.lua)

LockEmergencyOS() — Client. Schließt das Tablet und sperrt das Öffnen (IsPadLocked = true). Keine Rückgabe.

exports['EmergencyOS']:LockEmergencyOS()

UnlockEmergencyOS() — Client. Hebt die Sperre auf. Keine Rückgabe.

exports['EmergencyOS']:UnlockEmergencyOS()

CloseEmergencyOS() — Client. Schließt das Tablet (ohne Sperre).

exports['EmergencyOS']:CloseEmergencyOS()

OpenTablet([fullscreen]) — Client. Öffnet das Tablet; fullscreen (bool) erzwingt Vollbild.

exports['EmergencyOS']:OpenTablet(true)

SetPlayerJob(jobName) — Client. Setzt den lokal bekannten Job (Bridge.PlayerJob) für Whitelist/UI. Bei STANDALONE/Custom bei Charwahl, Spawn und Jobwechsel aufrufen.

exports['EmergencyOS']:SetPlayerJob("police")

Server-Exports

SetPlayerJob(source, jobName) — Server (server.lua). Setzt global den Job eines Online-Spielers (Bridge.PlayerJobs[src]) für Whitelist-Prüfung, LiveMap-Gruppen und Panic-Broadcast.

exports['EmergencyOS']:SetPlayerJob(source, "police")

GetInstanceId() — Server (api_endpoint.lua). Gibt die vom Panel zugewiesene Instance-ID zurück (oder nil, solange nicht verbunden).

local iid = exports['EmergencyOS']:GetInstanceId()

billPaid(billId) — Server (server_actions.lua). Meldet dem Panel, dass eine Rechnung bezahlt wurde (ruft api/billPaid). Aus eigenem Banking aufrufen.

exports['EmergencyOS']:billPaid(billId)

RegisterDataProvider(tbl) — Server (sv_data_provider.lua). Registriert die Custom-Framework-Hooks. Gültige Hooks: getIdentifier, resolveSource, getPlayer, searchPlayers, getVehicle, searchVehicles, findByPhone, getJob, removeMoney. Rückgabe: true/false. Alle Hooks müssen synchron liefern (oxmysql:executeSync).

exports['EmergencyOS']:RegisterDataProvider({
    getIdentifier = function(src) return "char:"..MyCore.GetCharId(src) end,
    getPlayer     = function(id) return { firstname="Max", lastname="Muster", dob="1990-05-14" } end,
})

HasDataProvider() — Server. true, wenn ein Provider registriert ist.

if exports['EmergencyOS']:HasDataProvider() then ... end

GetPlayerIdentifierEOS(src) — Server. Persistenter Panel-Identifier eines Spielers (Provider → ESX license → QB citizenid → FiveM license).

local ident = exports['EmergencyOS']:GetPlayerIdentifierEOS(src)

ResolveIdentifierToSource(identifier) — Server. Umkehrung: Identifier → Server-ID (oder nil).

local src = exports['EmergencyOS']:ResolveIdentifierToSource(ident)

RecordArrest(officerSrc) — Server (sv_duty.lua). Schreibt dem Beamten eine Verhaftung gut (erscheint in Team-Übersicht/Detailansicht/Settings). Rückgabe: true/false (false bei ungültiger/offline Source).

exports['EmergencyOS']:RecordArrest(officerSource)   -- aus dem eigenen Jail-Script

AddOfficerActivity(officerSrc, column, amount) — Server (sv_duty.lua). Erhöht einen beliebigen Aktivitäts-Zähler (z. B. 'arrests', 'scans', 'plate_checks', 'panics').

exports['EmergencyOS']:AddOfficerActivity(src, 'arrests', 1)

IsOnDuty(src) — Server (sv_duty.lua). true, wenn der Spieler aktuell als im Dienst geführt wird.

if exports['EmergencyOS']:IsOnDuty(src) then ... end

PlayTicketCeremony(officerSrc, targetSrc, amount, reason, fraction) — Server (sv_ticket_fx.lua). Spielt die Digital-Citation-Zeremonie ab (ohne echte Rechnung). officerSrc darf nil sein → Zustellung „per Post" (mode='mail'), sonst Vor-Ort-Modus (mode='handover'). Rückgabe: true/false.

exports['EmergencyOS']:PlayTicketCeremony(officerSrc, targetSrc, 250, "Falschparken", "LSPD")

IsPlateWanted(plate) — Server (sync_bridge.lua). true, wenn das (normalisierte) Kennzeichen im BOLO-RAM-Cache steht. Nicht-blockierend.

if exports['EmergencyOS']:IsPlateWanted("LS12345") then ... end

GetWanteds() — Server (sync_bridge.lua). Gibt wanteds (Tabelle) und version (Zahl) des aktuellen BOLO-Cache zurück.

local wanteds, version = exports['EmergencyOS']:GetWanteds()

MdtLookup(payload, cb) — Server (sync_bridge.lua). Asynchrone MDT-Abfrage ans Panel (mit Live-Fallback + Cache). payload z. B. { action="personDetail", id="emergencyos__LIVE:..." } bzw. { action=..., query=... }. Ergebnis kommt über cb(result).

exports['EmergencyOS']:MdtLookup({ action = "plate", query = "LS12345" }, function(res) ... end)

Öffentliche API-Events ans Panel (Payloads)

Für die Anbindung eigener Scripts (Verhaftungen/Scan/Dispatch/Tickets). Server-authoritativ, alle serverseitig auf WhitelistedJobs geprüft:

EmergencyOS:PanicButtonTriggerServerEvent. Payload: coords (vector3/Table mit x,y), streetName (string). Broadcastet 10-13 an alle Whitelist-Einheiten (bzw. übergibt an Vanguard, falls aktiv).

TriggerServerEvent("EmergencyOS:PanicButton", vec3(443.46,-982.45,30.68), "Mission Row")

EmergencyOS:ScanClosestPlayerTriggerServerEvent. Payload: targetServerId (number). Serverseitige Distanzprüfung (≤ 6 m), öffnet die Personenakte des Ziels beim scannenden Beamten (ForceOpenRecord). Zählt als Aktivität scans.

TriggerServerEvent("EmergencyOS:ScanClosestPlayer", targetSource)

EmergencyOS:CheckALPRTriggerServerEvent. Payload: plate (string, ≤ 12 Zeichen). Prüft BOLO-Status, Cooldown 10 s pro Spieler/Kennzeichen; bei Treffer ALPRResult an den Absender.

TriggerServerEvent("EmergencyOS:CheckALPR", "LS12345")

Verhaftungen ans Panel: über den Export RecordArrest(officerSrc) (siehe oben) — der empfohlene Weg aus dem CHANGELOG/examples/.

Dispatch ans Panel (Vanguard Gateway): Actions getDispatchState, dispatchCreateCall, dispatchAssign, dispatchClose; Live-Push über AddEventHandler('vanguard:dispatch:broadcast', …)TriggerClientEvent('EmergencyOS:ReceiveLiveLog', -1, { dispatch = payload }).

Events & Commands

Commands (für Betreiber/Spieler relevant)

Command Seite Beschreibung
/openEmergencyOS Client Öffnet das Tablet (Standard-Keybind MULTIPLY, via Config.UseKeyMapping).
/strafzettel Client Bürgerportal – offene Strafzettel einsehen & bezahlen (alle Spieler; Name/Key aus Config.CitizenTickets).
/runplate Client Sofort-Halterabfrage (Standard-Keybind F9, aus Config.PlateCheck.defaultKey).
/panicbutton Client Panic Button 10-13 (Standard-Taste P; aus Config.PanicButtonCommand/PanicButtonKey).
/scanid Client Scannt den nächsten Spieler im Radius 3,0 m (nur Whitelist-Jobs) → öffnet dessen Akte.
/toggleterminal Client Schaltet die eingebaute System-Konsole (Terminal) im Tablet um.
/emos_fix Client Rettungsanker: setzt festgefahrene Anzeigen/NUI sofort zurück.
/emos_ticketfx <targetId> [betrag] [grund…] bzw. emos_ticketfx demo … Server/Konsole Test der Ticket-Zeremonie (keine echte Rechnung). Konsole oder Whitelist-Beamter.
/emos_dutydebug Server/Konsole Diagnose: zeigt pro Online-Spieler den erkannten Job, On-Duty-Status, Identifier, emos_duty_stats-Zeilen — zum Ermitteln der exakten Jobnamen für onDutyJobs.
/testinternal, /stopinternal, /whatprop, /emosgethash Client Entwickler-/Debug-Hilfen (interner Monitor testen/stoppen, Prop-/Hash-Ermittlung).

Wichtige Netz-Events (Auszug)

  • Bürgerportal: Client→Server EmergencyOS:Citizen:GetTickets, EmergencyOS:Citizen:PayTicket(ticketId), EmergencyOS:Citizen:PayAll; Server→Client EmergencyOS:Citizen:Tickets, EmergencyOS:Citizen:PaidResult, EmergencyOS:Citizen:PaidAll. Zahlung holt frischen Preis (getOpenTickets.php), bucht ab, markiert atomar (setTicketPaid.php), zahlt an Society aus, mit Refund bei Fehler.
  • Dienst & Statistik: EmergencyOS:Duty:Idle, EmergencyOS:Duty:GetMine, EmergencyOS:Duty:GetBoard(period); Antworten EmergencyOS:Duty:State/Mine/Board.
  • Sonstige: EmergencyOS:openTablet, EmergencyOS:ForceOpenRecord, EmergencyOS:ALPRResult, EmergencyOS:PanicButtonAlert, EmergencyOS:RunPlate, EmergencyOS:TicketFx, EmergencyOS:TicketEvidence, EmergencyOS:SyncPosition (Live-Tracking), EmergencyOS:ReceiveLiveLog (Atlas/Dispatch), EmergencyOS:UpdateWantedBillboard, EmergencyOS:UpdateInternalMonitor.
  • Billing: RegisterNetEvent(SVConfig.BillingTrigger) ("EmergencyOS:billPayed") → meldet billId als bezahlt ans Panel.

Hinweise / Troubleshooting

  • Ordnername: Resource muss EmergencyOS heißen — alle exports['EmergencyOS'] und der Handshake (GetCurrentResourceName()) hängen daran.
  • Sync erreicht Panel nicht / Konsolenmeldungen:
  • „Kein API-Key hinterlegt"SVConfig.API.key in api_config.lua fehlt/ist Platzhalter (eos_HIER_DEN_KEY_EINFUEGEN, selberapikeywieimancomoxdashboard).
  • HTTP 400 → Anmeldedaten unvollständig: bei eos_…-Key darf nur der Key gesetzt sein; bei Legacy-Key müssen username+password gesetzt sein. Danach Resource neu starten.
  • HTTP 401/403 → Key falsch/abgelaufen. Nach „Key neu generieren" im Panel den neuen Key eintragen. Weitere Versuche laufen leise alle 10 min.
  • „Panel nicht erreichbar" → Netzwerk/Firewall; Retry-Backoff greift automatisch.
  • Multi-IP-Server: Schlägt der Verbindungstest im Panel trotz laufendem Server fehl, die erreichbare FiveM-IP in SVConfig.API.serverIp eintragen (überschreibt die Auto-Erkennung).
  • BOLO-Pull-Warnung (api/getWanteds.php HTTP ≠ 200) → Panel-Dateien nicht/veraltet deployed. Für Dev ohne deployte PHP: SVConfig.BoloDevFallback = true (nur bei geteilter DB).
  • Framework-Bridge / Custom Framework: Bei Config.Framework="STANDALONE" liefern nur registrierte Data-Provider-Hooks Daten; ein Hook, der nil liefert, gilt als „nicht gefunden" (kein Fallback auf ESX/QB). Hooks müssen synchron sein (oxmysql:executeSync). Jobs zusätzlich über SetPlayerJob (Server und Client) setzen. getIdentifier muss Reconnects überleben (Server-IDs ungeeignet); ohne Provider wird die FiveM-License genutzt.
  • Dienstzeiten falscher/kein Job erkannt: /emos_dutydebug zeigt den exakt erkannten Jobnamen → in Config.DutyStats.onDutyJobs bzw. jobFaction eintragen; Off-Duty-Jobs nicht auflisten.
  • Ticket-Zeremonie ohne Beweisfoto: screenshot-basic fehlt oder Config.ScreenshotWebhook ist leer/Platzhalter → Evidence Cam deaktiviert.
  • Tickets vor dem Update lassen sich nicht nachträglich Beamten zuordnen (Zählung ab Einspielen).
  • Update-Check meldet dauerhaft „neue Version": version.json auf dem Webserver an die tatsächliche Resource-Version 3.9.6 anpassen.

DispatchOS — Dokumentations-Spec

Leitstellen-, Dispatch- und MDT-System für FiveM. Quelle: [ANCOMOX]/dispatchos · Version 2.22.0.


Überblick

Zweck: DispatchOS ist das zentrale Notruf-, Einsatz- und Leitstellensystem. Es nimmt Alarme (automatisch erkannt oder per API) entgegen, legt Einsätze („Calls") mit Priorität, Blip, Zone und Timeline an, verteilt sie an die zuständigen Fraktionsgruppen und stellt eine Vollbild-Leitstelle mit Taktikkarte bereit. Architektur laut Manifest: „RAM-first, Push statt Polling, serverseitig validiert".

Kernfeatures (aus README.md und Code verifiziert): - Automatische Einsätze (Detektoren): Schüsse, Drive-by, Fahrzeugraub, Schlägerei, Unfall, Raser, Person am Boden, ALPR-Treffer, Panik, Zivil-Notrufe — mit Merge naher Mehrfachmeldungen (MergeRadius/MergeTime). - Leitstelle / Dispatch Center (F6): Taktikkarte mit Live-Einheiten, Icon-Marker je Code, Einsatzliste + Filter, Detail-Dock mit Timeline & Notizen, Zuweisung, Fahndungs-Ticker (BOLO), Ø-Reaktionszeit-KPI, 7-Tage-Statistik-Cockpit, Panic-Auto-Fokus. - MDT (F7): Kennzeichen-/Personenabfrage aus der Spieldatenbank (DirectDB) oder über EmergencyOS-Gateway; Fahndungsabgleich, Live-Online-Status. - ALPR (F10/F11): Kennzeichenscanner im Einsatzfahrzeug per Raycast mit 3D-Holo-Marker und Fahndungstreffer. - Schnellfunk (F5): konfigurierbare 10-Codes + Freitext + Position. - Panic Button, Smart-3D-Ping, Eskalation (P3→P2→P1), Zonen-Routing, Rechte-System, Auto-Zuweisung bei Panik/Backup. - Notruf-Integration: lb-phone (Nachricht + Anruf + Handy-Alerts) und generische Export-API für andere Telefon-Scripts. - Optionale Web-Leitstelle (dispatchos.de) und EmergencyOS-Panel-Anbindung über FiveM-Events. - Zweisprachig (de/en), Discord-Webhook-Feed integriert.


Voraussetzungen

Das fxmanifest.lua deklariert keine dependencies — alle Abhängigkeiten werden zur Laufzeit per GetResourceState(...) weich erkannt. Manifest-Eckdaten:

Manifest-Feld Wert
fx_version cerulean
game gta5
lua54 yes
ui_page html/index.html

Abhängigkeiten (alle laufzeit-erkannt, keine harte Pflicht):

Komponente Pflicht Zweck
FiveM-Server (aktuelle Artifacts) ja Basis
oxmysql empfohlen Persistenz (dispatchos_calls), Statistik/Heatmap, MDT-DirectDB. Ohne → alles läuft RAM-only, keine DB-Statistik.
ESX (es_extended) / QBCore (qb-core / qbx_core) optional Job/Duty/Namen. Auto-Erkennung; sonst STANDALONE.
EmergencyOS optional BOLO-Sync, Atlas-Feed, Panel-MDT-Gateway, Tablet-Panic, Heatmap.
lb-phone optional Telefon-Notrufe (Modul modules/phone).
ox_lib optional Schönere Notifications (ox_lib:notify); sonst Chat-Fallback.

Installation

  1. Ordner dispatchos nach resources/ kopieren.
  2. In server.cfg nach oxmysql, Framework und (optional) EmergencyOS starten: cfg ensure oxmysql ensure es_extended # bzw. qb-core / qbx_core ensure EmergencyOS # optional ensure dispatchos
  3. Tabelle dispatchos_calls wird beim ersten Start automatisch angelegt bzw. migriert (fehlende Spalten werden ergänzt; einmalige Übernahme einer alten vanguard_calls-Tabelle).
  4. config.lua anpassen — v. a. VConfig.JobGroups an die eigenen Fraktions-Jobs.

Panel-Bezug: Zwei optionale Anbindungen (beide ohne Ingame-Paywall): die gehostete Web-Leitstelle (VConfig.WebLink, Modul web_link) und das EmergencyOS-Panel (rein über FiveM-Events, siehe nächster Abschnitt).


Datenbank

Nur bei VConfig.PersistCalls = true und gestartetem oxmysql. Eine Tabelle:

dispatchos_calls — angelegt via CREATE TABLE IF NOT EXISTS, danach Auto-Migration der Spalten (SHOW COLUMNS → fehlende per ALTER TABLE ADD COLUMN).

Spalte Typ Bemerkung
id INT AUTO_INCREMENT PK
code VARCHAR(16) z. B. 10-71
label VARCHAR(120)
street VARCHAR(96)
priority TINYINT DEFAULT 3 1–3
x, y, z FLOAT DEFAULT 0 z per Migration ergänzt
responded_at TIMESTAMP NULL erste Zuweisung → Reaktionszeit-KPI
arrived_at TIMESTAMP NULL Auto-„Angekommen"
zone VARCHAR(48) Zuständigkeitszone
reports INT DEFAULT 1 Anzahl gemergter Meldungen
units INT DEFAULT 0 zugewiesene Einheiten (bei Close)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Index idx_created
closed_at TIMESTAMP NULL

Engine InnoDB, Charset utf8mb4. Retention: Zeilen älter als VConfig.DBRetentionDays werden periodisch gelöscht (Batch LIMIT 5000; 0 = nie). Einmal-Rename beim Start: falls vanguard_calls existiert und dispatchos_calls nicht, wird umbenannt (Bestandsdaten-Übernahme).

Die MDT-DirectDB liest zusätzlich fremde Tabellen (nur SELECT, keine eigene Anlage): ESX users/owned_vehicles, QBCore players/player_vehicles sowie optional eine lb-phone-Tabelle (Default phone_phones). Tabellennamen über VConfig.MDT.DirectDB überschreibbar.


Sync / Panel-Anbindung

A) EmergencyOS-Panel (Event-basiert, keine Webserver-Last)

Wird automatisch aktiv, sobald GetResourceState('EmergencyOS') == 'started': - BOLO/Fahndung: DispatchOS ruft per pcall einen der Exports exports.EmergencyOS:GetWanteds/GetBolos/GetWantedPlates/GetWantedList ab (erster funktionierender wird gemerkt) und exports.EmergencyOS:IsPlateWanted(plate). Push-Refresh über Listener EmergencyOS:WantedsUpdated. - Panic: Kompatibilitäts-Listener RegisterNetEvent('EmergencyOS:PanicButton', ...) — Tablet-Panik landet direkt als Einsatz (Dedupe über Cooldown). - Atlas-Feed: internes Event dispatchos:dispatch:broadcast transportiert alle Dispatch-/Funk-/Status-Ereignisse (auch Discord/Phone/WebLink hängen hier dran). - MDT-Gateway: exports.EmergencyOS:MdtLookup(payload, cb) (Panel-Akten + Live-Fallback), genutzt wenn DirectDB aus ist bzw. für Panel-Anreicherung (PanelEnrich/PanelFileLookup). - Akte öffnen: NUI-Callback mdtOpenRecordTriggerEvent('EmergencyOS:ForceOpenRecord', id). - Externer Fokus: AddEventHandler('dispatchos:client:FocusCall', callId) öffnet die Leitstelle und fliegt per Kamera auf den Einsatz (für Atlas-Terminal-Klick). - Statistik/Heatmap: gemeinsame Tabelle dispatchos_calls.

Für vollen Umfang liegen dem EmergencyOS-Paket 3 aktualisierte Dateien bei (html/app.js, Panel-Terminal.php, kleine client.lua-Ergänzungen). MDT.PanelFileLookup erst nach verbundener Panel-API (gültiger API-Key) aktivieren.

B) Web-Leitstelle (modules/web_link/server.lua, optional)

Nur aktiv, wenn VConfig.WebLink.Token gesetzt und kein Platzhalter ist (Platzhalter-Liste im Code: '', HIER_DEN_TOKEN_EINFUEGEN, los_HIER_DEN_KEY_EINFUEGEN). Ohne gültigen Token: Modul kehrt sofort zurück → reines Standalone.

  • Nur ausgehendes HTTPS (PerformHttpRequest), kein offener Port. Auth per Authorization: Bearer <Token> und Api-Key: <Token> (Apache/PHP-FPM-Kompatibilität).
  • Handshake: GET {BridgeUrl}/v1/hello → prüft Token; Klartext-Feedback in der Konsole (gültig / abgelaufen / unerreichbar / ungültig).
  • Push-Loop: Snapshot + Events an POST {BridgeUrl}/v1/ingest (Intervall PushIntervalMs, Queue max. 400). HTTP 403 = Abo abgelaufen → pausiert, Standalone läuft weiter.
  • Command-Long-Poll: GET {BridgeUrl}/v1/commands?wait=<PollWaitSec>; führt Web-Kommandos aus (Executors: call.assign/unassign/close/note/create, unit.status/callsign, radio.send, gps.send, bolo.add/remove, sys.presence/resync/idle, config.set).
  • Status-Global DispatchOSWebLink = { configured, linked } fürs Leitstellen-Settings-Panel.

Konfiguration

Alle Optionen aus config.lua (VConfig = {}). Zusätzlich am Ende die hartcodierten Blöcke aus server/main.lua (Discord/Auto-Panik) und Config-Keys, die der Code liest, aber die shipped config.lua nicht enthält (mit „(nicht in config.lua)").

Basis

Key Standard Beschreibung
Framework "auto" "ESX" | "QBCORE" | "STANDALONE" | "auto". Auto: erkennt es_extended, sonst qbx_core/qb-core, sonst Standalone.
UseDuty true Wenn true, werden Off-Duty-Spieler als Nicht-Einheit ignoriert (QBCore & Export-Duty).
Locale "de" Sprache der Server-/HUD-/Leitstellen-Texte ("de" / "en").
JobGroups Tabelle (s. u.) Job → Alarmgruppen. Ein Job kann mehrere Gruppen empfangen.

JobGroups-Default: police = {leo, leo_city, leo_county}, lssd = {leo, leo_county}, fib = {leo, leo_city, leo_county}, justiz = {leo, leo_city, leo_county}, ambulance = {ems}.

MDT

Key Standard Beschreibung
MDT.PanelEnrich true Live-Online-Status (framework-nativ) in Personenakte via EmergencyOS.
MDT.PanelFileLookup false Panel-Akten-Lookup („Akte öffnen") — erst mit verbundener Panel-API aktivieren.
MDT.Enabled true MDT-Modul an/aus (Client & Server prüfen dies).
MDT.Keybind "F7" MDT öffnen/schließen.
MDT.AlprQueryKeybind "F11" Letztes ALPR-Kennzeichen direkt im MDT abfragen.
MDT.LookupBurst 8 Token-Bucket: so viele Abfragen sofort hintereinander.
MDT.LookupRefillMs 1000 Nachschub: 1 Abfrage-Token pro X ms.
MDT.Debug false Konsolen-Trace der MDT-Kette.
MDT.AllowedJobs {police, lssd, fib, justiz = true} Jobs mit MDT-Zugriff.
MDT.DirectDB.Enabled true Personen/Fahrzeuge direkt aus Spiel-DB statt Gateway.
MDT.DirectDB.UsersTable nil Override Users-Tabelle (ESX default users, QB players).
MDT.DirectDB.VehiclesTable nil Override Fahrzeug-Tabelle (ESX owned_vehicles, QB player_vehicles).
MDT.DirectDB.Phone.UsersColumn "phone_number" Telefon-Spalte in der Users-Tabelle.
MDT.DirectDB.Phone.Table "phone_phones" lb-phone-Tabelle (Fallback).
MDT.DirectDB.Phone.PhoneColumn "phone_number" Nummer-Spalte in der Phone-Tabelle.
MDT.DirectDB.Phone.IdentifierColumn "owner_id" Besitzer-Spalte (LIKE-Match).

DispatchCenter

Key Standard Beschreibung
DispatchCenter.Enabled true Leitstelle an/aus.
DispatchCenter.Keybind "F6" Leitstelle öffnen/schließen.
DispatchCenter.BrandName "DISPATCHOS" Header-Branding.
DispatchCenter.BrandSub "LEITSTELLE" Untertitel.
DispatchCenter.FocusGuard true Verhindert Öffnen, wenn anderes NUI den Focus hält.
DispatchCenter.PanicFocus true Auto-Kameraflug bei Panik (10-13).
DispatchCenter.MapIcons Tabelle FontAwesome-Icon je Code, z. B. ["10-71"]="fa-gun", default="fa-location-dot".
DispatchCenter.Map.Image "map/atlas.jpg" Kartenbild.
DispatchCenter.Map.Width/Height 4096 / 4096 Bildmaße.
DispatchCenter.Map.MinX/MaxX -5661.196911 / 6694.015444 Weltkoordinaten-Mapping X.
DispatchCenter.Map.MinY/MaxY -4058.536586 / 8429.268293 Weltkoordinaten-Mapping Y.
DispatchCenter.Map.PosInterval 2500 Positions-Broadcast-Takt (ms; min. 1000 erzwungen).

Statuscodes, Eskalation, Funk

Key Standard Beschreibung
Statuses {"10-8","10-6","10-7","10-11","10-15","10-97"} Gültige Unit-Status-Codes.
Escalation.Enabled true Auto-Höherstufung wartender Einsätze.
Escalation.Steps {[3]=120, [2]=180} Sekunden ohne Einheit je Stufe (P3→P2 nach 120 s, P2→P1 nach 180 s).
Radio.Enabled true Schnellfunk-Modul.
Radio.Keybind "F5" Funk-Panel.
Radio.Cooldown 2000 Anti-Spam (ms) pro Sender.
Radio.Codes Liste 10-Codes; je Eintrag code, label, optional pos=true (Position anhängen), priority=true (roter Alarmton). Codes, die auch in Statuses stehen, setzen den Unit-Status mit.

Radio.Codes-Default: 10-4 Verstanden, 10-8 Einsatzbereit, 10-6 Beschäftigt, 10-97 Am Einsatzort, 10-20 Standort durchgeben (pos), 10-13 Benötige Unterstützung (priority,pos), Code 2, Code 3.

Zones

Key Standard Beschreibung
Zones.Enabled true Zonen-Routing an/aus.
Zones.BaseGroup "leo" Basisgruppe, die auf Zonengruppen aufgefächert wird.
Zones.FallbackToAll true Wenn niemand in der Zonengruppe online: an Basisgruppe zurückfallen (keine unversorgten Einsätze).
Zones.List Liste Von oben nach unten geprüft: name, groups, optional poly (Polygon {x,y}) oder center+radius; Eintrag ohne Shape = Rest der Karte. Default: „Los Santos" (Polygon → leo_city), „Blaine County" (→ leo_county).

Permissions

Jeder Wert: "unit" (jede Einheit) oder { jobs = { <job> = <Mindest-Grade> } }.

Key Standard Aktion
Permissions.CloseCall "unit" Einsatz schließen.
Permissions.AssignUnits "unit" Fremde Einheiten zuweisen / Route senden.
Permissions.CreateCall "unit" Manuellen Einsatz erstellen.
Permissions.ManageBolos "unit" /addbolo / /delbolo.
Permissions.AddNotes "unit" Notizen an Einsätze.

Zeiten, Merge, Blips, Melder

Key Standard Beschreibung
CallLifetime 600 Sekunden, dann Auto-Close durch „SYSTEM".
MergeRadius 60.0 Merge-Radius (Meter) für gleichen mergeKey.
MergeTime 90 Merge-Zeitfenster (Sekunden).
BlipLifetime 120 Blip-Standzeit am Client (Sekunden).
NotifyDuration 15 HUD-Karten-Anzeigedauer (Sekunden).
PersistCalls true Einsätze in DB schreiben.
DBRetentionDays 90 Aufbewahrung; 0 = nie löschen.
Show911Caller true Melder-Name bei /911//912 zeigen (sonst „Anonym").

Keybinds & Panik

Key Standard Beschreibung
RespondKeybind "F9" Auf letzten/gewählten Einsatz reagieren.
PanicKeybind "" Panic-Taste (leer = nur /dispatchpanic / GTA-Belegung).
AutoRoutePanic true Leitet bei Panik automatisch alle Kollegen per GPS hin.
AlprKeybind "F10" ALPR-Radar an/aus.
AlprLockKeybind "NUMPAD8" ALPR-Ziel sperren + 3D-Marker.

SmartPing (3D-Ping)

Key Standard Beschreibung
SmartPing.Enabled true Smart-Ping-System.
SmartPing.Keybind "MOUSE_MIDDLE" Ping-Taste (Mausrad-Klick).
SmartPing.Duration 10 Sichtbarkeit (Sekunden).
SmartPing.MaxDistance 800.0 Max. Sichtweite des Markers.

ALPR & Detektoren

Key Standard Beschreibung
AlprScanDistance 35.0 Raycast-Reichweite (Meter).
AlprScanRear true Auch nach hinten scannen.
AlprScanIntervalMs 400 Raycast-Takt (ms).
IgnoredWeapons Set Waffen, die keinen Schuss-Alarm auslösen (Stungun, Flaregun, Snowball, Ball, Fireextinguisher, Petrolcan).
IgnoreSilenced true Schallgedämpfte Schüsse ignorieren.
DetectorDebug false Konsolen-Trace der Detektoren.

Sounds

Je Ereignis { name, set } (Frontend-Sound) oder false (stumm). Keys mit Defaults: panic (Laster_10A/GTAO_Biker_FM_Soundset), p1/p2 (Beep_Red/DLC_HEIST_HACKING_SNAKE_SOUNDS), p3 (Text_Arrive_Tone/Phone_SoundSet_Default), alprHit, radio, radioPrio, ping (WAYPOINT_SET/HUD_FRONTEND_DEFAULT_SOUNDSET).

AutoCalls

Je Auslöser: enabled, code, labelKey, priority (1–3), groups, meist cooldown (s), ignoreUnits (Einsatzkräfte lösen nicht selbst aus), teils minSpeed, plus blip = { sprite, color, scale }.

Auslöser enabled code priority groups Besonderheit
shots true 10-71 2 leo cooldown 20, ignoreUnits
driveby true 10-71 2 leo cooldown 20, ignoreUnits
carjack true 10-90 3 leo cooldown 30, ignoreUnits
fight false 10-10 3 leo cooldown 30, ignoreUnits
crash true 10-50 2 leo, ems cooldown 30, ignoreUnits
speeding true 10-50 3 leo cooldown 45, minSpeed=160, ignoreUnits
playerdown true 10-11 2 ems, leo cooldown 30, ignoreUnits=false
alpr_hit true 10-99 2 leo
panic true 10-13 1 leo, ems Blip sprite 161, scale 1.5
civ_police true 911 3 leo /911
civ_ems true 912 2 ems /912

Locales

VConfig.Locales.de / .en — vollständige Text-Tabellen (HUD, Leitstelle, MDT, Funk, BOLO, Meldungen). Labels der AutoCalls kommen über labelKey (z. B. lbl_shots, lbl_panic).

Key Standard Beschreibung
WebLink.Token 'dos_6869…' Web-Panel-Token; leer/Platzhalter = Standalone.
WebLink.BridgeUrl 'https://dispatchos.de/bridge' Basis-URL der PHP-Bridge.
WebLink.PushIntervalMs 900 Event-Push-Intervall (ms).
WebLink.PollWaitSec 8 Long-Poll-Wartezeit (PHP-Bridge max. 8; Code erzwingt min. 4).
WebLink.HandshakeRetrySec 30 Retry-Intervall Verbindungsaufbau.
WebLink.IdlePollSec 45 (nicht in config.lua) Poll-Takt bei leerem Server.
WebLink.Runtime (zur Laufzeit gesetzt) Vom Web-Executor config.set befüllt.

Phone

Key Standard Beschreibung
Phone.Enabled true Telefon-Notruf-Modul.
Phone.Companies police={groups={leo},label='Notruf',code='911'}, ambulance={groups={ems},label='Notruf',code='112'} lb-phone-Firma → Alarmgruppen.
Phone.MessagePriority 3 Priorität einer Services-Nachricht mit Standort.
Phone.MissedCallPriority 2 Unbeantworteter Notruf-Anruf.
Phone.CreateOnAnswered false Auch bei angenommenem Anruf Einsatz anlegen.
Phone.AnsweredPriority 3 Priorität bei angenommenem Anruf.
Phone.PushDispatches true Neue Einsätze als lb-phone-Emergency-Alert.
Phone.PushMinPriority 2 Alerts nur ≤ dieser Prio (Panik immer).
Phone.Cooldown 15000 Spam-Schutz je Anrufer/Nummer (ms).

Config-Keys, die der Code liest, aber die config.lua NICHT enthält (Defaults im Code)

Key Fallback Wirkung
VConfig.ArrivedStatus nil Wenn gesetzt: Einheit bei Ankunft automatisch auf diesen Status.
VConfig.ArriveRadius 60.0 Radius (Meter) für Auto-„Angekommen".
VConfig.Sla.Enabled true (≠ false) SLA-Wächter: wartende Einsätze erneut in Erinnerung rufen.
VConfig.Sla.RemindAfter 240 Sekunden bis zur ersten Erinnerung.
VConfig.Sla.RemindEvery 300 Wiederhol-Intervall (Sekunden).

Hartcodierte Blöcke in server/main.lua (nicht in config.lua)

Discord-Feed (local DISCORD): enabled=true, webhook="https://discord.com/api/webhooks/DEINE/URL_HIER" (eintragen), username="Leitstelle", avatar="", postNew=true, postClosed=true, postRadio=false, pingRoleOnPanic="". Postet neue/geschlossene Einsätze (optional Funk/Panik-Ping) über dispatchos:dispatch:broadcast.

Auto-Panik-Zuweisung (local AUTO_PANIC): enabled=true, onlyIfUnassigned=true, notifyUnit=true. Weist bei neuem Panik-Einsatz automatisch die nächste freie On-Duty-Einheit derselben Abteilung zu.


Exports

Insgesamt 23 Exports (README nennt „~21"): 18 Server in server/main.lua, 1 Server in modules/phone/server.lua, 4 Client in client/main.lua. Namen SetPlayerJob und CreateCall existieren doppelt (client- und serverseitig, unterschiedliche Signatur/Wirkung — Kontext beachten).

Aufruf serverseitig: exports.dispatchos:Name(...) bzw. exports['dispatchos']:Name(...). Ressourcenname = Ordnername (dispatchos).

Server-Exports (server/main.lua)

1. CreateCall(data)Seite: server - Signatur: exports.dispatchos:CreateCall(data)data muss data.coords = {x,y,z} enthalten. - Felder (alle optional außer coords): code (≤16, Default "CALL"), label (≤120, Default "Einsatz"), priority (1–3, Default 3), groups (Default {"leo"}, danach Zonen-Routing), street (≤96), plate (≤12), vehName (≤40), details (≤200), sender (≤40), panic (bool), mergeKey, blip = {sprite,color,scale}. - Rückgabe: Call-id (number) — oder bei Merge die bestehende id; nil bei ungültigen Daten. - Beschreibung: Kern-Funktion zum Anlegen eines Einsatzes; führt Merge (gleicher mergeKey im Radius/Zeitfenster), Zonen-Routing, Broadcast an passende Gruppen und DB-Insert aus.

local id = exports.dispatchos:CreateCall({
    code='10-90', label='Fahrzeugraub', priority=2,
    groups={'leo'}, coords={x=215.3,y=-810.1,z=30.0},
    street='Legion Square', mergeKey='mycustom'
})

2. CloseCall(id, closedBy)server · Signatur (id, closedBy)bool. Schließt Einsatz, schreibt closed_at/reports/units, informiert Gruppen. closedBy = Anzeigename im Feed.

exports.dispatchos:CloseCall(42, 'ADMIN')

3. AttachUnit(id, src)server · (callId, playerSrc)bool. Weist Einheit zu (setzt Waypoint, Status active, responded_at). Nur wenn Einheit die Gruppe teilt.

4. DetachUnit(id, src)server · (callId, playerSrc)bool. Entfernt Einheit; ohne verbleibende Einheiten → Status pending.

5. AddDispatchCall(code, label, details, coords, priority, groups, blip, mergeKey, plate, vehName, sender)server · Positionsargumente-Wrapper um CreateCall → gibt id zurück. priority Default 3, groups Default {"leo"}, sender Default "System".

local id = exports.dispatchos:AddDispatchCall(
  '10-31','Alarm',nil,{x=1.0,y=2.0,z=3.0},1,{'leo'},60)

6. Alarm(a)server · Öffentliche Alarm-API → id oder nil. a.type: 'ladenraub' (10-90, P2), 'bankraub' (10-90, P1), 'gefaengnisausbruch' (10-98, P1) oder 'custom'. Weitere Felder: coords, code, label, details, priority, groups (Default {'leo'}), blip, mergeKey, sender. Auch via Event TriggerEvent('dispatchos:alarm', a).

local id = exports.dispatchos:Alarm({
  type='custom', code='10-31', label='Juwelier-Alarm',
  coords=coords, priority=1, groups={'leo'}, blip=617 })

7. GetCall(id)server · → öffentliche Call-Tabelle (id, code, label, priority, coords, street, units, history, …) oder nil.

8. GetActiveCalls()server · → Array aller aktiven Einsätze (öffentliche Form), nach id absteigend sortiert.

9. GetUnits()server · → Array aller On-Duty-Einheiten {src, name, callsign, status, job}.

10. AddWantedPlate(p, reason)server · Setzt lokale Fahndung (BOLO) für Kennzeichen; reason ≤90. Pusht an Leitstellen-Viewer. Keine Rückgabe.

exports.dispatchos:AddWantedPlate('ABC123', 'Fahrerflucht')

11. RemoveWantedPlate(p)server · Entfernt lokale Fahndung. Keine Rückgabe.

12. SetPlayerJob(src, job)server · Setzt Job im STANDALONE-Tracker (StandaloneJobs[src]). Für Server ohne ESX/QBCore.

exports.dispatchos:SetPlayerJob(source, 'police')

13. SetPlayerDuty(src, state)server · Setzt Duty-Flag (DutyStates[src]). Wirkt mit UseDuty=true.

14. LstSetStatus(src, st)server · Web-Leitstand-Wrapper → bool. Setzt Unit-Status (validiert gegen Statuses).

15. LstSetCallsign(src, cs)server · → true. Setzt Funkrufname.

16. LstGps(src, x, y)server · → bool. Sendet Waypoint an Einheit.

17. LstAddNote(id, who, text)server · → bool. Fügt Notiz (≤140) an Einsatz-Timeline.

18. LstRadio(who, code, text)server · → bool. Sendet Funkspruch an alle Einheiten.

14–18 („Lst…") sind bewusst schlanke Wrapper für das Web-Link-Modul, funktionieren aber auch direkt.

Server-Export (modules/phone/server.lua)

19. PhoneEmergencyCall(data)server · Generische Notruf-API für beliebige Telefon-Scripts → callId oder nil. - Felder: x, y (Pflicht), z, code, label, details (≤140), priority (1–3), group ('leo'|'ems'|'both') oder groups, sender (≤24) oder anonymous=true. Eingebauter Cooldown pro Sender (VConfig.Phone.Cooldown), automatischer mergeKey='phone:<sender>'.

local id = exports.dispatchos:PhoneEmergencyCall({
  x=215.3, y=-810.1, details='Bewaffneter Überfall',
  sender='555-0123', group='leo', priority=2, code='911', label='Notruf' })

Client-Exports (client/main.lua)

20. CreateCall(data)client · Third-Party-Integration. Ergänzt fehlende data.coords mit der Spielerposition und feuert TriggerServerEvent('dispatchos:server:CreateExternalCall', data). Keine Rückgabe (asynchron). Serverseitig gilt für echte Spieler: On-Duty-Pflicht, Rate-Limit, panic wird verworfen.

exports.dispatchos:CreateCall({ code='911', label='Notruf', priority=2 })

21. SetPlayerJob(job)client · Setzt DispatchOSJob lokal (für Standalone/eigene Job-Logik). Keine Rückgabe.

22. SetDuty(state)client · Setzt DispatchOSOnDuty lokal. Keine Rückgabe. (Beachte: Export heißt client SetDuty, server SetPlayerDuty.)

23. TriggerPanic()client · Löst den Panic-Button aus (ExecuteCommand('dispatchpanic')). Keine Rückgabe.

exports.dispatchos:TriggerPanic()

Events & Commands

Öffentliche/Integrations-Events (für eigene Scripts relevant)

  • TriggerEvent('dispatchos:alarm', { type=..., coords=..., details=... }) — serverseitig, Alarm auslösen (Pendant zu Export Alarm).
  • TriggerEvent('dispatchos:server:CreateExternalCall', data) — serverseitig (source 0 erlaubt) Einsatz anlegen; von Spieler-Clients gesendet gilt On-Duty + Rate-Limit, panic entfernt.
  • dispatchos:dispatch:broadcast — internes Feed-Event ({action='new'|'closed'|'update'|'unit'|'radio', …}); Anknüpfpunkt für Discord, lb-phone-Push, Web-Link, EmergencyOS-Atlas und Auto-Panik. Nicht von Clients auslösbar.
  • dispatchos:client:FocusCall(callId) — clientlokal, öffnet Leitstelle mit Kamera-Flug (Atlas-Integration).
  • Gehörte Fremd-Events: EmergencyOS:PanicButton, EmergencyOS:WantedsUpdated, lb-phone:newCompanyMessage, lb-phone:callEnded. Ausgelöst: EmergencyOS:ForceOpenRecord.

Client-seitige Server-Events (Auszug, spielergebunden, mit IsUnit/Cooldown/Permission-Guards)

dispatchos:server:WhoAmI, RespondCall, MarkArrived, RequestDispatchState, GetStatsFull, DcViewer, CloseCallReq, LeaveCallReq, SetStatus, SetCallsign, SendRoute (Perm AssignUnits), RadioSend, AddNote (Perm AddNotes), ManualCall (Perm CreateCall), AssignUnit/UnassignUnit (Perm AssignUnits), AutoCall, PanicButton, AlprScan, ReportPos, RequestBackup, SmartPing, SyncDraw/ClearBoard (Whiteboard). MDT: dispatchos:mdt:LookupPlate, SearchPerson, PersonDetail.

Commands & Standard-Keybinds

Taste* Command Funktion
F5 /dispatchosfunk Schnellfunk-Panel
F6 /dispatchcenter Leitstelle (Vollbild)
F7 /dispatchosmdt MDT
F9 /respondcall [id] Auf letzten/gewählten Einsatz reagieren
F10 /alprradar ALPR-Scanner an/aus
F11 /mdt_query_alpr Letztes ALPR-Kennzeichen im MDT
NUMPAD8 /alprlock ALPR-Ziel sperren/entsperren
MOUSE_MIDDLE /dispatchosping Smart-3D-Ping
/dispatchpanic Panic Button (PanicKeybind)
/dispatchhud HUD-Karten per Drag & Drop positionieren
↓ / ↑ /dispatch_hud_next · /dispatch_hud_prev HUD: nächster/voriger Einsatz
DELETE /dispatch_hud_pin HUD anpinnen/lösen
(frei) /dispatch_hud_gps GPS zum gewählten Einsatz
END /dispatch_hud_backup Backup für gewählten Einsatz
/status <code> · /callsign <kürzel> Unit-Status / Funkrufname
/closecall [id] · /leavecall Einsatz schließen / verlassen
/911 <text> · /912 <text> Zivil-Notruf Polizei / Rettungsdienst
/addbolo <kz> [grund] · /delbolo <kz> Lokale Fahndung (Perm ManageBolos)
/bolodebug BOLO-Quelle & Cache-Diagnose
/dcdiag Leitstellen-Selbsttest (warum F6 ggf. nicht öffnet)

* Alle Tasten in config.lua und pro Spieler unter GTA-Einstellungen → Tastenbelegung → FiveM änderbar.


Hinweise / Troubleshooting

  • Einsatzkräfte lösen keine Auto-Alarme aus → Absicht: ignoreUnits = true (Produktions-Default). Zum Testen im jeweiligen AutoCalls-Eintrag auf false.
  • fight ist standardmäßig aus (enabled=false) — bei Bedarf aktivieren.
  • Kein Blip/keine Karte für einen Job → Job in JobGroups eintragen und Zonengruppen (leo_city/leo_county) prüfen.
  • F6 öffnet nicht → anderes NUI hält den Focus (FocusGuard). /dcdiag zeigt Job/Unit/Focus-Status; notfalls DispatchCenter.FocusGuard = false.
  • „Panel nicht erreichbar" im MDTMDT.PanelFileLookup erst nach eingerichteter EmergencyOS-Panel-API aktivieren; ohne DirectDB und ohne EmergencyOS liefert das MDT nur BOLO-Fahndung.
  • Diagnose-SchalterVConfig.DetectorDebug = true (Detektoren) bzw. VConfig.MDT.Debug = true (MDT-Kette; Default bereits „an", da Debug ~= false).
  • Ohne oxmysql → Konsolenhinweis „Calls werden nicht persistiert (Heatmap bleibt leer)"; Statistik fällt auf In-Memory (seit Serverstart) zurück, Kern bleibt voll funktionsfähig.
  • Standalone-Job setzen → ohne ESX/QBCore Job/Duty über die Exports SetPlayerJob/SetPlayerDuty (Server) bzw. SetPlayerJob/SetDuty (Client) pflegen, sonst zählt niemand als Einheit.
  • Discord-Feed ist im Code aktiviert (enabled=true), postet aber nur mit gültiger webhook-URL (sonst still verworfen).
  • Sounds reminder/arrived werden clientseitig abgespielt, sind aber in VConfig.Sounds nicht definiert → bleiben stumm, bis ergänzt.

LicenseOS — Entwickler-Dokumentation

Ressourcen-Ordner: licenseos · fxmanifest-Version 1.0.0 · Frameworks: ESX · QBCore · Qbox · ox_core · Standalone


Überblick

LicenseOS ist ein Führerschein- und Fahrschulsystem für FiveM. Die eigentliche Fach-Logik (Lizenzkatalog, Prüfungsbögen, Preise, Fahrstunden-Vorgaben) läuft in einem externen Web-Panel (https://licenseos.app). Die hier dokumentierte FiveM-Ressource ist die Server-Bridge + NUI-Anbindung zu diesem Panel — sie enthält selbst keine Prüfungsinhalte, sondern verbindet Spielserver und Panel.

Die Ressource leistet vier Dinge:

  1. Panel ingame öffnen — das Web-Panel wird als NUI-iframe geladen (Muster: EmergencyOS-Tablet). Fahrlehrer arbeiten ingame mit demselben Panel wie im Browser, per Web-Login. Die Berechtigung wird serverseitig geprüft.
  2. Server-Bridge — Handshake ans Panel, plus ein eingehender Push-Endpunkt, über den das Panel Lizenz-Änderungen live an den Spielserver meldet.
  3. Exports — Lizenz-Checks (HasLicense, GetLicenses, …) für eigene Scripts, mit RAM-Cache und optionalem lokalem DB-Cache.
  4. Framework-Sync — Lizenz-Änderungen aus dem Panel werden live ins Framework gespiegelt (ESX user_licenses, QB/Qbox metadata.licences, ox_core Charakter-Metadaten).

Lizenzarten (aus Config.LicenseMapping)

Panel-Klasse Bedeutung Framework-Lizenz
A1, A Motorrad drive_bike
B, BE Auto (PKW / mit Anhänger) drive
C, CE, D LKW / Bus drive_truck
BOOT Boot boat
FLUG Flugzeug / Pilot pilot
WS Waffenschein weapon

Die Klassen-Namen (A1, B, FLUG …) stammen aus dem Panel; die Mapping-Tabelle ist frei erweiterbar (siehe Konfiguration).

Prüfungen & Vorgänge

  • Theorieprüfung — Fahrlehrer starten per /theorie <klasse> eine Gruppenprüfung; alle Spieler im Umkreis (Config.TheoryRadius) bekommen den Prüfungsbogen automatisch als Panel-Overlay eingeblendet.
  • Fahrstunde — Fahrlehrer tragen per /fahrstunde <id> <minuten> [notiz] Fahrstunden ein; das Panel zählt Richtung eines Soll-Werts (totalMinutes/required).
  • Praktische Prüfung / Lizenzvergabe / Gebühren — werden im Web-Panel abgewickelt.
  • Bürger-Abfrage — Spieler rufen per /lizenzen ihre Lizenzen, Punkte, Gültigkeit und offenen Gebühren im Chat ab.

Lizenz-Status

aktiv · gesperrt · entzogen · abgelaufen (werden in Benachrichtigungen und im licenseos:licenseUpdate-Event verwendet).


Voraussetzungen

fxmanifest.lua:

Element Wert
fx_version cerulean
game gta5
lua54 yes
ui_page html/wrapper.html
dependency /assetpacks
  • Harte Abhängigkeit: nur /assetpacks.
  • Framework: ESX (es_extended), QBCore (qb-core), Qbox (qbx_core) oder ox_core (ox_core) — automatisch erkannt, keine Anpassung nötig. Auch Standalone (ohne Framework) läuft; dann Identifier license:….
  • Datenbank / oxmysql: optional, aber empfohlen. Wird per GetResourceState('oxmysql') erkannt. Ohne oxmysql schaltet sich der lokale Lizenz-Cache automatisch ab (dann nur RAM-Cache + Live-API); die Offline-Spielersuche entfällt ebenfalls.
  • Web-Panel-Account mit erzeugtem API-Key (Panel → Einstellungen → FiveM-Bridge).
  • Netzwerk: Der Spielserver-HTTP-Port (Convar netPort, Standard 30120) muss vom Webserver aus per HTTP erreichbar sein, damit Panel-Pushes ankommen. Exports & Befehle (Richtung Spiel → Panel) funktionieren auch ohne eingehende Erreichbarkeit.
  • Optionale Notify-Ressourcen: okokNotify oder ox_lib (sonst Chat-Fallback).

Installation

  1. Ordner als licenseos in resources/ ablegen.
  2. Im Web-Panel einen API-Key erzeugen (Einstellungen → FiveM-Bridge, als Inhaber/Admin) und in config.lua eintragen: lua Config.PanelURL = 'https://licenseos.app' -- OHNE abschließenden / Config.ApiKey = 'los_...'
  3. In die server.cfg eintragen: cfg ensure licenseos
  4. Server starten. Beim Start meldet sich der Server am Panel an (/bridge/getInstance.php) und registriert automatisch den Push-Rückkanal (IP/Port/Resource) — es muss keine Server-IP im Panel eingetragen werden.

Reihenfolge: oxmysql und das genutzte Framework sollten vor licenseos gestartet sein. Die Ressource wartet aber nach: bei Config.Framework = 'auto' bis zu 10 s auf ein Framework, bei erzwungenem Framework bis zu 30 s (und weicht dann nie still auf ein anderes aus, sondern auf Standalone).

Berechtigungen setzen (server.cfg):

# Panel ingame öffnen (zusätzlich zu Config.AllowedJobs):
add_ace group.admin licenseos.open allow

# Fahrlehrer-Befehle /fahrstunde und /theorie:
add_ace group.fahrlehrer licenseos.instructor allow
add_principal identifier.license:DEINE_LICENSE group.fahrlehrer

Panel-Bezug: Das Panel wird ingame über /licenseos bzw. Taste F7 geöffnet und als iframe von Config.PanelURL geladen. Login-Daten werden nur lokal auf dem PC des Spielers gecacht (SetResourceKvp), nie auf dem Server.


Datenbank

Die Ressource legt eine eigene Tabelle an, sofern Config.LocalLicenseCache = true und oxmysql läuft. Die Tabelle wird automatisch beim ersten erfolgreichen Handshake erstellt (kein manuelles SQL-Import nötig):

CREATE TABLE IF NOT EXISTS licenseos_licenses (
    license_no VARCHAR(32) PRIMARY KEY,
    identifier VARCHAR(80) NOT NULL,
    owner_name VARCHAR(60) NOT NULL DEFAULT '',
    class      VARCHAR(6)  NOT NULL,
    status     VARCHAR(12) NOT NULL,
    points     INT NOT NULL DEFAULT 0,
    expires_at DATE DEFAULT NULL,
    INDEX idx_lic_ident (identifier, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  • Master bleibt das Panel. Diese Tabelle ist nur ein lokaler Spiegel: Voll-Sync beim Start (DELETE + Neu-Einfügen), Delta-Sync alle 15 Min (?since=…), plus Live-Pushes. Datenquelle: PanelURL/bridge/export.php.
  • Vorteil: HasLicense/GetLicenses laufen ohne Internet-Roundtrip; der Server bleibt bei Panel-Wartung spielbar.
  • Zusätzlich genutzte (Framework-)Tabellen — nur lesend für die Spielersuche bzw. schreibend für den Sync, werden nicht von dieser Ressource angelegt:
  • ESX: users (Suche), user_licenses (Lizenz-Spiegel)
  • QBCore/Qbox: players (Suche), metadata.licences (Spiegel, über Player-API)
  • ox_core: characters (Suche), Charakter-Metadaten licenseos:<fwLicense> (Spiegel)

Sync / Panel-Anbindung

Die Bridge kommuniziert in beide Richtungen über HTTP-Endpunkte unterhalb von Config.PanelURL. Authentifizierung durchgehend per Api-Key-Header (bzw. Authorization beim eingehenden Endpunkt), zeitkonstant verglichen.

Ausgehend (Spiel → Panel)

Endpunkt Methode Zweck Antwort (relevant)
/bridge/getInstance.php GET Handshake; sendet Api-Key, Resource-Name, Server-Port. Registriert Push-Rückkanal. Re-Handshake stündlich. Community-ID (Zahl)
/bridge/export.php GET Lizenz-Voll-/Delta-Sync (?since=…) für den lokalen Cache. { ok, licenses[], serverTime, count }
/bridge/licenses.php?identifier=… GET Lizenzen eines Spielers (für Live-Export & /lizenzen). { ok, licenses[], openFees }
/bridge/lessons.php POST Fahrstunde eintragen (/fahrstunde). { ok, required, totalMinutes, error? }
/bridge/theory.php POST Theorieprüfung starten (/theorie). { ok, url, code, error? }

Eingehend (Panel → Spiel, SetHttpHandler auf Port netPort)

Abgesichert durch: zeitkonstanten Key-Vergleich (SecureCompare), Rate-Limit (Config.ApiRateLimit/10 s), Body-Limit (Config.ApiMaxBodyBytes), 15-s-Watchdog-Timeout (504).

Aktion (Pfad) Zweck
ping Erreichbarkeitstest → { ok, resource }
refresh RAM-Cache leeren
searchPlayers Live-Spielersuche (Online-Spieler + Framework-DB), Rückgabe { identifier, name }[], max. 25 Treffer
licenseUpdate Lizenz-Änderung übernehmen: RAM-Cache invalidieren, lokalen Cache aktualisieren, Framework spiegeln, Spieler benachrichtigen, Event feuern

Datenschutz by Design: Es gibt keinen automatischen Bürger-Sync beim Joinen. Spieler werden nie pauschal ans Panel übertragen; die Suche läuft live, gespeichert wird ein Spieler erst bei einem echten Vorgang (Lizenz, Prüfung, Fahrstunde).

Lizenz vergeben/entziehen erfolgt im Panel (bzw. wird per licenseUpdate an den Server gepusht) — dafür gibt es bewusst keinen Export. Die Ressourcen-Exports sind lesend (siehe unten).


Konfiguration

Vollständige config.lua. Alle Werte sind die echten Defaults.

Key Standard Beschreibung
Config.PanelURL 'https://licenseos.app' Basis-URL des Web-Panels, ohne abschließenden /. Grundlage aller Bridge-Endpunkte und des iframe.
Config.ApiKey 'los_HIER_DEN_KEY_EINFUEGEN' API-Key aus dem Panel (Einstellungen → FiveM-Bridge). Solange der Platzhalter oder leer bleibt, ist die Bridge inaktiv.
Config.Command 'licenseos' Chat-Befehl zum Öffnen des Panels ingame.
Config.Keybind 'F7' Standard-Tastenbelegung (RegisterKeyMapping). '' = kein Keybind.
Config.AllowedJobs { ['fahrschule']=0, ['police']=1 } Job/Gruppe → Mindest-Grade, ab dem das Panel geöffnet werden darf. Bei ox_core sind das Gruppennamen.
Config.AcePermission 'licenseos.open' Ace-Permission, die das Öffnen immer erlaubt (zusätzlich zu AllowedJobs).
Config.Framework 'auto' Framework-Wahl. 'auto' erkennt selbst; erzwingbar: 'esx' / 'qbcore' / 'qbox' / 'ox' / 'standalone'.
Config.SyncFramework true Spiegelt Lizenz-Änderungen aus dem Panel live ins Framework (siehe LicenseMapping).
Config.LicenseMapping (Tabelle, s. u.) Panel-Klasse → Framework-Lizenzname (ESX type / QB metadata-Key).
Config.NotifyPlayer true Informiert den betroffenen Spieler ingame bei Lizenz-Änderung (okokNotify / ox_lib / Chat).
Config.LessonCommand 'fahrstunde' Fahrlehrer-Befehl zum Eintragen einer Fahrstunde. '' = deaktiviert.
Config.TheoryCommand 'theorie' Fahrlehrer-Befehl zum Start einer Gruppen-Theorieprüfung. '' = deaktiviert.
Config.TheoryRadius 25.0 Umkreis in Metern, in dem Spieler zur Theorieprüfung eingeladen werden.
Config.LocalLicenseCache true Spiegelt alle Lizenzen in die lokale Tabelle licenseos_licenses (benötigt oxmysql). Ohne oxmysql automatisch aus.
Config.CitizenCommand 'lizenzen' Bürger-Befehl: eigene Lizenzen/Punkte/Gebühren im Chat. '' = deaktiviert (5 s Cooldown).
Config.CacheTTL 90000 Gültigkeit des HasLicense-RAM-Caches in ms (90 s).
Config.ApiRateLimit 120 Max. eingehende Push-Requests pro Adresse je 10 s.
Config.ApiMaxBodyBytes 262144 Max. Body-Größe eingehender Pushes in Bytes (256 KB).

Config.AllowedJobs — Job → Mindest-Grade

Key Standard Beschreibung
['fahrschule'] 0 Job „fahrschule" darf ab Grade 0 öffnen.
['police'] 1 Job „police" darf ab Grade 1 öffnen.

Erweitern: einfach weitere ['jobname'] = mindestGrade ergänzen. Bei ox_core werden die Keys als Gruppennamen gegen player.getGroup(name) geprüft.

Config.LicenseMapping — Panel-Klasse → Framework-Lizenz

Key Standard Beschreibung
['A1'] 'drive_bike' Leichtkraftrad → Motorrad-Lizenz
['A'] 'drive_bike' Motorrad
['B'] 'drive' PKW
['BE'] 'drive' PKW mit Anhänger
['C'] 'drive_truck' LKW
['CE'] 'drive_truck' LKW mit Anhänger
['D'] 'drive_truck' Bus
['BOOT'] 'boat' Boot
['FLUG'] 'pilot' Flugzeug
['WS'] 'weapon' Waffenschein

Mehrere Panel-Klassen dürfen auf dieselbe Framework-Lizenz zeigen (z. B. B und BEdrive). Beim Entzug entfernt die Bridge die Framework-Lizenz nur, wenn keine andere aktive Panel-Klasse dasselbe Mapping benötigt.

Was wo konfiguriert wird — wichtig

  • Lizenzarten (Katalog), Prüfungsinhalte, Preise/Gebühren werden im Web-Panel gepflegt, nicht in config.lua. In der Ressource legt man nur fest, wie diese ins Spiel wirken:
  • Lizenzen: über Config.LicenseMapping (welche Panel-Klasse welche Framework-Lizenz setzt) und Config.SyncFramework.
  • Prüfungen: über Config.TheoryCommand und Config.TheoryRadius (Start & Einladungsradius der Theorieprüfung); Bögen/Bestehensgrenzen/praktische Prüfung liegen im Panel.
  • Preise/Gebühren: rein im Panel; die Ressource zeigt nur die vom Panel gelieferten openFees (/lizenzen) an.

Exports

Alle vier Exports sind serverseitig (in server/main.lua). Aufruf aus Server-Scripts über exports.licenseos:…. Als identifier ist der Framework-Identifier zu übergeben: ESX identifier, QBCore/Qbox citizenid, ox_core stateId, Standalone license:….

1. GetCommunityId

  • Seite: Server
  • Signatur: exports.licenseos:GetCommunityId()
  • Parameter: keine
  • Rückgabe: number | nil — die Community-ID nach erfolgreichem Handshake, sonst nil (noch nicht verbunden).
  • Beschreibung: Liefert die vom Panel vergebene Community-ID. Nützlich für Support-Anfragen und zur Prüfung, ob die Bridge verbunden ist.
local id = exports.licenseos:GetCommunityId()
print(id and ('Verbunden, Community ' .. id) or 'Bridge noch nicht verbunden')

2. GetLicenses

  • Seite: Server
  • Signatur: exports.licenseos:GetLicenses(identifier, cb)
  • Parameter: identifier (string), cb (function) — wird mit dem Ergebnis aufgerufen.
  • Rückgabe (via Callback): Array aktiver Lizenzen; jeder Eintrag: { class, license_no, status, points, expires_at }.
  • Beschreibung: Holt die aktiven Lizenzen eines Spielers. Reihenfolge der Quellen: lokale Tabelle (falls Cache aktiv & bereit) → RAM-Cache (< CacheTTL) → Live-API (/bridge/licenses.php). Kein Callback-Typ = No-Op.
exports.licenseos:GetLicenses(identifier, function(licenses)
    for _, l in ipairs(licenses) do
        print(l.class, l.license_no, l.points, l.expires_at)
    end
end)

3. HasLicense

  • Seite: Server
  • Signatur: exports.licenseos:HasLicense(identifier, class, cb)
  • Parameter: identifier (string), class (string, z. B. 'B'), cb (function(has)).
  • Rückgabe (via Callback): boolean — ob eine aktive Lizenz der Klasse existiert.
  • Beschreibung: Async-Check (empfohlen für normale Wege). Nutzt intern GetLicenses und damit denselben Cache.
-- Beispiel: Fahrzeug nur bei gültiger PKW-Lizenz freigeben
exports.licenseos:HasLicense(identifier, 'B', function(has)
    if not has then
        -- z. B. Motor sperren, Strafe, Hinweis ...
        TriggerClientEvent('ox_lib:notify', src, { description = 'Kein Führerschein Klasse B!' })
    end
end)

4. HasLicenseCached

  • Seite: Server
  • Signatur: local has = exports.licenseos:HasLicenseCached(identifier, class)
  • Parameter: identifier (string), class (string).
  • Rückgabe: booleansynchron, nur aus dem RAM-Cache. false, wenn für den Spieler noch kein HasLicense/GetLicenses lief (Cache leer).
  • Beschreibung: Kein HTTP, für Hot-Paths (z. B. jeden Frame / beim Einsteigen). Voraussetzung: der Spieler wurde vorher einmal geladen.
-- Hot-Path: vorher einmal HasLicense/GetLicenses aufrufen, um den Cache zu füllen
if not exports.licenseos:HasLicenseCached(identifier, 'C') then
    -- LKW-Lizenz fehlt (nach Cache-Stand)
end

Lizenz geben/entziehen: dafür existiert kein Export. Das geschieht im Panel und wird per licenseUpdate-Push an den Server übernommen — koppelbar über das Event licenseos:licenseUpdate (siehe unten).


Events & Commands

Events

Event Seite / Typ Signatur Beschreibung
licenseos:licenseUpdate Server / AddEventHandler (identifier, class, status, data) Feuert bei jeder vom Panel gepushten Lizenz-Änderung. status: 'aktiv' / 'gesperrt' / 'entzogen' / 'abgelaufen'. Zum Koppeln eigener Systeme.
licenseos:requestOpen Server / RegisterNetEvent () Vom Client ausgelöst; prüft serverseitig HasAccess und öffnet bei Erfolg das Panel. Intern — nicht manuell missbrauchen.
licenseos:openPanel Client / RegisterNetEvent (url) Öffnet das NUI-iframe mit der übergebenen URL (auch für Theorie-Einladungen).
licenseos:denied Client / RegisterNetEvent (reason) Wird bei fehlender Berechtigung gefeuert (Print im Client).
-- Eigene Systeme an Lizenz-Änderungen koppeln:
AddEventHandler('licenseos:licenseUpdate', function(identifier, class, status, data)
    if status == 'entzogen' and class == 'B' then
        -- z. B. Fahrzeugschlüssel sperren
    end
end)

NUI-Callbacks (Client, intern): close, saveLogin, getLogin, clearLogin — verwalten den lokalen Login-Cache (KVP) und das Schließen.

Commands

Befehl (Default) Config-Key Wer Nutzung / Wirkung
/licenseos Config.Command Jobs aus AllowedJobs oder Ace licenseos.open Öffnet das Panel ingame (Berechtigung serverseitig). Auch per Keybind F7.
/fahrstunde Config.LessonCommand Ace licenseos.instructor /fahrstunde <spieler-id> <minuten> [notiz] — trägt eine Fahrstunde ans Panel ein; Ziel-Spieler muss online sein.
/theorie Config.TheoryCommand Ace licenseos.instructor /theorie <klasse> — startet eine Gruppen-Theorieprüfung; alle Spieler im Umkreis (TheoryRadius) bekommen den Bogen als Overlay; Fahrlehrer erhält Prüfungs-Code.
/lizenzen Config.CitizenCommand alle Spieler Zeigt eigene Lizenzen, Punkte, Gültigkeit und offene Gebühren im Chat (5 s Cooldown).

Hinweise / Troubleshooting

  • „Kein API-Key in config.lua hinterlegt — Bridge inaktiv."Config.ApiKey ist leer oder noch der Platzhalter los_HIER_DEN_KEY_EINFUEGEN. Echten Key aus Panel → Einstellungen → FiveM-Bridge eintragen.
  • HTTP 401 beim Handshake → falscher/abgelaufener API-Key. Panel-Key prüfen bzw. neu erzeugen.
  • Panel-Pushes kommen nicht an (Lizenzänderungen erscheinen ingame nicht sofort) → der Spielserver-Port (netPort, Standard 30120) ist vom Webserver aus nicht erreichbar. Firewall/Portfreigabe prüfen. Exports/Befehle (Spiel → Panel) laufen davon unabhängig.
  • Lokaler Cache bleibt aus („oxmysql nicht gestartet") → oxmysql wird nicht vor licenseos gestartet. ensure oxmysql vor ensure licenseos setzen. Ohne oxmysql läuft die Ressource mit RAM-Cache + Live-API weiter, aber ohne Offline-Suche.
  • Falsches Framework erkanntConfig.Framework explizit setzen. Erzwungenes Framework wartet bis zu 30 s auf die Ressource und weicht nie still auf ein anderes aus (fällt sonst auf Standalone). Bei 'auto' wird qbx_core vor qb-core geprüft (Qbox liefert eine qb-core-Bridge mit).
  • HasLicenseCached liefert false, obwohl Lizenz vorhanden → RAM-Cache für den Spieler noch leer. Vorher einmal HasLicense/GetLicenses aufrufen (oder lokalen Cache aktiv lassen).
  • Panel öffnet nicht / „keine Berechtigung" → weder passender Job-Grade in AllowedJobs noch Ace licenseos.open. Berechtigung wird ausschließlich serverseitig geprüft.
  • Fahrlehrer-Befehle reagieren nicht → Ace licenseos.instructor fehlt (add_ace + add_principal in server.cfg).
  • Datenschutz: Kein automatischer Bürger-Sync beim Join — gewollt. Spieler werden erst bei einem echten Vorgang im Panel gespeichert.

Ancomox Garage — Entwickler-Dokumentation

Überblick

ancomox_garage (Handelsname „Zenith Glass Edition Garage") ist ein premium Multi-Framework-Garagensystem für FiveM. Es verwaltet das Ein- und Ausparken von Fahrzeugen über ein modernes Glass-NUI mit 3D-Fahrzeugvorschau und deckt weit mehr als eine klassische Garage ab.

Kernfunktionen - Öffentliche & Job-Garagen (statisch aus Config oder dynamisch per In-Game-Creator in der DB) - Fahrzeug einparken/ausparken, umbenennen, Schlüssel teilen (2 Stufen: Drive / Drive & Park) - Impound (job-basiert, Gebühr, Sperrzeit) + Verwahrstelle/Impound-Lot (Auslösung) + Schrottplatz für „verlorene" Fahrzeuge - Private, kaufbare Garagen in 3 Vanilla-GTA-Interiors (2/6/10 Stellplätze, kein MLO nötig, eigene Routing-Bucket-Instanz) - Vehicle Transfer (Besitzübertragung zwischen Spielern, optional gegen Bezahlung) - Valet (eingelagertes Fahrzeug liefern lassen), Admin-Fahrzeug-Panel, Housing-Bridge - Fahrzeug-Lock (Keymapping), GPS-Tracking, Fuel-Bridges, optionale ox_target/qb-target-Integration - Discord-Webhook-Logs für alle relevanten Aktionen

Autor: Ancomox | Emergency OS


Voraussetzungen

Anforderung Wert / Beschreibung
fx_version cerulean
game gta5
Lua lua54 'yes' (Lua 5.4 erforderlich)
Pflicht-Dependency oxmysql (dependency 'oxmysql' in fxmanifest)
Framework ESX (es_extended), QBCore (qb-core), QBox (qbx_core) oder Custom/Standalone — Auswahl über Config.Framework (Default "auto" erkennt die drei Standard-Frameworks selbst)
Datenbank MySQL/MariaDB via oxmysql (Tabellen werden automatisch angelegt/migriert, siehe unten)
OneSync Nur nötig, wenn Config.ServerSpawn.Enabled = true (server-autoritatives Spawnen, Anti-Dupe)
Optional ox_lib (empfohlen für Menüs/TextUI), ox_target/qb-target (Targeting), Fuel-Script (ox_fuel/legacyfuel/ps-fuel/rcore_fuel), Vehiclekeys-Script

Installation

  1. Ordner ancomox_garage nach resources/ (bzw. [ANCOMOX]/) kopieren.
  2. In der server.cfg ensure oxmysql VOR ensure ancomox_garage setzen (Reihenfolge ist wichtig).
  3. In config/config.lua Config.Framework auf "auto" lassen (erkennt ESX/QBCore/QBox) oder fest auf "esx", "qb" bzw. "custom" setzen.
  4. Server starten — es muss KEINE SQL-Datei importiert werden, das Script legt seine Tabellen beim ersten Start selbst an und ergänzt fehlende Fahrzeug-Spalten automatisch.

Empfohlene server.cfg-Reihenfolge:

ensure oxmysql
# ggf. Framework, ox_lib, target, fuel ...
ensure ancomox_garage

Datenbank

Es ist kein manueller SQL-Import nötig. Bei MySQL.ready werden folgende Tabellen per CREATE TABLE IF NOT EXISTS angelegt und bestehende Fahrzeugtabellen migriert (EnsureColumnExists):

Tabelle Zweck Wichtige Spalten
ancomox_garages Creator-/DB-Garagen (nur bei UseDatabaseGarages = true) id, name, type (public/job), job_name (JSON-Array), loc_x/y/z/w, spawn_x/y/z/w, npc_hash, blip_sprite, blip_color, vehicle_types (CSV), vehicle_classes (CSV), society (0/1), min_grade
ancomox_garage_keys Geteilte Fahrzeugschlüssel plate, receiver_identifier, access_level (UNIQUE plate,receiver)
ancomox_settings Globale Runtime-Settings aus dem Creator (Key/Value) skey, sval (DB-Wert hat Vorrang vor Config)
ancomox_private_garages Besitz privater Garagen id, garage_key (UNIQUE), owner, price, created, interior_override
ancomox_private_garage_vehicles Zuordnung Kennzeichen → private Garage plate (PK), private_garage_id
ancomox_private_garage_members Mitbenutzer privater Garagen private_garage_id, member, member_name, access_level (1–3)
ancomox_private_garage_presence Merkt sich, wer in welcher Garage ausgeloggt ist (Logout-in-Garage-Fix v2.11) identifier (PK), private_garage_id, garage_key, ent_x/y/z/w
ancomox_private_locations In-Game erstellte private Standorte (Creator-Tab) id, label, interior, price, ent_x/y/z/w, spawn_x/y/z/w

Automatisch ergänzte Spalten in deiner Fahrzeugtabelle (Config.CustomFramework.VehiclesTable, Default owned_vehicles): fuel (INT 100), name (VARCHAR 100), pound_time (INT 0), impound_reason (VARCHAR 120), impound_by (VARCHAR 64), last_x/last_y/last_z (FLOAT), garage_id (VARCHAR 64) sowie bei DBStyle = "esx" zusätzlich pound (TINYINT).


Konfiguration

config/config.lua — Core-Einstellungen

Key Standard Beschreibung
Config.Framework "auto" Framework: "esx", "qb", "custom" oder "auto" (erkennt es_extended, qb-core, qbx_core selbst)
Config.Debug false Debug-Prints in der Server-Konsole
Config.UseDatabaseGarages true true = Garagen aus DB + In-Game-Creator; false = ausschließlich Config.Garages (statisch)
Config.AdminGroups {'admin','superadmin','god'} Gruppen mit Zugriff auf den Garage-Creator und das Admin-Panel
Config.AllowSettingsInCreator true true = Settings-Tab im /creategarage sichtbar (Werte in ancomox_settings); false = nur config.lua als Quelle
Config.RetrievalMode "anywhere" Abholmodus: "anywhere" (jede passende Garage), "origin" (nur Ursprungsgarage), "transfer" (wie origin + „Bring here" gegen Gebühr)
Config.RetrievalTransferFee 500 Gebühr für „Bring here" im Modus "transfer"
Config.OnRestart "return" Restart-Recovery: "return" (ausgeparkte Autos kommen zurück, dupe-sicher), "recover_free" (kostenlos im Menü zurückholbar), "keep" (nur Schrottplatz gegen Gebühr)
Config.SaveLastLocation true Letzte bekannte Fahrzeugposition speichern (für „Track"-Wegpunkt)
Config.SaveLastLocationInterval 20000 ms-Intervall für das Positions-Update des gefahrenen Fahrzeugs
Config.MenuSystem "ox_lib" Kontextmenü-System für Impound & Schrottplatz: "ox_lib", "esx" oder "qb"
Config.Locale 'en' Sprache ('en'/'de' oder eigene, siehe locales.lua)
Config.Locales (abgeleitet) Wird automatisch aus Locales[Config.Locale] gesetzt (nicht manuell ändern)

Config.CustomFramework (nur bei Framework "custom" / wenn auto nichts findet)

Key Standard Beschreibung
DBStyle "esx" "esx" = getrennte Spalten stored + pound (0/1); "qb" = eine state-Spalte (0=draußen, 1=Garage, 2=Impound)
VehiclesTable "owned_vehicles" Name deiner Fahrzeugtabelle
Columns.Owner "owner" Spalte des Besitzer-Identifiers
Columns.Props "vehicle" Spalte mit dem JSON der Fahrzeug-Properties
Columns.Plate "plate" Kennzeichen-Spalte
Columns.Fuel "fuel" Tank-Spalte
Columns.Name "name" Custom-Name-Spalte
Columns.Stored "stored" Bei DBStyle "qb": Name der State-Spalte (z. B. "state")
Columns.Pound "pound" Nur DBStyle "esx"
Columns.PoundTime "pound_time" Zeit-Spalte der Beschlagnahmung
Jobs {police, ambulance, mechanic} Jobliste für den Garage-Creator ({value=, label=}), falls das Framework keine liefert

Custom-Anbindung: Pflichtfunktion Custom_GetPlayer in server/open.lua implementieren (muss identifier, name, job, group, getMoney, removeMoney, addMoney, jobGrade, source zurückgeben). addMoney ist Pflicht für Transfer-/Garagenverkäufe. Client-seitig bei Jobwechsel TriggerEvent('ancomox_garage:setJob', jobName) feuern.

Config.PrivateGarages (Premium — kaufbare private Garagen)

Key Standard Beschreibung
Enabled true Feature ein/aus
MaxOwnedPerPlayer 2 Max. private Garagen pro Spieler
SellPercent 50 Rückerstattung in % beim Verkauf (/sellgarage)
RoutingBucketBase 5000 Instanz-ID = Base + DB-ID der Garage (bei Bucket-Kollision erhöhen)
Blips.ForSale {sprite=374,color=2,scale=0.7,name="Garage For Sale"} Blip für kaufbare Garagen
Blips.Owned {sprite=357,color=5,scale=0.7,name="My Garage"} Blip für eigene Garagen
Interiors {small, medium, large} Die 3 Vanilla-Interiors (siehe unten)
Locations {legion_small, grove_medium, sandy_large} Kaufbare Standorte (siehe unten)

Interiors-Eintrag (je small/medium/large): label, capacity (2/6/10), playerEnter (vector4), exitPoint (vector3), menuPoint (vector3), slots (Liste von vector4-Stellplätzen). Z-Höhe der drei Vanilla-Garagen ist bewusst -99.60 (knapp über dem Interior-Boden).

Locations-Eintrag (Table-Key = eindeutige DB-ID):

Feld Beispiel Beschreibung
label "Legion Square Garage" Anzeigename
interior "small" Verweist auf Interiors (small/medium/large)
price 65000 Kaufpreis
entrance vector4(193.50,-831.60,30.73,340.0) Kauf-/Betreten-Punkt (im Auto: [E] parkt ein und betritt)
vehicleSpawn vector4(200.80,-838.60,30.62,340.0) Außen-Punkt: hier spawnen Autos & alternativer [E]-Einlagerpunkt

Default-Standorte: legion_small ($65.000), grove_medium ($145.000), sandy_large ($295.000).

Config.PrivateGarageUpgrades

Key Standard Beschreibung
Enabled true Upgrade privater Garagen per /upgradegarage am Eingang
Order { 'small', 'medium', 'large' } Upgrade-Reihenfolge
Prices { medium = 120000, large = 260000 } Preis der Ziel-Stufe (Fahrzeuge bleiben erhalten)

Config.VehicleTransfer (Premium — Besitzübertragung)

Key Standard Beschreibung
Enabled true Feature ein/aus
AllowPrice true Verkauf mit Preis erlauben (Geld → Verkäufer)
MaxPrice 10000000 Maximaler Verkaufspreis
RequireNearby true Empfänger muss in der Nähe sein
MaxDistance 10.0 Max. Distanz (Meter), wenn RequireNearby
RequestTimeout 60 Sekunden bis eine Anfrage verfällt

Config.Valet (Premium — Fahrzeug liefern lassen)

Key Standard Beschreibung
Enabled true Feature ein/aus
Command "valet" Befehl (/valet [kennzeichen])
Fee 750 Gebühr pro Lieferung
Cooldown 300 Sekunden Cooldown pro Spieler
NodeSearch 60.0 Max. Suchradius für einen Straßen-Node

Config.Keys (Premium — Fahrzeugschlüssel-Bridge)

Key Standard Beschreibung
System "none" "none", "qb", "qbx", "wasabi_carlock", "mk_vehiclekeys", "jaksam" oder "custom" (eigene Integration in client/open.luaCustom_GiveVehicleKeys)
GiveOnSpawn true Schlüssel beim Ausparken automatisch geben

Config.AdminPanel

Key Standard Beschreibung
Enabled true In-Game-Fahrzeugverwaltung für Admins
Command "adminvehicles" Panel-Befehl
SpawnCarCmd "admincar" Temporäres (nicht gespeichertes) Fahrzeug spawnen
SearchLimit 60 Max. Treffer pro Suche

Config.HousingBridge

Key Standard Beschreibung
Enabled true Erlaubt Housing-Scripts, per Export/Event eine Garage an eine Immobilie zu binden (garage_id = "house:<hausId>")

Config.PreviewCustomization

Key Standard Beschreibung
Livery true Livery-Umschaltung in der 3D-Vorschau
Extras true Extra-Toggles (1–14) in der Vorschau

Config.Target & Config.TextUI

Key Standard Beschreibung
Target.Enabled false true = ox_target/qb-target-Zonen statt Marker/[E]-Loops
Target.System "ox_target" "ox_target" oder "qb-target"
Target.Distance 2.5 Interaktionsreichweite (Meter)
Target.Debug false Zonen sichtbar rendern (nur zum Einrichten)
TextUI.Enabled false true = TextUI statt DrawText3D (wenn Target aus)
TextUI.System "ox_lib" "ox_lib" oder "qb-core"

Config.ServerSpawn (Anti-Dupe, opt-in)

Key Standard Beschreibung
Enabled false true = Server spawnt ausgeparkte Fahrzeuge autoritativ (CreateVehicleServerSetter). Benötigt OneSync. Vor Produktivbetrieb testen!
MaxPerPlayer 25 Obergrenze gleichzeitig server-gespawnter Fahrzeuge pro Spieler (Anti-Grief)

Config.Performance

Key Standard Beschreibung
IdleSleep 1000 ms-Schlafzeit der Interaktions-Loops im Leerlauf (höher = weniger CPU, trägere Reaktion)

Fahrzeug-Lock & System-Settings

Key Standard Beschreibung
Config.EnableVehicleLocking true Fahrzeug-Lock-System aktiv
Config.VehicleLockDefaultKey 'u' Standardtaste (per RegisterKeyMapping vom Spieler änderbar)
Config.MaxLockDistance 10.0 Max. Distanz zum Ab-/Aufschließen
Config.LockFeedback true Lichter blinken beim Ab-/Aufschließen
Config.LockAnimAndSound true Keyfob-Animation und -Sound
Config.UseVehicleHorn false Hupe statt Frontend-Sound

UI, Kamera, Distanzen, Fuel

Key Standard Beschreibung
Config.ServerName "Ancomox Roleplay" Anzeigename im Garagen-UI
Config.LogoPath "images/logo.png" Pfad zum Server-Logo (in html/images/)
Config.UseShowroom false Casino-Podium-Hintergrund im Menü (private Garagen nutzen ihn immer)
Config.CameraPanEnabled true Kamera langsam ums Fahrzeug rotieren
Config.CameraPanSpeed 0.1 Rotationsgeschwindigkeit
Config.MaxDistance 5.0 Distanz zum Öffnen des NPC-Menüs
Config.MaxDistancevehicleparkmarker 8.0 Distanz zum Spawnpunkt zum Einparken
Config.NPCSpawnDistance 30.0 Distanz, ab der der NPC spawnt
Config.AllowOthersToParkVehicles false Erlaubt Einparken fremder Fahrzeuge
Config.EnableGPS true GPS-Tracking für ausgeparkte Fahrzeuge
Config.Fuel.system "ox_fuel" Fuel-Bridge: "ox_fuel", "legacyfuel", "ps-fuel", "rcore_fuel"

Config.NotifyClient / Config.NotifyServer (Funktionen)

Standard-Benachrichtigungen. NotifyClient(msg, type) und NotifyServer(source, msg, type) verzweigen intern nach ESX (ShowNotification / esx:showNotification) bzw. QB (Functions.Notify / QBCore:Notify). Für Custom-Frameworks hier anpassen.

Impound, Schrottplatz & Verwahrstelle

Config.Impound (Beschlagnahmungs-/Impound-Interaktionspunkt):

Key Standard Beschreibung
location vector4(425.81,-1014.28,28.99,89.54) Impound-Punkt
fee 500 Auslösegebühr
jobCanAlwaysRelease "police" Job, der jederzeit freigeben darf
spawnNPC true NPC spawnen
npcHash "s_m_y_cop_01" NPC-Modell

Config.ScrapYard (Schrottplatz für „verlorene" Fahrzeuge):

Key Standard Beschreibung
location vector4(101.34,-1071.60,29.23,338.30) Schrottplatz-Punkt
fee 1000 Gebühr zum Wiederherstellen
spawnNPC true NPC spawnen
npcHash "s_m_y_garbage" NPC-Modell

Config.ImpoundLot (physische Verwahrstelle/Depot zum Auslösen):

Key Standard Beschreibung
Enabled true Verwahrstelle aktiv
location vector4(409.79,-1622.92,29.29,231.0) Büro (Davis Impound)
spawn vector4(407.44,-1645.31,29.31,142.0) Fahrzeug-Ausgabepunkt
releaseFee 800 Auslösegebühr nach Ablauf der Sperrzeit
blip {sprite=68,color=1,scale=0.75,name="Impound Lot"} Blip der Verwahrstelle

Config.Garages — statische Garagen (nur bei UseDatabaseGarages = false)

Enthält rund zwei Dutzend vordefinierte Standard-Garagen (öffentliche + Job-Garagen für police, ambulance, mechanic, merryweather, admin sowie Boots-/Air-Garagen). Struktur eines Garagen-Eintrags:

Feld Typ Pflicht Beschreibung
location vector4 ja NPC-/Interaktionspunkt (Garage öffnen)
spawnPos vector4 ja Punkt, an dem ausgeparkte Fahrzeuge erscheinen / eingeparkt wird
job nil | string | table nein Job-Bindung: nil = für alle, 'police' oder {'admin'}
spawnNPC boolean nein NPC an der Garage spawnen
npcHash string nein NPC-Modell (Default 'a_m_y_business_02')
blip table nein { sprite, color, scale, name }
markerSettings table nein { id, size = {x,y,z}, color = {r,g,b,a} } (optional; DB-Garagen nutzen Default-Marker id 36, Farbe hellblau)
vehicleTypes table nein Erlaubte Typen, z. B. {"car","air","boat","bike"}
vehicleClasses table nein Erlaubte GTA-Fahrzeugklassen, z. B. {0,1,2,...,20}

Neue Garage anlegen — Variante A (statisch, in Config.Garages)

Neuen Eintrag in die Config.Garages-Liste ergänzen. Beispiel öffentliche PKW-Garage:

{
    location = vector4(-1985.00, 3065.13, 32.81, 102.20),  -- Garage öffnen
    spawnPos = vector4(-1993.00, 3067.41, 32.81, 57.95),   -- Fahrzeug-Ausgabe
    job = nil,                                             -- nil = für alle
    spawnNPC = true,
    npcHash = 'a_m_y_business_02',
    blip = { sprite = 357, color = 3, scale = 0.75, name = "Garage" },
    markerSettings = { id = 25, size = {x = 5.0, y = 5.0, z = 1.0}, color = {r = 0, g = 255, b = 0, a = 100} },
    vehicleTypes = {"car"},
    vehicleClasses = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 19, 20}
}

Beispiel Job-Garage (Polizei):

{
    location = vector4(451.94, -973.12, 25.70, 176.38),
    spawnPos = vector4(450.43, -977.88, 25.70, 102.37),
    job = 'police',
    spawnNPC = true,
    npcHash = 'a_m_y_business_02',
    blip = { sprite = 60, color = 29, scale = 0.8, name = "Police Garage" },
    vehicleClasses = {18, 20, 0, 1, 2}
}

Neue Garage anlegen — Variante B (dynamisch, empfohlen bei UseDatabaseGarages = true)

Im Spiel als Admin /creategarage → Platzierung per Raycast, Job-Bindung, Fahrzeugtyp-/Klassenfilter, Blip-Einstellungen, optional Company fleet (society) + Mindest-Job-Rang. Speicherung in ancomox_garages, sofortiger Sync an alle Clients. Private Standorte im selben Creator unter Tab „Private Garages" (Name, Interior small/medium/large, Preis, Entrance + Vehicle Exit Point) → Speicherung in ancomox_private_locations.


config/locales.lua — Sprachdatei

Locales enthält vollständige en- und de-Blöcke. Sprache über Config.Locale wählen; eigene Sprache = Block kopieren, Kürzel ändern, übersetzen. Strings mit %s/%d sind Format-Platzhalter (nicht entfernen). Übersicht der Schlüsselgruppen:

Gruppe (Präfix) Zweck Beispiel-Keys
Core Prompts & Grundmeldungen store_vehicle, garage_prompt, impound_prompt, scrapyard_prompt, vehicle_stored, not_your_vehicle, vehicle_locked, gps_set
target_* Saubere Labels für ox_target/qb-target (ohne [E]) target_garage, target_store, target_impound, target_scrapyard
retr_* Abhol-Modi origin/transfer retr_other_garage, retr_brought_here, retr_move_prompt
settings_saved, mods_saved Creator-Settings / Vorschau-Anpassung
valet_* Valet-Service valet_disabled, valet_cooldown, valet_delivered, valet_arrived
adm_* Admin-Fahrzeug-Panel adm_returned, adm_given, adm_deleted, adm_plate_changed, adm_car_spawned
pg_* Private Garagen (Kauf/Verkauf/Betreten/Slots/Session/Mitbenutzer/Upgrade) pg_buy_prompt, pg_purchased, pg_sold, pg_limit_reached, pg_garage_full, pg_member_added, pg_upgraded
soc_* Firmen-Fuhrpark (society) soc_added, soc_removed, soc_wrong_job, soc_grade_low
imp_* Verwahrstelle/Impound-Lot imp_lot_prompt, imp_release_paid, imp_list_header, imp_list_entry, imp_list_empty
tr_* Vehicle Transfer tr_sent, tr_received_free, tr_received_price, tr_done_sender, tr_done_receiver, tr_no_money, tr_failed
dbg_no_response Diagnose (Server antwortet nicht)

(Beide Sprachblöcke enthalten jeweils dieselbe Schlüsselmenge; Standardwerte sind die englischen bzw. deutschen Klartexte oben in config.lua-Nähe zitiert.)


config_logs.lua — Discord-Webhook-Konfiguration

Config_Logs = {
    Webhooks = {
        Store    = "",   -- Fahrzeug eingeparkt
        ParkOut  = "",   -- Fahrzeug ausgeparkt / verschoben / Valet
        Impound  = "",   -- Beschlagnahmung / Auslösung
        Scrap    = "",   -- Schrottplatz / kostenlose Wiederherstellung / Löschung
        Admin    = "",   -- Admin- & Creator-Aktionen
        Transfer = "",   -- Fahrzeug-Übertragungen zwischen Spielern
        Property = ""    -- Kauf/Verkauf privater Garagen, Mitglieder, Upgrades
    },
    LogSettings = {
        Store = true, ParkOut = true, Impound = true, Scrap = true,
        Admin = true, Transfer = true, Property = true
    }
}
Key Standard Beschreibung
Webhooks.Store "" Discord-Webhook-URL für Einpark-Logs
Webhooks.ParkOut "" Ausparken / „Bring here" / Valet
Webhooks.Impound "" Beschlagnahmung & Auslösung
Webhooks.Scrap "" Schrottplatz, kostenlose Wiederherstellung, Löschung
Webhooks.Admin "" Admin-/Creator-/Settings-Aktionen
Webhooks.Transfer "" Besitzübertragungen
Webhooks.Property "" Private-Garagen-Kauf/-Verkauf, Mitglieder, Upgrades
LogSettings.<Typ> true (alle 7) Schaltet den jeweiligen Log-Typ global an/aus

Ein Log wird nur gesendet, wenn LogSettings[typ] = true und die passende Webhook-URL gesetzt ist. Ein leerer String oder eine Demo-Webhook (interner Guard api/webhooks/119887…) unterdrückt den Versand.


Exports

Beide Exports liegen client-seitig in client/client.lua und dienen der Housing-Bridge. Sie sind nur aktiv, wenn Config.HousingBridge.Enabled = true. Beide geben keinen Wert zurück (kein Rückgabewert / nil).

1. OpenHouseGarage(houseId, data)

  • Seite: Client
  • Definition: exports('OpenHouseGarage', function(houseId, data) ... end)
  • Signatur:
  • houseId (string|number) — eindeutige Haus-ID (wird zu string konvertiert; intern retrievalKey = "house:<hausId>")
  • data (table, optional):
    • label (string) — Anzeigename der Garage (Default "House Garage")
    • spawn (vector4/{x,y,z,w}) — wo ausgeparkte Fahrzeuge erscheinen (Default: aktuelle Ped-Position)
    • vehicleTypes (table, optional) — Typ-Filter
    • vehicleClasses (table, optional) — Klassen-Filter
  • Rückgabe: keine
  • Beschreibung: Öffnet das volle Garagen-Menü für eine an ein Haus gebundene Garage. Fahrzeuge, die hier eingelagert werden, erscheinen nur in dieser Haus-Garage. Auch als Event ancomox_garage:openHouseGarage verfügbar.
  • Beispiel:
exports.ancomox_garage:OpenHouseGarage(houseId, {
    label = "Mein Haus",
    spawn = vector4(x, y, z, w),      -- wo Autos rauskommen
    vehicleTypes = {"car"},
    vehicleClasses = nil
})
-- alternativ per Event:
TriggerEvent('ancomox_garage:openHouseGarage', houseId, data)

2. StoreVehicleAtHouse(houseId, vehicle)

  • Seite: Client
  • Definition: exports('StoreVehicleAtHouse', function(houseId, vehicle) ... end)
  • Signatur:
  • houseId (string|number) — Haus-ID (Zuordnung über garage_id = "house:<hausId>")
  • vehicle (entity handle, optional) — zu lagerndes Fahrzeug. Wird nichts/0 übergeben, nutzt der Export das Fahrzeug, in dem der Spieler sitzt, sonst das nächste Fahrzeug im Umkreis < 5.0 m.
  • Rückgabe: keine
  • Beschreibung: Lagert das angegebene (oder automatisch ermittelte) Fahrzeug in die Haus-Garage ein (ruft intern AttemptToStoreVehicle). Ist kein Fahrzeug in Reichweite, erscheint die Notify no_vehicle_nearby.
  • Beispiel:
-- aktuelles/nächstes Fahrzeug einlagern:
exports.ancomox_garage:StoreVehicleAtHouse(houseId)
-- bestimmtes Fahrzeug einlagern:
exports.ancomox_garage:StoreVehicleAtHouse(houseId, vehicleEntity)

Events & Commands

Befehle (RegisterCommand)

Befehl Wer Zweck
/creategarage Admin (Config.AdminGroups) Garage-Creator (öffentliche + private Standorte, Society, Settings-Tab)
/impound <stunden> [grund...] Impound-Job Fahrzeug beschlagnahmen (1–48 h); speichert Grund + Beamten
/impoundlist Impound-Job Übersicht beschlagnahmter Fahrzeuge
/sellgarage Besitzer, am Eingang Private Garage verkaufen (SellPercent % Rückerstattung; muss leer sein)
/upgradegarage Besitzer, am Eingang Private Garage auf nächstes Interieur aufwerten
/acceptvehicle Transfer-Empfänger Fahrzeug-Übertragung annehmen
/declinevehicle Transfer-Empfänger Übertragung ablehnen
/valet [kennzeichen] Alle Eingelagertes Fahrzeug liefern lassen (Name via Config.Valet.Command)
/adminvehicles Admin Admin-Fahrzeug-Panel (Name via Config.AdminPanel.Command)
/admincar <model> Admin Temporäres Fahrzeug spawnen (Name via Config.AdminPanel.SpawnCarCmd)
/addjobvehicle <job> Admin Fahrzeug zum Firmen-Fuhrpack hinzufügen (im/neben Fahrzeug)
/removejobvehicle Admin Firmenfahrzeug entfernen
/togglegaragelock (intern/Admin) Garagen-Lock umschalten
/garagedebug (Diagnose) Diagnose-Ausgabe, wenn der Garagen-Server nicht antwortet
Taste U Alle Fahrzeug ab-/aufschließen (via Config.VehicleLockDefaultKey, Keymapping frei belegbar)

Wichtige Events (Auswahl)

Event Seite Zweck
ancomox_garage:openHouseGarage (houseId, data) Client Haus-Garage öffnen (Pendant zum Export)
ancomox_garage:setJob (jobName) Client Job-Update für Custom-Frameworks setzen
ancomox_garage:leavePrivate Server Private Garage verlassen
ancomox_garage:forceExitPrivate (entrance, msg) Client Spieler aus der Garage nach draußen bringen
ancomox_garage:updateVehCoords Server Letzte Fahrzeugposition speichern
ancomox_garage:saveCustomName Server Fahrzeug-Custom-Name speichern
garage:syncGarages / garage:syncPrivateGarages Client Garagenlisten an Clients syncen
garage:settingsUpdated / garage:notify / gps:trackVehicle Client Runtime-Settings-Sync, Benachrichtigung, GPS-Track
garage:toggleLockServer / garage:toggleLockResult Server/Client Fahrzeug-Lock-Roundtrip
garage:deleteImpoundedVehicle Client Beschlagnahmtes Fahrzeug löschen
esx:setJob / QBCore:Client:OnJobUpdate / esx:playerLoaded / QBCore:Client:OnPlayerLoaded Client Framework-Hooks (Jobwechsel/Login)

Server-Callbacks laufen über eine eigene Bridge (ancomox_garage:cbRequest / cbResponse, Custom_RegisterServerCallback), z. B. garage:getVehicles, garage:fetchDBGarages, garage:saveCreatorGarage, garage:getPrivateGarageStates.


Discord-Logs

Zentrale Funktion: SendWebhookLog(type, title, message, color) (server/server.lua, Zeile ~8583). Sie prüft Config_Logs.LogSettings[type], holt Config_Logs.Webhooks[type], überspringt leere/Demo-Webhooks und postet dann per PerformHttpRequest ein Discord-Embed:

  • Benutzername: "Garage Logs"
  • Embed: title, description (Markdown mit Spielername, Identifier, Kennzeichen [PLATE], Beträgen), footer = "Garage System | <YYYY-MM-DD HH:MM:SS>", color = dezimaler Farbwert (z. B. grün 5763719 Store/Kauf, blau 3447003 ParkOut/Info, rot 15548997 Impound/Löschung, gelb 16776960 Update/Verkauf)

Welche Aktionen loggen in welchen Typ:

Typ Geloggte Aktionen (Embed-Titel)
Store „Vehicle Stored" (Fahrzeug eingeparkt; inkl. Hinweis auf private Garage)
ParkOut „Vehicle Parked Out" (ausgeparkt), „Vehicle Moved" (Bring here, transfer-Modus), „Valet Delivery"
Impound „Vehicle Impounded" (mit Stunden + Grund), „Impound Released" (Auslösung)
Scrap „Vehicle Recovered" (kostenlose Wiederherstellung), „Vehicle Retrieved from Scrapyard" (gegen Gebühr), „Vehicle Permanently Deleted"
Admin „Settings Updated", „Garage Created/Updated/Deleted", „Society Vehicle", „Vehicle Returned/Given/Deleted", „Plate Changed", „Private Location Saved/Deleted"
Transfer „Vehicle Transferred" (Besitzübergabe zwischen Spielern)
Property „Private Garage Purchased/Sold", „Garage Member Added", „Garage Upgraded"

Webhook-Setup: 1. In Discord: Servereinstellungen → Integrationen → Webhooks → Neuer Webhook, Kanal wählen, URL kopieren. 2. URL in config_logs.lua beim gewünschten Typ eintragen, z. B. Store = "https://discord.com/api/webhooks/…". Für mehrere Kategorien im selben Kanal dieselbe URL mehrfach eintragen. 3. Einzelne Kategorien über LogSettings.<Typ> = false deaktivieren (spart HTTP-Requests).


Hinweise / Troubleshooting

  • Reihenfolge in server.cfg: oxmysql muss vor ancomox_garage starten, sonst schlagen die MySQL.ready-Migrationen fehl.
  • Kein SQL-Import nötig: Alle Tabellen und fehlende Fahrzeug-Spalten werden beim ersten Start automatisch angelegt/ergänzt.
  • „Garage server did not respond" (dbg_no_response): /garagedebug ausführen und die Ressource neu starten; meist ein DB-/Callback-Timing-Problem.
  • Falscher identifier: Bei Custom-Frameworks muss der von Custom_GetPlayer gelieferte identifier exakt zur Owner-Spalte (Config.CustomFramework.Columns.Owner) passen, sonst tauchen keine Fahrzeuge auf. addMoney ist Pflicht für Transfer-/Garagenverkauf, jobGrade für Fleet-Garagen.
  • DBStyle korrekt wählen: "esx" (getrennte stored/pound-Spalten) vs. "qb" (eine state-Spalte). Falsche Wahl → Fahrzeuge gelten als draußen/beschlagnahmt.
  • Private Garagen — Routing Buckets: RoutingBucketBase (Default 5000) darf nicht mit anderen Instanz-Systemen kollidieren; bei Bedarf erhöhen. Slot-Positionen bei „Auto klebt in der Wand" einfach als vector4 in Config.PrivateGarages.Interiors verschieben — Z wird per SetVehicleOnGroundProperly abgesichert (Z = -99.60 nicht unter -100.0 setzen, sonst fällt man durchs Interior).
  • Logout in privater Garage: Wird über ancomox_private_garage_presence abgefangen (v2.11-Fix) — Tabelle nicht manuell leeren, während Spieler online sind.
  • ServerSpawn (Anti-Dupe): Config.ServerSpawn.Enabled = true benötigt OneSync (Infinity) und ändert die Spawn-Architektur — vorher auf einem Testserver prüfen. Passt zu Config.OnRestart = "return".
  • AdvancedParking: fixDeleteVehicle.lua ist ein Kompatibilitäts-Shim; die Datei nicht in AdvancedParking selbst starten (sie bricht dort bewusst ab).
  • Discord-Logs kommen nicht an: Prüfen, ob (a) LogSettings[typ] = true, (b) die Webhook-URL gesetzt und gültig ist und (c) keine Demo-Webhook (…api/webhooks/119887…) verwendet wird — diese wird bewusst blockiert.
  • Fahrzeugbilder: Eigene PNGs (Modellname, klein) nach html/images/vehicles/ legen; fehlt ein Bild, lädt das UI das offizielle FiveM-Docs-Bild und danach default.png.

ancomox_vehicleshop — Entwickler-Dokumentation

Ancomox Premium Vehicle Shop — Fahrzeug-Shop/Autohaus-Script für FiveM mit NUI-Oberfläche, permanenten Vorschaufahrzeugen, Probefahrt, kinematischer Kamera, Job-beschränkten Shops und Discord-Logging.

Überblick

ancomox_vehicleshop stellt beliebig viele Autohäuser (Standard: 14 Shops) an frei konfigurierbaren Orten bereit. Der Spieler interagiert über einen Verkäufer-Ped (via ox_target/qb-target) oder über einen klassischen 3D-Marker und öffnet eine NUI, in der Fahrzeuge nach Kategorien durchgeblättert, in einer Live-3D-Vorschau betrachtet (Farbe/Türen/Licht/Motor umschaltbar), probegefahren und gekauft werden können.

Kernmerkmale (aus dem Code bestätigt): - Multi-Framework: esx, qbcore oder custom (Config.Framework). - Wiederverwendbare Fahrzeug-Pools (Config.GlobalVehicles), pro Shop über useGlobal referenziert, oder Inline-Fahrzeuglisten pro Kategorie (vehicles). - Job-beschränkte Shops (restrictedJob), z. B. Police/Ambulance/Mechanic. - Permanente Zufalls-Vorschaufahrzeuge vor dem Shop (Config.UseRandomPreview, pro Shop via DisablePermanentPreview abschaltbar). - Ghost-Car-Schutz (Preview-Fahrzeuge tragen das Kennzeichen SHOPPREV* und werden aggressiv aufgeräumt). - Discord-Logs für Kauf und Probefahrt (config_logs.lua).

Wichtig: Die Ressource besitzt keine exports() und keine RegisterCommand. Der Shop wird ausschließlich über Ped-Target oder Marker geöffnet. Integriert wird ausschließlich über Config, Events und die entkapselten Dateien client/open.lua / server/open.lua.

Voraussetzungen

Abhängigkeit Pflicht Zweck / Quelle
es_extended Ja (siehe Hinweis) client/shop_fix.lua ruft exports['es_extended']:getSharedObject() unbedingt auf. → Auch im qbcore/custom-Betrieb aktuell erforderlich, sofern nicht editiert.
oxmysql Ja Fahrzeug-Speicherung.
qb-core Alternativ Nur wenn Config.Framework = "qbcore".
ox_target oder qb-target Optional Nur wenn Config.InteractionType = "ox_target" bzw. "qb-target". Andernfalls klassische Marker.
Keys-Ressource Optional Für Fahrzeugschlüssel nach Kauf: ESX → Event vehicles_keys:selfGiveVehicleKeys, QB → vehiclekeys:client:SetOwner, custom → custom_keys:give.
AdvancedParking Optional fixDeleteVehicle.lua bietet Kompatibilität; darf laut Datei nicht in AdvancedParking selbst gestartet werden.

Framework-Runtime (aus dem Code): - ESX: exports["es_extended"]:getSharedObject() - QBCore: exports["qb-core"]:GetCoreObject() - custom: keine Core-Anbindung; die relevante Logik (Geld, Speichern, Job, Notify) muss in client/open.lua / server/open.lua selbst implementiert werden.

Installation

  1. Ordner ancomox_vehicleshop in das resources-Verzeichnis kopieren.
  2. In der server.cfg ergänzen: cfg ensure ancomox_vehicleshop Reihenfolge: nach Framework (es_extended/qb-core), nach oxmysql und (falls genutzt) nach ox_target/qb-target sowie der Keys-Ressource.
  3. config/config.lua: Framework, Interaktionsart, Shops und Fahrzeuge einrichten.
  4. config/locales.lua: Sprache über Config.Locale ('en' oder 'de') wählen.
  5. config_logs.lua: Discord-Webhooks eintragen (optional).
  6. Fahrzeugbilder unter html/images/vehicles/<model>.png ablegen (Kleinschreibung; fehlt das Bild, wird images/vehicles/default.png genutzt).

Custom-Framework: Config.Framework = "custom" setzen und die entkapselte Logik in client/open.lua / server/open.lua ausfüllen. Achtung: In server/open.lua gibt ProcessPayment im custom-Modus immer true zurück (kein Geldabzug) und SaveVehicleToDatabase speichert nichts (cb(true)) — beides selbst implementieren.

Datenbank

Die Ressource bringt keine eigene .sql-Datei mit. Es werden die Standard-Tabellen des jeweiligen Frameworks verwendet (Speicherung nur wenn Config.Use_ESX_Owned_Vehicles = true).

ESX → Tabelle owned_vehicles:

Spalte Wert im Code
owner Spieler-Identifier (xPlayer.identifier)
plate generiertes 8-stelliges Kennzeichen (A–Z, 0–9)
vehicle json.encode(props) inkl. model (Hash) und plate
type 'car' (fest)

QBCore → Tabelle player_vehicles:

Spalte Wert im Code
license GetPlayerLicense(src)
citizenid Player.PlayerData.citizenid
vehicle Modellname (String)
hash GetHashKey(model)
mods json.encode(props)
plate generiertes Kennzeichen
state 0 (fest)

Beide Tabellen müssen aus dem jeweiligen Framework bereits existieren. Der Fahrzeug-type ist bei ESX fest 'car' — für Boote/Helis/Flugzeuge ggf. anpassen, sonst erscheint das Fahrzeug evtl. nicht im korrekten Garagentyp.

Konfiguration

Globale Optionen — config/config.lua

Key Standard Beschreibung
Config.Framework "esx" Framework: "esx", "qbcore" oder "custom". Steuert Geld-, Job-, Notify- und Speicherlogik.
Config.InteractionType "ox_target" Interaktion am Ped: "ox_target", "qb-target" oder "marker" (3D-Marker + Taste E).
Config.TargetPedModel "a_m_y_business_02" Ped-Modell des Verkäufers, das an jedem Shop gespawnt wird.
Config.Use_ESX_Owned_Vehicles true true = Kauf wird in DB gespeichert + Kennzeichen generiert. false = keine Speicherung, Kennzeichen "NOPLATE".
Config.UseRandomPreview true true = permanentes Zufalls-Vorschaufahrzeug vor jedem Shop (sofern Shop DisablePermanentPreview nicht setzt).
Config.TestDriveCoords vector3(-891.3095, -3205.846, 13.94) Globaler Spawnort für alle Probefahrten.
Config.TestDriveHeading 58.0 Blickrichtung beim Probefahrt-Spawn.
Config.TestDriveTime 40 Probefahrt-Dauer in Sekunden.
Config.ShopMarker 36 Marker-Typ (nur bei InteractionType = "marker").
Config.ShopMarkerColor { r = 0, g = 255, b = 0, a = 100 } Marker-Farbe (RGBA).
Config.ShopMarkerScale 1.0 Marker-Größe.
Config.ShopDrawDistance 30.0 Distanz (m), ab der der Marker gezeichnet wird.
Config.ShopInteractDistance 2.5 Distanz (m) für „Drücke E"-Hinweis und Interaktion (Marker-Modus).
Config.BlipSprite 225 Standard-Blip-Icon aller Shops.
Config.BlipColor 5 Standard-Blip-Farbe.
Config.BlipScale 0.8 Standard-Blip-Größe.
Config.Shops Tabelle (14 Einträge) Liste aller Autohäuser (siehe Shop-Struktur).
Config.GlobalVehicles Tabelle (16 Kategorien) Wiederverwendbare Fahrzeug-Pools, referenziert über useGlobal.
Config.DefaultScale 1.0 Im gelieferten Code nicht referenziert — vermutlich reserviert/Legacy.
Config.MinScale 0.5 Im gelieferten Code nicht referenziert.
Config.MaxScale 3.0 Im gelieferten Code nicht referenziert.

Hinweis: Im ox_target/qb-target-Modus ist die Interaktionsdistanz in der Target-Option fest 2.5 hinterlegt (nicht aus Config.ShopInteractDistance).

Struktur eines Shop-Eintrags (Config.Shops[i])

Feld Pflicht Beispiel Beschreibung
Name Ja "Premium Deluxe Motorsport" Anzeigename im UI und am Blip.
ShopCoords Ja vector3(-46.82244, -1095.814, 27.67) Position von Ped, Blip und Marker.
ShopHeading Ja 135.0 Blickrichtung des Verkäufer-Peds.
PreviewCoords Ja vector3(-47.5914, -1091.934, 27.3023) Standort des Vorschaufahrzeugs (permanent + im Menü).
PreviewHeading Ja 137.0 Ausrichtung des Vorschaufahrzeugs.
ReturnCoords Ja vector3(-46.6705, -1095.858, 27.274) Rückkehrposition nach Ende der Probefahrt.
PurchaseSpawnCoords Ja vector3(-21.51374, -1084.052, 27.04) Spawnort des gekauften Fahrzeugs.
PurchaseSpawnHeading Ja 68.0 Ausrichtung des gekauften Fahrzeugs (auch Heading bei Probefahrt-Rückkehr).
Categories Ja Tabelle Kategorien/Fahrzeuglisten des Shops.
restrictedJob Nein "police" Nur Spieler mit diesem Job sehen Blip/Ped/Marker und dürfen kaufen.
DisablePermanentPreview Nein true Deaktiviert das dauerhafte Vorschaufahrzeug vor diesem Shop.
BlipSprite / BlipColor / BlipScale Nein 43 / 3 / 0.8 Pro-Shop-Override der globalen Blip-Werte.
MarkerType / MarkerScale / MarkerColor Nein Pro-Shop-Override der Marker-Werte (im Code unterstützt, in der Default-Config nicht gesetzt).

Struktur einer Kategorie (Categories[j])

Feld Pflicht Beschreibung
name Ja Anzeigename des Kategorie-Tabs im UI (z. B. "Super Cars").
useGlobal Entweder/oder Schlüssel in Config.GlobalVehicles (z. B. "Super").
vehicles Entweder/oder Inline-Fahrzeugliste statt useGlobal (siehe Police/Ambulance/Mechanic Shops).

Ist useGlobal gesetzt, wird Config.GlobalVehicles[useGlobal] geladen; sonst vehicles. Ist keins vorhanden, ist die Kategorie leer.

Struktur eines Fahrzeug-Eintrags

Feld Pflicht Beispiel Beschreibung
name Ja "Adder" Anzeigename im UI.
model Ja "adder" Spawnname des Fahrzeugs (muss serverseitig vorhanden sein; bestimmt auch das Bild images/vehicles/adder.png).
price Ja 1540000 Kaufpreis (Rohwert; 1540000 = $1.540.000).
maxSpeed Optional 200 Im gelieferten Code nicht ausgewertet. Die UI-Stats (Speed/Beschleunigung/Bremse/Handling) werden live aus den Handling-Daten des gespawnten Fahrzeugs berechnet. Feld dekorativ/reserviert.

Config.GlobalVehicles — verfügbare Pools

16 Pools mit fertigen Vanilla-Fahrzeuglisten, per useGlobal referenzierbar:

Super, Sports, SportsClassic, MuscleCars, Bikes, Trucks, Trailers, Helis, Planes, Boats, SUVs, Offroad, Sedans, Coupes, Vans, Bike

Zwei Stolperfallen: - Bikes = Motorräder, Bike = Fahrräder (BMX, Cruiser …). Nicht verwechseln. - Vans ist definiert, wird aber von keinem Default-Shop referenziert — bei Bedarf einer Kategorie über useGlobal = "Vans" zuweisen.

Beispiel: neuen Shop mit globaler und eigener Kategorie anlegen

Config.Shops[#Config.Shops + 1] = {
    Name                 = "Import Tuner",
    ShopCoords           = vector3(120.0, -1050.0, 29.2),
    ShopHeading          = 90.0,
    PreviewCoords        = vector3(124.0, -1047.0, 29.2),
    PreviewHeading       = 90.0,
    ReturnCoords         = vector3(120.0, -1050.0, 29.2),
    PurchaseSpawnCoords  = vector3(128.0, -1045.0, 29.2),
    PurchaseSpawnHeading = 180.0,
    -- restrictedJob      = "cardealer",  -- optional: Job-Sperre
    -- DisablePermanentPreview = true,    -- optional
    -- BlipSprite = 326, BlipColor = 3,   -- optional: Blip-Override
    Categories = {
        { name = "Sportwagen", useGlobal = "Sports" },        -- aus GlobalVehicles
        {
            name = "Sondermodelle",                            -- eigene Inline-Liste
            vehicles = {
                { name = "Mein Auto", model = "customcar1", price = 250000, maxSpeed = 200 },
                { name = "VIP GT",    model = "customcar2", price = 900000, maxSpeed = 200 },
            }
        }
    }
}
  • Fahrzeug zu bestehendem Pool hinzufügen: neuen { name, model, price }-Eintrag in Config.GlobalVehicles.<Pool> einfügen — erscheint automatisch in jedem Shop, der diesen Pool via useGlobal nutzt.
  • Preis ändern: price des jeweiligen Eintrags anpassen.
  • Bild: html/images/vehicles/<model>.png (Kleinschreibung) ablegen, sonst default.png.

config/locales.lua

Config.Locale wählt die aktive Sprache (Standard 'en'; 'de' vorhanden). Jede Sprache ist ein Eintrag in Config.Locales:

Key Beschreibung / Platzhalter
press_to_open Marker-Hinweistext; %s = Shopname. ~INPUT_PICKUP~ = Taste.
target_label Target-/Interaktions-Label; %s = Shopname.
already_in_test_drive Meldung: bereits in Probefahrt.
error_loading_vehicle Fehler beim Modell-Laden.
test_drive_started Probefahrt gestartet; %d = Sekunden.
test_drive_ended Probefahrt beendet.
vehicle_bought Kaufbestätigung; %s Modell, %d Preis, %s Kennzeichen.
not_enough_money Nicht genug Geld.
db_error Datenbankfehler beim Speichern.

Standardwerte (de): u. a. press_to_open = "Drücke ~INPUT_PICKUP~ um %s zu betreten", vehicle_bought = "Gekauft: ~b~%s~s~ für ~g~$%d~s~ (Kennz.: ~y~%s~s~)".

Events & Commands

Commands: Keine (RegisterCommand existiert nicht). Der Shop öffnet ausschließlich über Ped-Target oder Marker.

Netz-Events der Ressource:

Richtung Event Parameter Zweck
Client → Server ancomox_vehicleshop:buyVehicle shopIndex, model, price, vehicleProps Kaufanfrage. Server prüft Geld (ProcessPayment), speichert Fahrzeug, sendet purchaseSuccess.
Client → Server ancomox_vehicleshop:logTestDrive model Meldet Probefahrt-Start für Discord-Log.
Server → Client ancomox_vehicleshop:purchaseSuccess shopIndex, model, price, plate, vehicleProps Spawnt gekauftes Fahrzeug am PurchaseSpawnCoords, setzt Kennzeichen, gibt Schlüssel.

Betreiber-Warnung (Anti-Cheat): price wird vom Client an buyVehicle übergeben und serverseitig ungeprüft an ProcessPayment/Discord-Log durchgereicht. Es findet keine serverseitige Preis-/Modell-Validierung gegen die Config statt — ein manipulierter Client könnte Preis 0 senden. Bei Bedarf serverseitige Validierung in server/open.lua ergänzen.

Extern erwartete Events (Kompatibilität / selbst bereitzustellen):

Event Modus Zweck
vehicles_keys:selfGiveVehicleKeys (Server) esx Schlüsselvergabe nach Kauf.
vehiclekeys:client:SetOwner (lokal) qbcore Schlüsselvergabe nach Kauf.
custom_keys:give (Server) custom Schlüsselvergabe nach Kauf.
esx:showNotification / QBCore:Notify / custom:notify jeweils Server-Benachrichtigungen an Client.
esx:playerLoaded, esx:setJob, QBCore:Client:OnPlayerLoaded, QBCore:Client:OnJobUpdate, custom:playerLoaded, custom:setJob jeweils Lösen FullShopRefresh() aus (Blips/Previews/Job-Rechte neu aufbauen).

Interne NUI-Callbacks (nur zur Info, keine Integrationspunkte): closeUI, toggleAutoRotate, changeColor, toggleDoor, toggleLights, toggleEngine, selectVehicle, buyVehicle, startTestDrive, hoverVehicleList.

Discord-Logs

Konfiguration in config_logs.lua (globale Tabelle Config_Logs):

Key Standard Beschreibung
Config_Logs.Webhooks.VehicleSale "" Webhook-URL für Fahrzeugkäufe. Leer = kein Versand.
Config_Logs.Webhooks.TestDrive "" Webhook-URL für Probefahrten. Leer = kein Versand.
Config_Logs.LogSettings.VehicleSale true Kauf-Logging aktivieren/deaktivieren.
Config_Logs.LogSettings.TestDrive true Probefahrt-Logging aktivieren/deaktivieren.

Versand über PerformHttpRequest als Embed (Username „Vehicle Shop", Footer „Ancomox Premium Vehicle Shop"). Kauf-Embed enthält Spielername, Identifier, Modell, Preis, Kennzeichen; Probefahrt-Embed Spielername, Identifier, Modell. Bei Use_ESX_Owned_Vehicles = false wird der Kauf als „(No DB Save)" geloggt.

Hinweise / Troubleshooting

  • es_extended trotz qbcore/custom nötig: client/shop_fix.lua ruft exports['es_extended']:getSharedObject() unbedingt auf. Ohne es_extended bricht der Client-Start. Für reinen QB-/Custom-Betrieb muss diese Stelle editiert werden.
  • Kein Geldabzug im custom-Modus: ProcessPayment gibt bei Config.Framework = "custom" immer true zurück und SaveVehicleToDatabase speichert nichts — eigene Logik in server/open.lua einbauen.
  • Ghost-Cars / Preview-Reste: Vorschaufahrzeuge tragen das Kennzeichen SHOPPREV*. client/shop_fix.lua und ClearGhostCars/SafeDeleteEntity räumen sie beim Start, Öffnen und Ressourcen-Stopp auf. Manuell platzierte Fahrzeuge mit diesem Kennzeichenmuster werden ebenfalls gelöscht.
  • fixDeleteVehicle.lua: überschreibt DeleteEntity/DeleteVehicle für AdvancedParking-Kompatibilität. Die Datei darf nicht innerhalb von AdvancedParking gestartet werden (bricht sonst mit Hinweismeldung ab).
  • Fahrzeug erscheint nicht in Garage: Bei ESX wird type = 'car' fest gespeichert — für Boote/Helis/Flugzeuge ggf. Speicher-Typ in server/open.lua anpassen.
  • Kein Schlüssel nach Kauf: Passende Keys-Ressource muss laufen und das jeweilige Event (vehicles_keys:selfGiveVehicleKeys bei ESX, vehiclekeys bei QB) bereitstellen.
  • maxSpeed/DefaultScale/MinScale/MaxScale: in der Config vorhanden, aber im ausgelieferten Code nicht ausgewertet (ggf. Legacy).
  • Vans-Pool ungenutzt, BikesBike — siehe Abschnitt GlobalVehicles.
  • Preis-Manipulation: siehe Betreiber-Warnung unter „Events" — serverseitig kein Preisabgleich mit der Config.
© Ancomox · EmergencyOS Support: Discord · info@ancomox.com