Ancomox Docs

Ancomox — Dokumentation

Willkommen in der offiziellen Entwickler- und Betreiber-Dokumentation von Ancomox. 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

Ancomox ist die Dachmarke für alle hier dokumentierten FiveM-Ressourcen. Die Produkte teilen sich in zwei Gruppen:

  • Panel-gebundene ProdukteEmergencyOS und LicenseOS sind Web-Anwendungen. Akten, Fahndungen, Tickets, LiveMap und Statistiken werden im Browser unter emergencyos.de verwaltet, unabhängig von FiveM. Die zugehörigen FiveM-Ressourcen sind kostenlos und bringen das Panel als Tablet ins Spiel — dazu Funktionen, die nur dort möglich sind. Die Synchronisation läuft über eine gesicherte HTTP-Bridge. Für EmergencyOS gibt es zusätzlich einen Client für Garry’s Mod. Für die Nutzung wird ein kostenloses Panel-Konto mit API-Key benötigt.
  • Eigenständige RessourcenDispatchOS, Ancomox Garage, Vehicle Shop, Ancomox Warehouse, Ancomox Airdrop, Ancomox Fishing, DriveOS, Ancomox Admin Suite, Ancomox Activity Suite, Architect, die Börse, das HUD, GPS + Job + Admin Blips, Ancomox Banking und Ancomox Crafting laufen ohne Panel-Konto direkt auf deinem Server und binden sich nur an Framework, Datenbank und Inventar. Bei DispatchOS gibt es zusätzlich eine optionale Web-Leitstelle — sie erweitert den Funktionsumfang, wird aber für den Betrieb nicht benötigt.

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
Ancomox Warehouse Private Lagerhallen: kaufen, mieten, ausbauen, im Team teilen 4
Ancomox Airdrop Versorgungsabwürfe mit Flugzeug, Fallschirm und Loot-Container 3
Ancomox Fishing Angeln mit 3D-Showcase, Minigame, Markt, Turnieren & Bootsverleih 9
DriveOS Automatische Fahrschule — Theorie & Praxis ohne Fahrlehrer 2
Ancomox Admin Suite Admin-Panel im Spiel und im Browser, mit feingranularem Rechtesystem
Ancomox Activity Suite Player-Analytics, Reports, Bans & Spielerakten
Architect Bauwerkzeug mit Live-3D-Vorschau, Blueprints & Job-Rechten 20
Börse Dynamische Rohstoffkurse, Handelspunkte & Marktereignisse 7
Ancomox HUD HUD mit 3 Themes, Drag & Drop und eigener API Suite
GPS + Job + Admin Blips Job- und Admin-Blips auf der Karte (nur ESX)
Ancomox Banking Filialen, Automaten, Spar- & Firmenkonten, Kredite, Bankkarten Bridge
Ancomox Crafting Stationen, Fertigkeiten, Qualitätsstufen & Warteschlange 24

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 kostenloses EmergencyOS-Panel-Konto mit API-Key — nur für die panel-gebundenen Scripts EmergencyOS und LicenseOS. Alle übrigen Ressourcen laufen ohne Konto.

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.

Store

Alle hier dokumentierten Ressourcen findest du im offiziellen Ancomox-Store. Jede Produktseite in dieser Doku verlinkt oben direkt auf den passenden Artikel.

Ancomox-StoreAlle Scripts im Überblick
Store öffnen

Support

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

EmergencyOS — Entwickler-Dokumentation

Im Ancomox-StoreEmergencyOS — TabletKostenlos
Zum Produkt

Ü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. API-Key erstellen: Im Panel unter Einstellungen → Datenbank Sync → API-Key — diesen Button sieht nur der Besitzer-Account. Neue Keys tragen das Präfix eos_.
  3. Key eintragen: In config/api_config.luaSVConfig.API.key (Details im nächsten 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 — fertig. Server-IP und 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 hinterlegt IP, Port und Resource-Name automatisch als Push-Rückkanal. 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

Im Ancomox-StoreDispatchOS Stand Alone
Zum Produkt

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

Im Ancomox-StoreLicenseOS — TabletKostenlos
Zum Produkt

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

Im Ancomox-StoreGaragen System
Zum Produkt

Ü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

Im Ancomox-StorePremium Dealership
Zum Produkt

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 Warehouse — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Warehouse
Zum Produkt

Ancomox Warehouse — private Lagerhallen für FiveM: kaufen oder mieten, in vier Stufen ausbauen, über mehrere Eingänge betreten und per Zugangs-Key mit dem Team teilen. ESX · QBCore · QBox mit Auto-Erkennung, eigene NUI, vollständig serverseitig abgesichert.

Überblick

ancomox_warehouse stellt beliebig viele Lagertypen bereit (ausgeliefert: Kleines, Mittleres und Großes Lager). Jeder Lagertyp hat mehrere Eingänge auf der Map, die der Spieler einzeln freischaltet. Betreten wird über ox_target, qb-target oder klassische Marker — je nachdem, was auf dem Server läuft.

Kernmerkmale:

  • Kaufen oder mieten — pro Server konfigurierbar; der Spieler entscheidet beim Erwerb. Der Miet-Modus umfasst Intervalle, automatischen Einzug, Kulanzzeit, Sperrung und optionale Zwangsauflösung.
  • Private Instanzen — jedes Lager läuft in einem eigenen Routing Bucket. Besitzer und alle Key-Inhaber teilen sich automatisch dieselbe Instanz; freigewordene Buckets werden wiederverwendet.
  • Vier Ausbaustufen je Lagertyp mit eigenen Werten für Slots, Traglast und Preis. Nach dem Kauf wird der Stash sofort mit neuer Kapazität registriert — ohne Neustart.
  • Zugangs-Keys mit Einzelrechten — Inventar, Zugänge kaufen, Ausbauen, Keys vergeben.
  • Verwaltung am Eingang — das komplette Verwaltungsmenü öffnet sich auch vor der Tür. Das ist der einzige praktikable Weg, einem Spieler einen Key zu geben, der noch keinen Zutritt hat, und der einzige Weg, ein wegen Mietrückstand gesperrtes Lager wieder freizukaufen.
  • Serverautoritativ — Preise, Besitz, Rechte und Distanzen werden ausschließlich serverseitig geprüft. Dazu Rate-Limit pro Aktion und ein Strike-System mit Auto-Kick.
  • Offene Bridgebridge/client.lua und bridge/server.lua sind unverschlüsselt und dienen der Anbindung eigener Frameworks oder Inventare.

Wichtig: Ohne OneSync funktioniert die Instanzierung nicht — mehrere Spieler würden sich dann im selben Interior sehen. Setze in dem Fall Config.Instancing.enabled = false und sei dir bewusst, dass Lager dann nicht privat sind.

Voraussetzungen

Abhängigkeit Pflicht Zweck / Hinweis
oxmysql Ja Datenbank-Layer. Muss vor der Ressource starten.
es_extended oder qb-core oder qbx_core Ja (eines) Wird über Config.Framework = 'auto' selbst erkannt. QBox läuft über den Wert 'qb'.
ox_inventory, qb-inventory oder qs-inventory Ja (eines) Alternativ eigenes Inventar über Config.Inventory = 'custom' und die Bridge. ox_inventory wird empfohlen, weil nur dort Slots und Gewicht pro Stash gesetzt werden können.
OneSync Ja Für Routing Buckets (private Interiors).
ox_target oder qb-target Optional Ohne Target-Script greift automatisch der Marker-Modus mit Tastendruck.
ox_lib Optional Alternative Notifications und TextUI (Config.Notify = 'ox_lib').

Installation

  1. Ordner ancomox_warehouse in das resources-Verzeichnis kopieren (z. B. resources/[ANCOMOX]/ancomox_warehouse).
  2. In der server.cfg eintragen — nach Framework, oxmysql und Inventar:
ensure oxmysql
ensure es_extended        # oder qb-core / qbx_core
ensure ox_inventory
ensure ox_target          # optional
ensure ancomox_warehouse
  1. Die Tabelle wird beim ersten Start automatisch angelegt. Fehlen dem MySQL-Benutzer die CREATE-Rechte, importiere install/ancomox_warehouse.sql von Hand.
  2. config/config.lua und config/warehouses.lua anpassen.
  3. Server neu starten und die Konsole prüfen:
[ANC-WH] Framework: esx · Inventar: ox_inventory · Version 2.1.1
[ANC-WH] 0 Lager aus der Datenbank geladen.
[ANC-WH] Interaktion: ox_target

Startreihenfolge: Erscheint „Kein unterstütztes Framework gefunden!", startet die Ressource vor dem Framework. Die Erkennung versucht es zwar bis zu 10 Sekunden lang, die server.cfg sollte aber trotzdem korrekt sortiert sein.

Datenbank

Die Ressource legt genau eine Tabelle an: anc_warehouses. Es wird nichts an Framework-Tabellen verändert.

Spalte Typ Bedeutung
id INT UNSIGNED AI Primärschlüssel.
owner VARCHAR(64) ESX-Identifier bzw. QB-citizenid.
warehouse VARCHAR(32) id aus Config.Warehousesnachträglich nie ändern.
custom_name VARCHAR(32) Vom Spieler vergebener Lagername (leer = Standardlabel).
tier TINYINT UNSIGNED Ausbaustufe, 1 = Basis.
entries TEXT JSON-Array der freigeschalteten Zugangs-Indizes, z. B. [1,3].
akeys TEXT JSON-Objekt identifier → { name, permissions, grantedAt }.
tenure VARCHAR(8) owned oder rented.
rent_until INT UNSIGNED Unix-Timestamp des Mietendes, 0 = kein Mietvertrag.
locked TINYINT(1) Gesperrt wegen Mietrückstand.
inside TINYINT(1) Besitzer war beim letzten Logout im Lager (für restoreOnRelog).
last_entry TINYINT UNSIGNED Zuletzt genutzter Eingang.
created_at INT UNSIGNED Unix-Timestamp des Erwerbs.

Ein UNIQUE KEY auf (owner, warehouse) stellt sicher, dass ein Spieler jeden Lagertyp nur einmal besitzen kann.

Performance: Alle Datensätze liegen nach dem Start im RAM. Die Datenbank wird ausschließlich bei echten Änderungen beschrieben — nicht bei jeder Interaktion.

Konfiguration

Alles Relevante liegt in config/ und bleibt auch bei Escrow-Schutz editierbar.

Kompatibilität — config/config.lua

Key Standard Beschreibung
Config.Debug false Ausführliche Konsolenausgaben inkl. gemessener Distanzen. Nur zum Testen.
Config.Locale 'de' 'de' oder 'en'. Weitere Sprachen als Datei in config/locales/.
Config.Framework 'auto' 'auto' · 'esx' · 'qb' (QBox nutzt ebenfalls 'qb').
Config.Inventory 'auto' 'auto' · 'ox_inventory' · 'qb-inventory' · 'qs-inventory' · 'custom'. 'custom' wird nie automatisch erkannt.
Config.Target 'auto' 'auto' · 'ox_target' · 'qb-target' · 'marker'.
Config.Notify 'anc' 'anc' (eigene Toasts) · 'ox_lib' · 'framework'.

Geld — Config.Money

Key Standard Beschreibung
purchaseAccount 'bank' Konto für Kauf, Zugänge und Ausbau. 'bank' oder 'cash'.
rentAccount 'bank' Konto für Mietzahlungen und automatischen Einzug.
symbol '$' Währungssymbol in der UI.
symbolBefore true Symbol vor (true) oder hinter (false) dem Betrag.
thousandsSep '.' Tausendertrennzeichen. Leerstring = keine Gruppierung.

Interface — Config.UI

Key Standard Beschreibung
accent '#00E5A0' Akzentfarbe des gesamten Interfaces. Färbt auch die Ingame-Marker, sofern useAccentColor aktiv ist.
accentAlt '#22D3EE' Zweite Farbe für Verläufe.
danger '#FF4D6D' Warnfarbe (Verkauf, abgelaufene Miete).
backdropDim 0.55 Abdunklung des Spielbilds hinter der UI. 0.0 bis 0.95.
panelOpacity 0.92 Deckkraft des Fensters. Sinnvoll zwischen 0.80 und 1.00.
gameBlur true Ingame-Weichzeichner hinter der UI (TriggerScreenblurFadeIn).
toastPosition 'top-right' 'top-right' · 'top-left' · 'top-center' · 'bottom-right' · 'bottom-left'.
sounds true Synthetisches Sound-Feedback der UI (benötigt keine Audiodateien).
brand / brandSub 'ANCOMOX' / 'Storage Division' Beschriftung im Kopfbereich der NUI.

Kein backdrop-filter ergänzen. FiveMs CEF-Schicht kennt das Spielbild nicht und füllt eine so gestaltete Fläche komplett schwarz — genau daher stammen die typischen schwarzen Kästen hinter Dialogen. Die UI arbeitet deshalb durchgehend mit echter Transparenz über rgba(...); der Weichzeichner kommt vom Spiel selbst.

Interaktion — Config.Interaction

Key Standard Beschreibung
key 38 Taste im Marker-Modus (38 = E).
keyLabel 'E' Beschriftung in der TextUI. Muss zu key passen.
manageAtEntrance true Verwaltung auch am Eingang öffnen. Dringend aktiviert lassen — ohne diese Option lässt sich niemandem ein Key geben, der noch keinen Zutritt hat.
keyManage 47 Zweittaste dafür im Marker-Modus (47 = G). Im Target-Modus erscheint stattdessen ein eigener Menüeintrag.
keyManageLabel 'G' Beschriftung der Zweittaste.
drawDistance 12.0 Ab welcher Entfernung Marker gezeichnet werden.
interactDistance 1.6 Ab welcher Entfernung interagiert werden kann.
showMarkers true Marker auch im Target-Modus zeichnen. Empfohlen: Target-Zonen sind unsichtbar.
targetRadius 1.6 Radius der Zonen im Interior (Stash, Verwaltung, Ausgang).
targetRadiusEntry 2.0 Radius der Eingänge draußen — Türen brauchen etwas mehr Spiel.
targetDistance 2.5 Maximale Distanz, aus der eine Option angezeigt wird. Zieht automatisch mit dem Zonenradius mit.
markerType, markerScale, markerBob, markerRotate 21, vector3(.35,.35,.25), true, true Aussehen und Animation der Marker.
useAccentColor true Marker übernehmen Config.UI.accent. Bei false gelten colorEntry, colorStash, colorManage und colorExit.

Instanzierung — Config.Instancing

Key Standard Beschreibung
enabled true Private Interiors über Routing Buckets.
baseBucket 51000 Startwert der Bucket-IDs. Ab hier wird aufsteigend so viel belegt, wie gleichzeitig Lager betreten sind.

Bucket-Konflikte: Nutzt ein anderes Script (Housing, Gangs, MLOs) denselben Bereich, „verschwinden" Spieler gegenseitig von der Map. In dem Fall baseBucket auf einen freien Wert setzen.

Limits — Config.Limits

Key Standard Beschreibung
maxWarehousesPerPlayer 3 Maximale Anzahl gleichzeitig besessener Lager. 0 = unbegrenzt.
maxKeysPerWarehouse 8 Maximale Anzahl Zugangs-Keys pro Lager.
allowCustomName true Individueller Lagername erlaubt.
customNameMaxLength 24 Maximale Länge in Zeichen (UTF-8-sicher gekürzt, keine zerschnittenen Umlaute).

Verkauf — Config.Sell

Key Standard Beschreibung
enabled true Verkauf grundsätzlich erlauben.
refundPercent 0.60 Anteil des investierten Werts, den der Spieler zurückbekommt.
refundEntries / refundUpgrades true / true Ob gekaufte Zugänge bzw. Ausbaustufen in den Wert einfließen.
requireEmptyStash true Lager muss leer sein. Kann das Inventar den Inhalt nicht melden, wird die Prüfung übersprungen statt den Verkauf zu blockieren.
wipeStash true Stash beim Verkauf leeren, damit ein Neukauf keine alten Items enthält.
requireTypeConfirm true Spieler muss den Lagernamen zur Bestätigung eintippen.
payoutAccount 'bank' Konto für die Erstattung.

Gemietete Lager können nicht verkauft werden — der Spieler besitzt sie nicht. Der Verkaufsbereich verschwindet in dem Fall aus der UI.

Zugangs-Keys — Config.Keys

Key Standard Beschreibung
enabled true Key-System aktiv. Bei false verschwindet der Team-Tab.
giveDistance 3.0 Maximale Entfernung zum Empfänger. Serverseitig mit kleinem Puffer geprüft.
defaultPermissions { stash = true, … } Vorauswahl beim Hinzufügen einer Person.
accessWhileOwnerOffline true Dürfen Key-Inhaber das Lager auch betreten, wenn der Besitzer offline ist?

Verfügbare Rechte:

Recht Erlaubt
stash Lagerinventar öffnen.
entry Weitere Zugänge freischalten (zahlt selbst).
upgrade Ausbaustufen kaufen (zahlt selbst).
keys Weitere Keys vergeben und entziehen.

Rechteausweitung ist ausgeschlossen: Nur der Besitzer kann das Recht keys weitergeben, und ein Key-Inhaber kann seine eigenen Rechte nicht bearbeiten. Beide Versuche werden serverseitig abgelehnt und als Strike gewertet.

Miete — Config.Rent

Key Standard Beschreibung
enabled true Miet-Modus aktiv. Bei false verschwindet der Mieten-Button aus der UI.
pricePercent 0.04 Miete pro Intervall als Anteil des Kaufpreises (4 %).
intervalDays 7 Länge eines Intervalls in Tagen.
graceHours 48 Kulanzzeit nach Ablauf, bevor gesperrt wird.
autoDebit true Automatischer Einzug, sofern der Besitzer online ist und genug Guthaben hat.
maxPrepaid 8 Maximal im Voraus zahlbare Intervalle.
deleteAfterDays 14 Tage nach der Sperrung bis zur Zwangsauflösung. 0 = nie löschen.
wipeStashOnDelete false Ob der Stash bei der Auflösung geleert wird.
checkIntervalMin 15 Prüfzyklus in Minuten.

Ablauf eines überfälligen Mietvertrags: Erinnerung (letzte 24 h) → automatischer EinzugKulanzzeitSperrung (alle Spieler werden aus der Instanz entfernt) → optional Auflösung. Ein gesperrtes Lager lässt sich am Eingang über die Verwaltung wieder freikaufen; alternativ per /warehouse unlock.

Sicherheit — Config.Security

Key Standard Beschreibung
validateDistance true Serverseitige Distanzprüfung bei jeder Aktion.
distanceTolerance 12.0 Erlaubte Abweichung in Metern. Puffer für Lag und für Türen, die je Lagergröße auseinanderliegen.
strikeDistance 100.0 Ab dieser Entfernung gilt eine Aktion als Exploit-Versuch. Darunter wird nur abgelehnt, ohne Strafpunkt.
eventCooldown 750 Mindestabstand zweier Events derselben Aktion in ms. Kauf, Keys und UI haben getrennte Kontingente.
strikeWarn / strikeKick 5 / 12 Schwellen für Log-Eintrag bzw. automatischen Kick.
kickReason '[ANC Warehouse] …' Kick-Nachricht.

Admin, Blips & Sonstiges

Key Standard Beschreibung
Config.Admin.groups { 'admin', 'superadmin', 'god', 'mod' } ESX-Gruppen bzw. QB-Permissions mit Adminrechten.
Config.Admin.useAce true Zusätzlich die ACE-Permission ancomox_warehouse.admin akzeptieren.
Config.Admin.command 'warehouse' Name des Admin-Befehls.
Config.Blips.enabled true Blips für Lagereingänge anzeigen.
Config.Blips.onlyOwned false Blips nur für bereits freigeschaltete Zugänge.
Config.Misc.restoreOnRelog true Spieler landet nach dem Relog wieder im Lager.
Config.Misc.fadeTime 500 Bildschirm-Fade beim Betreten und Verlassen in ms. 0 = aus.
Config.Misc.loadInteriors true IPLs beim Start anfordern.

Lager definieren — config/warehouses.lua

Jeder Eintrag in Config.Warehouses beschreibt einen Lagertyp:

{
    id      = 'bunker',                 -- eindeutig, NIE nachträglich ändern
    label   = 'Bunker',
    tagline = 'Tief unter der Erde.',   -- Werbetext im Kaufdialog
    price   = 2500000,
    order   = 4,                        -- Sortierung im Kaufdialog
    theme   = { from = '#10B981', to = '#059669' },
    icon    = 'warehouse',              -- box | warehouse | crown
    ipl     = nil,                      -- optionales IPL

    interior = {
        spawn  = vector4(x, y, z, heading),  -- Ankunftspunkt
        stash  = vector3(x, y, z),           -- Lagerbestand
        manage = vector3(x, y, z),           -- Verwaltungspult
        exit   = vector3(x, y, z),           -- Ausgang
    },

    perks = { 'Feature 1', 'Feature 2' },    -- Bullet-Points im Kaufdialog

    tiers = {   -- Index 1 ist IMMER die Basis und kostet nichts
        { label = 'Basis',    slots = 100, weight = 500000, price = 0 },
        { label = 'Ausbau I', slots = 150, weight = 900000, price = 1500000 },
    },

    entries = {
        {
            index  = 1,
            label  = 'Sandy Shores',
            price  = 200000,
            coords = vector4(x, y, z, heading),
            blip   = { sprite = 473, color = 2, scale = 0.75, label = 'Bunker' },
        },
    },
}
Feld Hinweis
id Wird als Fremdschlüssel in der Datenbank gespeichert. Eine spätere Änderung macht alle vorhandenen Lager dieses Typs unerreichbar.
weight Angabe in Gramm (ox_inventory-Standard): 500000 = 500 kg.
tiers Beliebig viele Stufen. Wird die Liste nachträglich verkürzt, werden vorhandene Lager beim Laden automatisch auf die höchste noch existierende Stufe begrenzt.
entries[].index Muss eindeutig und stabil sein — er wird in anc_warehouses.entries gespeichert.
blip Optional. Fehlt der Block, greifen Standardwerte.

Koordinaten übernehmen: An die gewünschte Stelle stellen, /warehouse pos ausführen und die Zeile aus der F8-Konsole in die Config kopieren. Der Befehl gibt sowohl vector3 als auch vector4 aus.

Eigenes Framework oder Inventar anbinden

Setze Config.Inventory = 'custom' und fülle die vier Funktionen in der Bridge aus. Mehr braucht das Script nicht.

Serverbridge/server.lua:

-- Stash anlegen bzw. Kapazität aktualisieren.
-- Aufruf: beim Serverstart, beim Kauf und nach jedem Ausbau.
function CustomInventory.RegisterStash(id, label, slots, weight, owner) return false end

-- Items zurückgeben. Wird nur für die Leer-Prüfung beim Verkauf gebraucht.
-- nil = nicht ermittelbar; das Script überspringt die Prüfung dann.
function CustomInventory.GetItems(id) return nil end

-- Stash vollständig leeren (Verkauf, Zwangsauflösung).
function CustomInventory.Clear(id) return false end

Clientbridge/client.lua:

-- Stash für den Spieler öffnen. true = geöffnet.
function CustomInventory.Open(stashId, label, slots, weight) return false end

Zu den Ausbaustufen: Das Script übergibt bei jeder Stufe die neuen Werte für Slots und Gewicht. Kann dein Inventar die Kapazität pro Stash nicht begrenzen — das gilt z. B. für esx_addoninventory — funktioniert das Lager zwar, der Ausbau hat aber keine spürbare Wirkung. Reduziere die Tiers in dem Fall lieber auf eine Stufe, statt Kapazität zu verkaufen, die nie greift.

Vollständige Config-Dateien

config/config.lua325 Zeilen · vollständige Datei
--[[
    ╔══════════════════════════════════════════════════════════════════════╗
    ║  ANC WAREHOUSE  ·  HAUPT-KONFIGURATION                               ║
    ╚══════════════════════════════════════════════════════════════════════╝
]]

Config = {}

--──────────────────────────────────────────────────────────────────────────────
-- ALLGEMEIN
--──────────────────────────────────────────────────────────────────────────────

Config.Debug  = false   -- Ausführliche Konsolenausgaben (nur zum Testen!)
Config.Locale = 'de'    -- 'de' | 'en'  (weitere: config/locales/)

--──────────────────────────────────────────────────────────────────────────────
-- KOMPATIBILITÄT  ·  'auto' erkennt automatisch, was auf dem Server läuft
--──────────────────────────────────────────────────────────────────────────────

Config.Framework = 'auto'  -- 'auto' | 'esx' | 'qb'  (QBox läuft über 'qb')
-- 'custom' bindet dein eigenes Inventar über CustomInventory in
-- bridge/server.lua und bridge/client.lua an (wird nie automatisch erkannt).
Config.Inventory = 'auto'  -- 'auto' | 'ox_inventory' | 'qb-inventory' | 'qs-inventory' | 'custom'
Config.Target    = 'auto'  -- 'auto' | 'ox_target' | 'qb-target' | 'marker'
-- 'anc'       = eigenes Premium-Toast-Design (empfohlen, passt zur UI)
-- 'ox_lib'    = ox_lib Notify
-- 'framework' = ESX/QBCore Standard-Notify
Config.Notify    = 'anc'   -- 'anc' | 'ox_lib' | 'framework' | 'auto'

--──────────────────────────────────────────────────────────────────────────────
-- GELD
--──────────────────────────────────────────────────────────────────────────────

Config.Money = {
    -- Womit werden Lager bezahlt? 'bank' | 'cash'
    purchaseAccount = 'bank',
    -- Womit wird die Miete abgebucht? 'bank' | 'cash'
    rentAccount     = 'bank',
    -- Währungssymbol in der UI
    symbol          = '$',
    -- Symbol vor (true) oder hinter (false) dem Betrag
    symbolBefore    = true,
    -- Tausendertrennzeichen für die UI
    thousandsSep    = '.',
}

--──────────────────────────────────────────────────────────────────────────────
-- UI  ·  Dark-Glass Theme
--──────────────────────────────────────────────────────────────────────────────

Config.UI = {
    -- Akzentfarbe des gesamten Interfaces (HEX). Beispiele:
    -- Neon-Mint '#00E5A0' · Cyan '#22D3EE' · Violett '#8B5CF6' · Gold '#F5B301'
    accent      = '#00E5A0',
    -- Zweite Akzentfarbe für Verläufe
    accentAlt   = '#22D3EE',
    -- Warn-/Gefahrfarbe (Verkaufen, Miete abgelaufen)
    danger      = '#FF4D6D',

    -- ── Durchsichtigkeit ────────────────────────────────────────────────
    -- Wie stark wird das Spielbild hinter der UI abgedunkelt?
    -- 0.0 = gar nicht · 0.55 = Standard · 1.0 = komplett schwarz
    backdropDim  = 0.55,
    -- Deckkraft des Fensters selbst. Sinnvoll zwischen 0.80 und 1.00 —
    -- niedriger = mehr Spielbild sichtbar, aber schlechter lesbar.
    panelOpacity = 0.92,
    -- Weichzeichner hinter der UI. Das ist ein ECHTER Ingame-Effekt
    -- (kein CSS-Blur — den kann FiveM nicht über das Spielbild legen).
    gameBlur     = true,

    -- Wo erscheinen die Benachrichtigungen? Falls deine HUD oben rechts
    -- sitzt, stell hier z. B. 'top-center' oder 'bottom-right' ein.
    -- 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left'
    toastPosition = 'top-right',

    -- Sound-Feedback in der UI
    sounds      = true,
    -- Markenname im Header
    brand       = 'ANCOMOX',
    -- Untertitel im Header
    brandSub    = 'Storage Division',
}

--──────────────────────────────────────────────────────────────────────────────
-- INTERAKTION
--──────────────────────────────────────────────────────────────────────────────

Config.Interaction = {
    -- Taste bei Marker-Modus (38 = E)
    key                = 38,
    -- Beschriftung der Taste in der TextUI. Muss zu `key` passen.
    -- (GTA-Tokens wie ~INPUT_CONTEXT~ funktionieren NUR in der nativen
    --  Hilfeanzeige — in ox_lib oder eigener NUI werden sie als Text
    --  ausgegeben. Deshalb hier den Buchstaben eintragen.)
    keyLabel           = 'E',
    -- Ab welcher Distanz wird der Marker gezeichnet
    drawDistance       = 12.0,
    -- Ab welcher Distanz kann interagiert werden
    interactDistance   = 1.6,

    -- ── Verwaltung am Eingang ──────────────────────────────────────────
    -- Besitzer und Key-Inhaber können die Verwaltung direkt am Eingang
    -- öffnen, ohne das Lager zu betreten. Das ist der einzige Weg, jemandem
    -- einen Zugangs-Key zu geben: Wer noch keinen hat, kommt nicht in die
    -- Instanz und wäre "nie in der Nähe". Ebenso lässt sich damit die Miete
    -- eines bereits gesperrten Lagers nachzahlen.
    manageAtEntrance   = true,
    -- Zweittaste dafür im Marker-Modus (47 = G).
    -- Im Target-Modus erscheint stattdessen ein eigener Menüeintrag.
    keyManage          = 47,
    keyManageLabel     = 'G',

    -- Marker auch dann anzeigen, wenn ox_target/qb-target aktiv ist.
    -- Dringend empfohlen: Target-Zonen sind unsichtbar, und der Spieler
    -- muss IN der Zone stehen. Ohne Marker sucht er den Punkt blind.
    showMarkers        = true,
    -- Marker-Design
    markerType         = 21,
    markerScale        = vector3(0.35, 0.35, 0.25),
    markerBob          = true,   -- schwebende Animation
    markerRotate       = true,
    -- Farben (r,g,b,a) — werden bei UI.accent automatisch überschrieben,
    -- wenn `useAccentColor = true`
    useAccentColor     = true,
    colorEntry         = { 0, 229, 160, 140 },
    colorStash         = { 245, 179, 1, 140 },
    colorManage        = { 34, 211, 238, 140 },
    colorExit          = { 255, 77, 109, 140 },
    -- ── ox_target / qb-target ──────────────────────────────────────────
    -- Radius der Interaktionszone in Metern.
    -- WICHTIG: Bei ox_target muss der Spieler IN dieser Kugel stehen.
    -- Zu klein = "ich sehe den Punkt, kann ihn aber nicht anvisieren".
    -- 1.4–2.0 ist ein guter Bereich; kleiner nur, wenn zwei Punkte sehr
    -- dicht beieinander liegen.
    targetRadius       = 1.6,
    -- Radius für die Eingänge draußen (Türen brauchen etwas mehr Spiel)
    targetRadiusEntry  = 2.0,
    -- Maximale Distanz, aus der eine Option angezeigt wird
    targetDistance     = 2.5,
}

--──────────────────────────────────────────────────────────────────────────────
-- INSTANZIERUNG  ·  Jedes Lager ist privat (Routing Buckets)
--──────────────────────────────────────────────────────────────────────────────

Config.Instancing = {
    enabled    = true,
    -- Startwert für Buckets. Muss frei von anderen Skripten sein!
    -- Besitzer und alle Key-Inhaber teilen sich automatisch dieselbe Instanz.
    baseBucket = 51000,
}

--──────────────────────────────────────────────────────────────────────────────
-- BESITZ-LIMITS
--──────────────────────────────────────────────────────────────────────────────

Config.Limits = {
    -- Wie viele Lager darf ein Spieler gleichzeitig besitzen? (0 = unbegrenzt)
    maxWarehousesPerPlayer = 3,
    -- Maximale Anzahl Zugangs-Keys pro Lager
    maxKeysPerWarehouse    = 8,
    -- Individueller Lagername erlaubt
    allowCustomName        = true,
    customNameMaxLength    = 24,
}

--──────────────────────────────────────────────────────────────────────────────
-- VERKAUFEN
--──────────────────────────────────────────────────────────────────────────────

Config.Sell = {
    enabled            = true,
    -- Wie viel Prozent des Kaufpreises bekommt der Spieler zurück (0.0 – 1.0)
    refundPercent      = 0.60,
    -- Werden gekaufte Eingänge mit erstattet?
    refundEntries      = true,
    -- Werden gekaufte Upgrades mit erstattet?
    refundUpgrades     = true,
    -- Lager muss leer sein bevor verkauft werden kann
    requireEmptyStash  = true,
    -- Stash beim Verkauf leeren (verhindert, dass alte Items nach einem
    -- Neukauf desselben Lagers wieder auftauchen)
    wipeStash          = true,
    -- Sicherheitsabfrage: Spieler muss den Lagernamen eintippen
    requireTypeConfirm = true,
    -- Auf welches Konto geht die Erstattung
    payoutAccount      = 'bank',
}

--──────────────────────────────────────────────────────────────────────────────
-- ZUGANGS-KEYS  ·  Mitbesitzer / Mitarbeiter
--──────────────────────────────────────────────────────────────────────────────

Config.Keys = {
    enabled = true,
    -- Maximale Distanz zum Spieler, dem ein Key gegeben wird
    giveDistance = 3.0,
    -- Rechte, die beim Vergeben standardmäßig aktiv sind
    defaultPermissions = { stash = true, entry = false, upgrade = false, keys = false },
    -- Dürfen Key-Inhaber das Lager auch betreten, wenn der Besitzer offline ist?
    accessWhileOwnerOffline = true,
}

--──────────────────────────────────────────────────────────────────────────────
-- MIET-MODUS  (optional)
--──────────────────────────────────────────────────────────────────────────────

Config.Rent = {
    enabled          = true,
    -- Miete pro Intervall = Kaufpreis * pricePercent
    pricePercent     = 0.04,
    -- Länge eines Miet-Intervalls in Tagen
    intervalDays     = 7,
    -- Kulanzzeit in Stunden nach Ablauf, bevor das Lager gesperrt wird
    graceHours       = 48,
    -- Automatischer Einzug vom Bankkonto, wenn genug Guthaben da ist
    autoDebit        = true,
    -- Wie viele Intervalle darf man im Voraus zahlen
    maxPrepaid       = 8,
    -- Wird ein gesperrtes Lager nach X Tagen komplett gelöscht? (0 = nie)
    deleteAfterDays  = 14,
    -- Beim Löschen: Items in den Besitz des Servers (true) oder Stash behalten (false)
    wipeStashOnDelete = false,
    -- Wie oft prüft der Server die Mietverträge (Minuten)
    checkIntervalMin = 15,
}

--──────────────────────────────────────────────────────────────────────────────
-- SICHERHEIT  ·  Anti-Cheat / Anti-Exploit
--──────────────────────────────────────────────────────────────────────────────

Config.Security = {
    -- Serverseitige Distanzprüfung bei jedem Kauf/Interaktion
    validateDistance      = true,
    -- Erlaubte Abweichung in Metern (Puffer für Lag und für Standorte,
    -- an denen die Türen der einzelnen Lagergrößen auseinanderliegen).
    -- Zu klein gewählt = Spieler bekommen beim Kauf "Du bist zu weit entfernt".
    distanceTolerance     = 12.0,
    -- Ab dieser Entfernung (in Metern) gilt eine Aktion als Exploit-Versuch
    -- und zählt als Strike. Darunter wird nur abgelehnt, ohne Strafpunkt —
    -- damit ehrliche Spieler nicht wegen Lag oder Türabständen auffallen.
    strikeDistance        = 100.0,
    -- Minimaler Abstand zwischen zwei Events desselben Spielers (ms)
    eventCooldown         = 750,
    -- Ab wie vielen geblockten Events wird geloggt / gekickt
    strikeWarn            = 5,
    strikeKick            = 12,
    kickReason            = '[ANC Warehouse] Ungültige Aktionen erkannt.',
    -- Spieler bei Verlassen des Servers automatisch aus der Instanz nehmen
    forceLeaveOnDrop      = true,
}

--──────────────────────────────────────────────────────────────────────────────
-- DISCORD LOGS
--──────────────────────────────────────────────────────────────────────────────

Config.Logs = {
    enabled  = false,
    webhook  = '',                       -- https://discord.com/api/webhooks/...
    botName  = 'ANC Warehouse',
    avatar   = '',                       -- optionale Bild-URL
    footer   = 'ANCOMOX · Warehouse System',
    -- Welche Ereignisse geloggt werden
    events   = {
        purchase = true,
        entry    = true,
        upgrade  = true,
        sell     = true,
        rent     = true,
        key      = true,
        stash    = false, -- kann sehr gesprächig werden
        admin    = true,
        security = true,
    },
    colors   = {
        purchase = 3066993,   -- grün
        entry    = 3447003,   -- blau
        upgrade  = 10181046,  -- violett
        sell     = 15158332,  -- rot
        rent     = 15844367,  -- gold
        key      = 1752220,   -- türkis
        stash    = 9807270,   -- grau
        admin    = 15105570,  -- orange
        security = 10038562,  -- dunkelrot
    },
}

--──────────────────────────────────────────────────────────────────────────────
-- ADMIN
--──────────────────────────────────────────────────────────────────────────────

Config.Admin = {
    -- ESX-Gruppen / QB-Permissions die als Admin gelten
    groups  = { 'admin', 'superadmin', 'god', 'mod' },
    -- Zusätzlich: ACE-Permission "ancomox_warehouse.admin"
    useAce  = true,
    command = 'warehouse',
}

--──────────────────────────────────────────────────────────────────────────────
-- BLIPS
--──────────────────────────────────────────────────────────────────────────────

Config.Blips = {
    enabled = true,
    -- Blips nur anzeigen, wenn der Spieler das Lager besitzt
    onlyOwned = false,
    -- Besitzte Lager bekommen einen anderen Farbton
    ownedColor = 2,
    shortRange = true,
}

--──────────────────────────────────────────────────────────────────────────────
-- SONSTIGES
--──────────────────────────────────────────────────────────────────────────────

Config.Misc = {
    -- Spieler wird nach Relog automatisch zurück ins Lager gesetzt
    restoreOnRelog   = true,
    -- Bildschirm-Fade beim Betreten/Verlassen (ms, 0 = aus)
    fadeTime         = 500,
    -- IPLs beim Start laden (bei manchen Servern schon durch andere Skripte geladen)
    loadInteriors    = true,
}
config/warehouses.lua201 Zeilen · vollständige Datei
--[[
    ╔══════════════════════════════════════════════════════════════════════╗
    ║  ANC WAREHOUSE  ·  LAGER-DEFINITIONEN                                 ║
    ║                                                                       ║
    ║  Aufbau eines Lagers:                                                 ║
    ║    id          → eindeutiger String-Key (NICHT nachträglich ändern!)  ║
    ║    label       → Anzeigename                                          ║
    ║    tagline     → kurzer Werbetext im Kaufdialog                       ║
    ║    price       → Kaufpreis                                            ║
    ║    ipl         → Interior IPL das geladen wird                        ║
    ║    interior    → Koordinaten innerhalb des Lagers                     ║
    ║    tiers       → Ausbaustufen (Index 1 = Basis, kostenlos)            ║
    ║    entries     → Zugänge, die einzeln freigeschaltet werden           ║
    ║    perks       → Bullet-Points für den Kaufdialog                     ║
    ╚══════════════════════════════════════════════════════════════════════╝
]]

Config.Warehouses = {

    ---------------------------------------------------------------------------
    -- KLEINES LAGER
    ---------------------------------------------------------------------------
    {
        id      = 'small',
        label   = 'Kleines Lager',
        tagline = 'Der ideale Einstieg. Diskret, günstig und sofort einsatzbereit.',
        price   = 300000,
        order   = 1,

        -- Farbverlauf der Karte im Kaufdialog
        theme   = { from = '#0EA5E9', to = '#22D3EE' },
        icon    = 'box',

        ipl     = 'ex_exec_warehouse_placement_interior_1_int_warehouse_s_dlc_milo',

        interior = {
            spawn  = vector4(1087.34, -3099.31, -38.99, 268.0),
            stash  = vector3(1095.05, -3098.16, -38.99),
            manage = vector3(1088.34, -3101.39, -38.99),
            exit   = vector3(1087.49, -3099.38, -38.99),
        },
        perks = {
            'Privates Interior — niemand sieht dein Lager',
            'Bis zu 3 Zugänge in ganz Los Santos',
            '4 Ausbaustufen für mehr Platz',
            'Zugangs-Keys für dein Team',
        },

        --  Index 1 ist IMMER die Basis-Stufe und kostet nichts.
        tiers = {
            { label = 'Basis',      slots = 40,  weight = 100000,  price = 0       },
            { label = 'Ausbau I',   slots = 60,  weight = 200000,  price = 250000  },
            { label = 'Ausbau II',  slots = 85,  weight = 350000,  price = 500000  },
            { label = 'Ausbau III', slots = 110, weight = 500000,  price = 900000  },
        },

        entries = {
            {
                index  = 1,
                label  = 'Paleto Bay',
                price  = 50000,
                coords = vec4(37.85, 6454.09, 31.43, 41.528),
                blip   = { sprite = 473, color = 3, scale = 0.75, label = 'Kleines Lager · Paleto Bay' },
            },
            {
                index  = 2,
                label  = 'Hafen / City',
                price  = 50000,
                coords = vec4(159.18, -2944.14, 7.22, 93.54),
                blip   = { sprite = 473, color = 3, scale = 0.75, label = 'Kleines Lager · Hafen' },
            },
            {
                index  = 3,
                label  = 'Grapeseed',
                price  = 50000,
                coords = vec4(378.05, 3577.05, 33.71, 263.60),
                blip   = { sprite = 473, color = 3, scale = 0.75, label = 'Kleines Lager · Grapeseed' },
            },
        },
    },

    ---------------------------------------------------------------------------
    -- MITTLERES LAGER
    ---------------------------------------------------------------------------
    {
        id      = 'medium',
        label   = 'Mittleres Lager',
        tagline = 'Mehr Fläche, mehr Umschlag. Für Crews, die ernst machen.',
        price   = 600000,
        order   = 2,

        theme   = { from = '#8B5CF6', to = '#D946EF' },
        icon    = 'warehouse',

        ipl     = 'ex_exec_warehouse_placement_interior_0_int_warehouse_m_dlc_milo',

        interior = {
            spawn  = vector4(1048.42, -3097.06, -38.99, 268.36),
            stash  = vector3(1053.179, -3096.95, -38.99),
            manage = vector3(1048.98, -3100.56, -38.99),
            exit   = vector3(1048.42, -3097.06, -38.99),
        },
        perks = {
            'Deutlich größeres Interior',
            'Bis zu 3 Zugänge · strategisch verteilt',
            'Höhere Traglast & mehr Slots',
            'Ideal für 2–5 Spieler mit Keys',
        },
        tiers = {
            { label = 'Basis',      slots = 70,  weight = 250000,  price = 0       },
            { label = 'Ausbau I',   slots = 100, weight = 450000,  price = 500000  },
            { label = 'Ausbau II',  slots = 140, weight = 700000,  price = 1000000 },
            { label = 'Ausbau III', slots = 180, weight = 1000000, price = 1800000 },
        },

        entries = {
            {
                index  = 1,
                label  = 'Paleto Bay',
                price  = 100000,
                coords = vec4(44.54, 6461.002, 31.42, 47.43),
                blip   = { sprite = 473, color = 5, scale = 0.75, label = 'Mittleres Lager · Paleto Bay' },
            },
            {
                index  = 2,
                label  = 'Hafen / City',
                price  = 100000,
                coords = vector3(159.05, -2928.17, 7.23),
                blip   = { sprite = 473, color = 5, scale = 0.75, label = 'Mittleres Lager · Hafen' },
            },
            {
                index  = 3,
                label  = 'Grapeseed',
                price  = 100000,
                coords = vector3(379.48, 3583.62, 33.29),
                blip   = { sprite = 473, color = 5, scale = 0.75, label = 'Mittleres Lager · Grapeseed' },
            },
        },
    },

    ---------------------------------------------------------------------------
    -- GROSSES LAGER
    ---------------------------------------------------------------------------
    {
        id      = 'large',
        label   = 'Großes Lager',
        tagline = 'Die Endstufe. Maximaler Stauraum für organisierte Strukturen.',
        price   = 1500000,
        order   = 3,

        theme   = { from = '#F59E0B', to = '#EF4444' },
        icon    = 'crown',

        ipl     = 'ex_exec_warehouse_placement_interior_2_int_warehouse_l_dlc_milo',

        interior = {
            spawn  = vector4(1007.83, -3112.57, -38.90, 356.83),
            stash  = vector3(1003.65, -3104.74, -38.99),
            manage = vector3(994.43, -3100.06, -38.99),
            exit   = vector3(1007.83, -3112.97, -38.9),
        },

        perks = {
            'Größtes verfügbares Interior',
            'Bis zu 3 Premium-Zugänge',
            'Bis zu 300 Slots & 2.5 t Traglast',
            'Volle Team-Verwaltung mit Rechten',
        },
        tiers = {
            { label = 'Basis',      slots = 120, weight = 600000,  price = 0       },
            { label = 'Ausbau I',   slots = 170, weight = 1000000, price = 1200000 },
            { label = 'Ausbau II',  slots = 230, weight = 1600000, price = 2400000 },
            { label = 'Ausbau III', slots = 300, weight = 2500000, price = 4000000 },
        },

        entries = {
            {
                index  = 1,
                label  = 'Paleto Bay',
                price  = 150000,
                coords = vec4(55.60, 6472.23, 31.42, 46.32),
                blip   = { sprite = 473, color = 2, scale = 0.75, label = 'Großes Lager · Paleto Bay' },
            },
            {
                index  = 2,
                label  = 'Hafen / City',
                price  = 150000,
                coords = vector3(159.04, -2911.99, 7.72),
                blip   = { sprite = 473, color = 2, scale = 0.75, label = 'Großes Lager · Hafen' },
            },
            {
                index  = 3,
                label  = 'Grapeseed',
                price  = 150000,
                coords = vector3(387.48, 3584.75, 33.29),
                blip   = { sprite = 473, color = 2, scale = 0.75, label = 'Großes Lager · Grapeseed' },
            },
        },
    },
}

Exports

Alle Exports sind serverseitig.

HasWarehouse(source, warehouseId) — gibt true zurück, wenn der Spieler diesen Lagertyp besitzt.

local has = exports.ancomox_warehouse:HasWarehouse(source, 'medium')

GetPlayerWarehouses(source) — liefert alle Lager des Spielers als Tabelle { [warehouseId] = record }. Der Datensatz enthält u. a. tier, entries, keys, tenure, rentUntil und locked.

local list = exports.ancomox_warehouse:GetPlayerWarehouses(source)
for warehouseId, rec in pairs(list) do
    print(warehouseId, rec.tier, rec.tenure)
end

GetStashId(ownerIdentifier, warehouseId) — liefert die Stash-ID für eigene Inventar-Zugriffe, oder nil. Erwartet den Identifier, nicht die Server-ID.

local stash = exports.ancomox_warehouse:GetStashId(identifier, 'large')

GiveWarehouse(source, warehouseId, entryIndex) — vergibt ein Lager per Script (Belohnung, Whitelist-Job, …). Gibt false zurück, wenn die ID unbekannt ist oder der Spieler das Lager bereits besitzt. Das Limit aus Config.Limits wird dabei nicht geprüft.

exports.ancomox_warehouse:GiveWarehouse(source, 'small', 1)

Events & Commands

Admin-Befehle

Berechtigung: Gruppe aus Config.Admin.groups oder ACE ancomox_warehouse.admin. Alle Befehle funktionieren auch aus der Serverkonsole.

Befehl Wirkung
/warehouse list Alle konfigurierten Lager-IDs mit Preis, Zugängen und Stufen anzeigen.
/warehouse info <id> Lager eines Spielers inklusive Keys anzeigen.
/warehouse give <id> <lagerId> [entry] Lager vergeben. entry ist optional, Standard ist Zugang 1.
/warehouse remove <id> <lagerId> Lager entziehen. Anwesende Spieler werden zuvor aus der Instanz entfernt.
/warehouse tier <id> <lagerId> <stufe> Ausbaustufe setzen. Der Stash wird sofort neu registriert.
/warehouse unlock <id> <lagerId> Gesperrtes Mietlager entsperren und die Laufzeit verlängern.
/warehouse pos [notiz] Aktuelle Position im Config-Format in die F8-Konsole schreiben.
/warehouse reload Cache aus der Datenbank neu laden und alle Stashes neu registrieren.

Spieler-Befehl

Befehl Wirkung
/warehouseui Notfall: gibt Maus und Tastatur frei, falls das Interface hängen bleibt.

Netz-Events sind intern. Alle ancomox_warehouse:*-Events erwarten einen validierten Spielerkontext und prüfen Besitz, Rechte und Distanz selbst. Für die Anbindung eigener Scripts sind ausschließlich die oben genannten Exports vorgesehen.

Discord-Logs

Konfiguration in Config.Logs. Die Embeds werden in Paketen zu maximal zehn Stück gesendet und bei Bedarf zwischengespeichert, damit ein voller Server den Webhook nicht überlastet.

Config.Logs.enabled = true
Config.Logs.webhook = 'https://discord.com/api/webhooks/...'
Config.Logs.events.stash = false   -- Stash-Öffnungen erzeugen viel Traffic
Kategorie Standard Wird geloggt bei
purchase an Kauf eines Lagers.
entry an Freischaltung eines zusätzlichen Zugangs.
upgrade an Kauf einer Ausbaustufe.
sell an Verkauf inklusive Erstattungsbetrag.
rent an Anmietung, Zahlung, Sperrung und Auflösung.
key an Vergabe und Entzug von Zugangs-Keys.
stash aus Jedes Öffnen des Lagerinventars.
admin an Alle Admin-Eingriffe.
security an Erreichen der Strike-Warnschwelle und Auto-Kicks.

Jedes Embed enthält Spielername, Server-ID und die verfügbaren Identifier (license, discord, steam). Farben je Kategorie lassen sich über Config.Logs.colors anpassen.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
„Kein unterstütztes Framework gefunden!" Die Ressource startet vor dem Framework. Reihenfolge in der server.cfg prüfen — ancomox_warehouse muss nach es_extended bzw. qb-core stehen.
Stash öffnet nicht oder ist leer Config.Inventory hart auf das tatsächlich genutzte System setzen statt 'auto'. Bei ox_inventory muss die Ressource vorher starten.
Spieler sehen sich gegenseitig im Lager Config.Instancing.enabled = true setzen und sicherstellen, dass OneSync aktiv ist.
Andere Spieler „verschwinden" auf der Map Ein anderes Script nutzt denselben Bucket-Bereich. Config.Instancing.baseBucket auf einen freien Wert setzen.
Interior leer oder man fällt durch die Map Das IPL wurde nicht geladen. Config.Misc.loadInteriors prüfen und sicherstellen, dass kein anderes Script dieselben IPLs entfernt.
Marker statt ox_target Config.Target = 'ox_target' hart setzen. 'auto' wartet beim Start bis zu 5 Sekunden auf das Target-Script; startet es später, greift der Marker-Modus.
Punkt sichtbar, aber nicht anvisierbar Bei ox_target muss der Spieler in der unsichtbaren Kugelzone stehen. targetRadius bzw. targetRadiusEntry erhöhen. Hilft das nicht, liegt die Koordinate in einer Wand — mit /warehouse pos neu setzen.
„Du bist zu weit entfernt" beim Kaufen Jede Lagergröße hat an einem Standort ihre eigene Tür; diese können weit auseinanderliegen. Der Server akzeptiert sowohl die Tür des gewählten Lagers als auch die, an der der Dialog geöffnet wurde. Tritt die Meldung trotzdem auf, Config.Security.distanceTolerance erhöhen. Mit Config.Debug = true wird die gemessene Entfernung ausgegeben.
Kein Key vergebbar Der Empfänger muss in derselben Instanz und in Reichweite stehen. Am Eingang funktioniert das immer, im Interior nur, wenn die Person bereits Zutritt hat. Config.Interaction.manageAtEntrance muss aktiv sein.
Schwarze Kästen hinter Dialog oder Toast Stammt von backdrop-filter im CSS. Im Auslieferungszustand ist keines enthalten — eigene Ergänzungen entfernen und stattdessen rgba(...) verwenden.
Hilfetext zeigt „INPUT_CONTEXT" ~INPUT_CONTEXT~ löst nur die native Hilfeanzeige auf. In eigenen Locale-Texten keine ~…~-Tokens verwenden; die Taste kommt aus Config.Interaction.keyLabel.
Ausbau ohne Wirkung Das genutzte Inventar kann die Kapazität pro Stash nicht begrenzen. Siehe den Hinweis unter „Eigenes Framework oder Inventar anbinden".
Nichts passiert beim Kaufen Config.Debug = true setzen und die Serverkonsole prüfen. Distanzprüfungen werden mit gemessener Entfernung geloggt.

Ancomox Airdrop — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Airdrop
Zum Produkt

Ancomox Airdrop — server-autoritatives Versorgungsabwurf-System für FiveM. Ein Transportflugzeug fliegt an, wirft eine Kiste am Fallschirm ab, der Inhalt landet in einem echten Inventar-Container. ESX · QBCore · QBox · Standalone.

Überblick

ancomox_airdrop lässt in konfigurierbaren Intervallen — oder per Admin-Befehl oder per Leuchtfackel eines Spielers — einen Airdrop niedergehen. Der Ablauf ist eine echte Szene: Ankündigung, Zonenradius auf der Karte, anfliegendes Transportflugzeug, Abwurf, Fallschirm mit Pendelbewegung und Rauchfahne, Aufschlag.

Kernmerkmale:

  • Kein Host-Client. Der Server berechnet einen deterministischen Flugplan (Start, Abwurfpunkt, Ende, Geschwindigkeit, Zeitstempel). Jeder Client rechnet daraus dieselbe Flugbahn und rendert Flugzeug und Kiste lokal. Alle sehen dasselbe, ohne Netzwerk-Entities — und kein Spieler kann den Ablauf beeinflussen, weil er während der gesamten Szene kein einziges Event sendet.
  • Flugzeug als Karten-Event. Der Blip bewegt sich für jeden Spieler über die Karte, unabhängig von der Entfernung.
  • Container statt Instant-Loot. Die Kiste öffnet ein echtes Inventar — Spieler können teilen, sich darum streiten und mehrfach zugreifen.
  • Loot-Engine mit gewichteten Pools, garantierten Einträgen, unique-Flag, Stapelung und Geld-Einträgen über account.
  • Boden-Auflösung mit Konsens. Der Server kann nicht raycasten; die Bodenhöhe liefern mehrere Clients unabhängig, der Median gewinnt. Ein manipulierter Client kann die Kiste nicht unerreichbar machen.
  • Läuft ohne alles. Framework, Inventar, Target, Notify, Progress und TextUI werden zur Laufzeit erkannt; fehlt etwas, greift ein eingebauter Fallback. Das Script startet auch auf einem nackten Server.

Zeitlicher Ablauf mit Standardwerten: Ankündigung → 27 s Anflug → Abwurf → 36 s Fall → Aufschlag → Kiste bleibt 25 Minuten liegen. Wird sie komplett leergeräumt, verschwindet sie 6 Sekunden später.

Voraussetzungen

Es gibt keine harten Abhängigkeiten. Alles unten ist optional und wird zur Laufzeit erkannt.

Abhängigkeit Pflicht Zweck / Hinweis
es_extended, qb-core oder qbx_core Optional Ohne Framework läuft das Script im Standalone-Modus mit eingebautem Loot-Fenster.
ox_inventory, qb-inventory oder qs-inventory Optional Ohne Inventarsystem übernimmt das eingebaute NUI-Loot-Fenster. Bei ox_inventory wird ein temporärer Stash mit Positionsbindung erzeugt.
oxmysql Optional Nur für Statistiken und persistente Flare-Cooldowns. Config.Database.enabled = false deaktiviert SQL vollständig.
ox_target oder qb-target Optional Ohne Target-Script greift ein performanter 3D-Text-Fallback mit Tastendruck.
ox_lib Optional Notifications, Progressbar und TextUI.
ps-dispatch, cd_dispatch, qs-dispatch Optional Polizei-Meldungen. Ohne Dispatch-System werden Job-Spieler direkt benachrichtigt.

Umbenannte Ressourcen: Heißt bei dir eine Abhängigkeit anders, trag den echten Namen in Config.ResourceNames ein — die Erkennung greift dann trotzdem.

Installation

  1. Ordner ancomox_airdrop nach resources/[ancomox]/ kopieren.
  2. In der server.cfg eintragen — nach Framework, Inventar und oxmysql:
ensure ancomox_airdrop
  1. Admin-Rechte setzen. Über ACE funktioniert das in jedem Framework:
add_ace group.admin ancomox.airdrop allow
  1. Flare-Items anlegen (nur nötig, wenn Spieler Airdrops selbst auslösen sollen). Bei ox_inventory den fertigen Block aus items/ox_inventory.lua in ox_inventory/data/items.lua kopieren — die Einträge enthalten bereits die nötige server.export-Verknüpfung.
  2. config/loot.lua an die eigenen Item-Namen anpassen (siehe unten).
  3. config/zones.lua prüfen und eigene Abwurfpunkte setzen.
  4. Server starten. In der Konsole sollte stehen:
[ancomox_airdrop] Umgebung erkannt → Framework: esx | Inventar: ox | Target: ox_target | Notify: ox_lib | SQL: true

Startet eine Abhängigkeit später als das Script, erkennt es sie automatisch nach und meldet das in der Konsole. Ein zu früher Start führt also nicht dauerhaft zu standalone.

Flare-Items ohne ox_inventory

QBCore / QBox: Einträge aus items/qb_core.lua in qb-core/shared/items.lua kopieren, Bilder nach qb-inventory/html/images/.

ESX mit Legacy-Inventar:

INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
    ('airdrop_flare',      'Leuchtfackel',          1, 0, 1),
    ('airdrop_flare_rare', 'Signalfackel (Selten)', 1, 0, 1);

Standalone: Flares lassen sich per Befehl auslösen (/use_airdrop_flare), oder du setzt Config.Flares.enabled = false.

Datenbank

Die Datenbank ist optional. Ohne oxmysql läuft alles weiter — es fehlen nur Statistiken, und Flare-Cooldowns überleben keinen Serverneustart. Die Tabellen werden beim Start automatisch angelegt; sql/install.sql liegt für die manuelle Installation bei.

Tabelle Inhalt
ancomox_airdrop_drops Jeder Airdrop mit Typ, Zone, Koordinaten, Auslöser, Start- und Endzeit sowie Endgrund.
ancomox_airdrop_loot Jede Item-Entnahme mit Identifier, Spielername, Item und Menge.
ancomox_airdrop_cooldowns Flare-Cooldown pro Identifier — überlebt dadurch einen Neustart.

Konfiguration

Drei Dateien, alle über escrow_ignore editierbar:

Datei Inhalt
config/config.lua Erkennung, Intervalle, Szene, Kiste, Blips, Interaktion, Dispatch, Flares, Sicherheit, Webhooks, HUD
config/zones.lua Abwurfzonen, Blacklist-Bereiche, Regeln zur Punktauflösung
config/loot.lua Airdrop-Typen und Loot-Tabellen

Die wichtigsten Schalter

Key Standard Beschreibung
Config.Automatic.intervalMin / intervalMax 45 / 90 Zufälliger Abstand zwischen automatischen Airdrops in Minuten.
Config.Automatic.minPlayers 4 Darunter wird kein automatischer Drop gestartet.
Config.Automatic.maxConcurrent 2 Gleichzeitig aktive Airdrops. Gilt auch für Flares und Admin-Drops.
Config.Scene.enabled true false = die Kiste erscheint ohne Flugzeug.
Config.Scene.approachDistance 2200.0 Anflugstrecke in Metern. Bei 80 m/s ergibt das rund 27 Sekunden.
Config.Scene.renderDistance 800.0 Ab hier wird das Flugzeug als Objekt erzeugt. Der Karten-Blip ist davon unabhängig immer sichtbar.
Config.Crate.despawnAfter 25 Minuten, bis eine unangetastete Kiste verschwindet.
Config.Crate.despawnWhenEmpty true Leergeräumte Kiste nach emptyGrace Sekunden entfernen.
Config.Interaction.unlock.duration 6000 Dauer der Aufbruch-Animation in ms. Pro Airdrop-Typ über unlockTime überschreibbar.
Config.Interaction.unlock.item nil Optionales Werkzeug, z. B. 'lockpick'. consume steuert, ob es verbraucht wird.
Config.Container.maxOpenDistance 4.0 Ab dieser Entfernung wird der geöffnete Container zwangsweise geschlossen.
Config.Flares.playerCooldown 30 Minuten Sperre pro Spieler nach einer Fackel.
Config.Security.rateLimit 30 Maximale Aufrufe pro Spieler, pro Event-Typ, pro 10 Sekunden.

Zonen — zwei Modi

Der Modus entscheidet, wie zuverlässig der Abwurfpunkt sitzt:

Modus Verhalten
preferPoints = true (empfohlen) Es wird ausschließlich einer der hinterlegten Punkte genutzt. Die Höhe gilt als vermessen: keine Boden-Abfrage, kein Wasser-Risiko, kein Höhenfehler.
preferPoints = false Zufälliger Punkt im Radius. Die Bodenhöhe wird zur Laufzeit von bis zu drei Clients abgefragt (Median). Gut in flachem Gelände, kann in Bergen und an Küsten danebenliegen.

So kommst du an gute Punkte: im Spiel an die Stelle gehen, Koordinaten abgreifen, als vector3(x, y, z) in points eintragen und preferPoints = true setzen. Die mitgelieferten Koordinaten sind Startwerte und sollten auf der eigenen Map geprüft werden — Küsten- und Gebirgszonen sind bewusst auf feste Punkte gestellt bzw. deaktiviert.

Loot-Regeln

  • guaranteed landet immer in der Kiste.
  • pool wird rolls-mal gewichtet gezogen. weight ist ein relatives Gewicht, keine Prozentangabe — die Summe muss nichts Bestimmtes ergeben.
  • unique = true — der Eintrag kann pro Kiste nur einmal gezogen werden.
  • account = 'money' | 'bank' | 'black_money' wird als Geld gutgeschrieben statt als Item. Bei QB/QBox gibt es kein Schwarzgeld-Konto; das System legt dort markedbills an, sonst Bargeld.
  • Waffen einfach als weapon_pistol schreiben — die Bridge normalisiert bei ox_inventory automatisch auf WEAPON_PISTOL.
  • Nur Items belegen Slots, Geld nicht. Passen garantierte Einträge nicht in container.slots, warnt die Konsole.

Eigenen Airdrop-Typ anlegen:

Config.AirdropTypes['military'] = {
    label       = 'Militär-Airdrop',
    weight      = 8,                 -- relative Wahrscheinlichkeit
    plane       = 'titan',
    crateModel  = 'prop_mil_crate_01',
    blipColour  = 1,
    smokeColour = { r = 255, g = 40, b = 40 },
    container   = { slots = 20, maxWeight = 150000 },
    unlockTime  = 15000,             -- ms bis die Kiste offen ist
    announce    = true,
    loot = {
        rolls      = { min = 6, max = 9 },
        guaranteed = {
            { item = 'armor', min = 3, max = 5 },
        },
        pool = {
            { item = 'weapon_assaultrifle', min = 1, max = 1, weight = 20,
              unique = true, metadata = { ammo = 150 } },
            { item = 'rifle_ammo',          min = 100, max = 300, weight = 80 },
            { account = 'black_money',      min = 30000, max = 70000, weight = 50,
              label = 'Schwarzgeld' },
        },
    },
}

Vollständige Config-Dateien

config/config.lua300 Zeilen · vollständige Datei
--[[--------------------------------------------------------------------------
    ancomox_airdrop  ·  Hauptkonfiguration
    ------------------------------------------------------------------------
    Alle Werte hier sind sicher editierbar (escrow_ignore).
    Loot  -> config/loot.lua
    Zonen -> config/zones.lua
----------------------------------------------------------------------------]]

Config = {}

-- =========================================================================
--  ALLGEMEIN
-- =========================================================================
Config.Debug              = false        -- Ausführliche Konsolenausgabe
Config.Locale             = 'de'         -- de | en | fr | es
-- Versionsprüfung beim Start (leere URL = aus).
-- Erwartet eine Textantwort, die NUR die Versionsnummer enthält, z.B. "2.0.1"
Config.VersionCheck       = false
Config.VersionCheckUrl    = ''

-- =========================================================================
--  AUTO-DETECTION
--  'auto' erkennt automatisch, was auf dem Server läuft.
--  Nur überschreiben, wenn die Erkennung fehlschlägt.
-- =========================================================================
Config.Framework          = 'auto'       -- auto | esx | qb | qbx | standalone
Config.Inventory          = 'auto'       -- auto | ox | qb | qs | core | builtin
Config.Target             = 'auto'       -- auto | ox_target | qb-target | none
Config.Notify             = 'auto'       -- auto | ox_lib | esx | qb | builtin
Config.Progress           = 'auto'       -- auto | ox_lib | qb | builtin
Config.TextUI             = 'auto'       -- auto | ox_lib | qb | builtin

-- Ressourcen-Namen überschreiben (falls umbenannt)
Config.ResourceNames = {
    esx        = 'es_extended',
    qb         = 'qb-core',
    qbx        = 'qbx_core',
    oxInv      = 'ox_inventory',
    qbInv      = 'qb-inventory',
    qsInv      = 'qs-inventory',
    oxLib      = 'ox_lib',
    oxTarget   = 'ox_target',
    qbTarget   = 'qb-target',
    oxmysql    = 'oxmysql',
}

-- =========================================================================
--  DATENBANK  (optional – nur für Statistiken & Crash-Recovery)
-- =========================================================================
Config.Database = {
    enabled   = true,        -- false = komplett ohne SQL lauffähig
    logDrops  = true,        -- Jeden Airdrop in DB schreiben
    logLoot   = true,        -- Jede Item-Entnahme loggen
}

-- =========================================================================
--  AUTOMATISCHE AIRDROPS
-- =========================================================================
Config.Automatic = {
    enabled        = true,
    intervalMin    = 45,     -- Minuten (untere Grenze)
    intervalMax    = 90,     -- Minuten (obere Grenze)
    minPlayers     = 4,      -- Erst ab so vielen Spielern online
    maxConcurrent  = 2,      -- Gleichzeitig aktive Airdrops
    firstDropDelay = 10,     -- Minuten nach Serverstart bis zum ersten Drop
    announce       = true,   -- Globale Ankündigung
}

-- =========================================================================
--  ADMIN / BERECHTIGUNGEN
-- =========================================================================
Config.Permissions = {
    command      = 'airdrop',                       -- /airdrop [typ]
    cancelCmd    = 'airdropcancel',                 -- /airdropcancel [id]
    listCmd      = 'airdroplist',
    -- ACE-Permission (empfohlen, funktioniert in JEDEM Framework)
    ace          = 'ancomox.airdrop',
    -- Zusätzlich akzeptierte Framework-Gruppen
    groups       = { 'admin', 'superadmin', 'god', 'owner' },
    allowConsole = true,
}

-- =========================================================================
--  FLUGZEUG / SZENE
-- =========================================================================
--  Der Flug ist vollständig deterministisch: der Server berechnet Start,
--  Abwurfpunkt und Endpunkt, jeder Client rechnet daraus dieselbe Position.
--  Dadurch sehen ALLE Spieler dasselbe Flugzeug – auch quer über die Karte,
--  und es gibt keinen "Host"-Client mehr, der ausfallen könnte.
Config.Scene = {
    enabled            = true,    -- false = Kiste erscheint ohne Flugzeug
    -- Entfernung, in der das Flugzeug relativ zum Abwurfpunkt startet
    approachDistance   = 2200.0,  -- ergibt bei 80 m/s ca. 27 s Anflug
    exitDistance       = 1500.0,  -- wie weit es danach weiterfliegt
    altitude           = 320.0,   -- Abwurfhöhe über dem Boden
    speed              = 80.0,    -- m/s
    -- Ab dieser Entfernung wird das Flugzeug als Objekt erzeugt.
    -- GTA zeichnet Fahrzeuge darüber hinaus ohnehin kaum noch – höhere Werte
    -- kosten nur Leistung. Der Karten-Blip ist IMMER für alle sichtbar.
    renderDistance     = 800.0,
    planeBlip          = true,    -- Flugzeug als Event-Blip auf der Karte
    planeLights        = true,
}

-- =========================================================================
--  KISTE
-- =========================================================================
Config.Crate = {
    defaultModel   = 'prop_mil_crate_01',
    chuteModel     = 'p_cargo_chute_s',
    descentSpeed   = 9.0,       -- m/s Sinkgeschwindigkeit am Fallschirm
    swayStrength   = 0.9,       -- Seitliches Pendeln
    spinSpeed      = 12.0,      -- Grad/Sekunde Eigendrehung
    landingImpact  = true,      -- Staubwolke + Sound beim Aufsetzen
    -- Rauchfahne an der Kiste. Es wird der erste Eintrag verwendet, dessen
    -- Partikel-Dictionary sich laden lässt (Robustheit über GTA-Builds hinweg).
    smoke = {
        enabled  = true,
        scale    = 2.5,
        zOffset  = 0.35,
        variants = {
            { dict = 'scr_ba_bb',      name = 'scr_ba_bb_package_flare'      },
            { dict = 'scr_xm_ht',      name = 'scr_xm_ht_package_flare'      },
            { dict = 'scr_ie_export',  name = 'scr_ie_export_package_flare'  },
            { dict = 'core',           name = 'exp_grd_flare'                },
        },
    },
    -- Sound beim Aufsetzen (leerer Name = aus)
    landSound = { name = 'Crate_Land', set = 'FBI_05_SOUNDS' },
    -- Ab dieser Entfernung wird die Kiste beim Spieler erzeugt
    streamDistance = 400.0,
    despawnAfter   = 25,        -- Minuten nach der Landung -> Kiste verschwindet
    despawnWarn    = 3,         -- Minuten vorher warnen
    -- Kiste verschwindet, sobald sie komplett leergeräumt ist
    despawnWhenEmpty = true,
    emptyGrace       = 6,       -- Sekunden Verzögerung, damit man es noch sieht
}

-- =========================================================================
--  BLIPS
-- =========================================================================
Config.Blips = {
    -- Ungefähre Landezone. Wird ab der Ankündigung angezeigt und beim
    -- Aufsetzen durch den exakten Kisten-Blip ersetzt.
    zone = {
        enabled = true,
        radius  = 200.0,
        colour  = 1,             -- rote Fläche
        alpha   = 80,
        fuzzy   = true,          -- Mittelpunkt zufällig versetzen
        fuzzyOffset = 70.0,
        showMarker  = true,      -- zusätzlicher Symbol-Blip in der Zonenmitte
    },
    crate = {
        enabled     = true,
        sprite      = 478,
        colour      = 5,
        scale       = 0.9,
        shortRange  = false,
        flash       = false,
    },
    -- Flugzeug-Blip: an/aus über Config.Scene.planeBlip
    plane = {
        sprite  = 423,
        colour  = 4,
        scale   = 0.8,
    },
}

-- =========================================================================
--  INTERAKTION
-- =========================================================================
Config.Interaction = {
    distance      = 2.2,
    key           = 38,                 -- E (nur bei Fallback ohne Target)
    useTarget     = true,               -- ox_target / qb-target verwenden wenn vorhanden
    -- Kiste muss erst "geknackt" werden
    unlock = {
        enabled   = true,
        duration  = 6000,               -- ms
        item      = nil,                -- z.B. 'lockpick' – nil = kein Item nötig
        itemLabel = 'Brecheisen',       -- Anzeigename für die Fehlermeldung
        consume   = false,              -- Item verbrauchen?
        anim      = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer' },
        prop      = nil,
        cancelOnMove = true,
    },
    -- Nur bestimmte Jobs dürfen looten (leer = alle)
    restrictJobs  = {},
    -- Loot nur außerhalb von Fahrzeugen
    denyInVehicle = true,
    denyWhenDead  = true,
}

-- =========================================================================
--  CONTAINER (Loot-Kiste)
-- =========================================================================
Config.Container = {
    prefix     = 'ancomox_airdrop_',
    slots      = 18,
    maxWeight  = 120000,
    -- Sicherheit: max. Distanz, in der der Container geöffnet bleiben darf
    maxOpenDistance = 4.0,
}

-- =========================================================================
--  POLIZEI / DISPATCH
-- =========================================================================
Config.Dispatch = {
    enabled  = true,
    system   = 'auto',      -- auto | ps-dispatch | cd_dispatch | qs-dispatch | core | none
    onSpawn  = true,        -- Meldung wenn Airdrop startet
    onLoot   = true,        -- Meldung wenn jemand plündert
    lootChance = 65,        -- % Wahrscheinlichkeit für Loot-Meldung
    jobs     = { 'police', 'sheriff', 'sast', 'bcso' },
    blipTime = 120,         -- Sekunden
}

-- =========================================================================
--  FLARES (Spieler löst Airdrop mit Item aus)
-- =========================================================================
Config.Flares = {
    enabled = true,
    -- Cooldown pro Spieler in Minuten
    playerCooldown = 30,
    -- Globaler Cooldown in Minuten (0 = aus)
    globalCooldown = 5,
    -- Verzögerung zwischen Flare-Wurf und Flugzeug (Sekunden)
    callDelay      = 25,
    -- Flare darf nur in diesen Zonen benutzt werden (leer = überall erlaubt)
    restrictToZones = false,
    -- Verbotene Bereiche (Safezone, Stadt etc.)
    blacklistZones = {
        { coords = vector3(-1037.0, -2737.0, 20.0), radius = 400.0, label = 'Flughafen' },
    },
    items = {
        ['airdrop_flare'] = {
            label       = 'Leuchtfackel',
            airdropType = 'standard',
            prop        = 'prop_flare_01',
            smokeColour = { r = 255, g = 60,  b = 40  },
        },
        ['airdrop_flare_rare'] = {
            label       = 'Signalfackel (Selten)',
            airdropType = 'rare',
            prop        = 'prop_flare_01',
            smokeColour = { r = 80,  g = 140, b = 255 },
        },
    },
}

-- =========================================================================
--  SICHERHEIT / ANTI-CHEAT
-- =========================================================================
Config.Security = {
    -- Max. Aufrufe pro Spieler, pro Event-Typ, pro 10 Sekunden
    rateLimit          = 30,
    -- Nach wie vielen Verstößen gekickt wird
    violationsToKick   = 4,
    -- Spieler bei Überschreitung kicken
    kickOnAbuse        = true,
    kickMessage        = 'Ancomox Airdrop: verdächtige Netzwerkaktivität erkannt.',
    -- Loot nur wenn Spieler wirklich in Reichweite (Server-seitige Prüfung)
    enforceDistance    = true,
    distanceTolerance  = 6.0,
    -- Höhentoleranz für den Loot-Zugriff. Verhindert Looten aus dem
    -- Stockwerk darüber, lässt aber kleine Boden-Z-Abweichungen zu.
    verticalTolerance  = 10.0,
    -- Verdächtige Vorgänge an Discord melden
    logViolations      = true,
}

-- =========================================================================
--  DISCORD WEBHOOKS
-- =========================================================================
Config.Webhook = {
    enabled  = false,
    botName  = 'ANCOMOX Airdrop',
    avatar   = '',
    colour   = 3447003,
    urls = {
        spawn     = '',
        loot      = '',
        collected = '',
        admin     = '',
        security  = '',
    },
}

-- =========================================================================
--  HUD / NUI
-- =========================================================================
Config.HUD = {
    enabled        = true,
    showDistance   = true,
    showDespawn    = true,      -- Verbleibende Zeit bis Despawn
    position       = 'right',   -- right | left
    soundOnSpawn   = true,
}
config/zones.lua147 Zeilen · vollständige Datei
--[[--------------------------------------------------------------------------
    ancomox_airdrop  ·  Abwurfzonen
    ------------------------------------------------------------------------
    ZWEI MODI PRO ZONE

    1) preferPoints = true   (EMPFOHLEN)
       Es wird ausschließlich einer der hinterlegten Punkte verwendet.
       Die Höhe gilt als vermessen: keine Boden-Abfrage, kein Wasser-Risiko,
       kein Höhenfehler. Das ist der zuverlässigste Modus.

    2) preferPoints = false
       Zufälliger Punkt im Radius. Die Boden-Höhe wird zur Laufzeit von
       mehreren Clients abgefragt (Median). Funktioniert gut in flachem
       Gelände, kann in Bergen und an Küsten danebenliegen.

    ==> Für einen Verkaufs-fertigen Server: eigene Punkte im Spiel abgreifen
        und preferPoints = true setzen. Die hier hinterlegten Koordinaten
        sind Startwerte und sollten auf deiner Map geprüft werden.
----------------------------------------------------------------------------]]

Config.Zones = {
    {
        name         = 'Sandy Shores / Grapeseed',
        enabled      = true,
        weight       = 25,
        center       = vector3(1900.0, 3700.0, 32.0),
        radius       = 850.0,
        preferPoints = false,      -- flaches Wüstengelände, Radius ist unkritisch
        points = {
            vector3(2431.87, 4968.25, 45.31),
            vector3(1690.44, 3272.11, 40.14),
            vector3(2140.19, 4780.55, 40.97),
        },
        hours      = {},
        allowFlare = true,
    },
    {
        name         = 'Grand Senora Desert',
        enabled      = true,
        weight       = 20,
        center       = vector3(1100.0, 2700.0, 38.0),
        radius       = 700.0,
        preferPoints = false,      -- sehr flach, ideal für Radius-Modus
        points = {
            vector3(1207.11, 2660.44, 37.85),
            vector3(880.55, 2380.19, 51.66),
        },
        hours      = {},
        allowFlare = true,
    },
    {
        name         = 'Mount Chiliad / Raton Canyon',
        enabled      = true,
        weight       = 15,
        center       = vector3(500.0, 5600.0, 780.0),
        radius       = 700.0,
        preferPoints = true,       -- Gebirge: NUR feste Punkte verwenden
        points = {
            vector3(501.28, 5604.51, 797.91),
            vector3(-70.66, 6432.11, 31.49),
            vector3(1310.11, 6520.44, 19.32),
        },
        hours      = {},
        allowFlare = true,
    },
    {
        name         = 'Fort Zancudo Umland',
        enabled      = true,
        weight       = 15,
        center       = vector3(-2200.0, 3100.0, 32.0),
        radius       = 450.0,      -- klein gehalten, damit nichts im Meer landet
        preferPoints = false,
        points       = {},
        hours        = {},
        allowFlare   = true,
    },
    {
        name         = 'Alamo Sea · Ufer',
        enabled      = true,
        weight       = 15,
        center       = vector3(1300.0, 4200.0, 33.0),
        radius       = 800.0,
        -- Der See selbst ist Wasser -> ausschließlich Uferpunkte verwenden
        preferPoints = true,
        points = {
            vector3(1531.11, 3789.44, 34.32),
            vector3(908.66, 3609.19, 32.60),
            vector3(1728.55, 4681.11, 42.16),
            vector3(1129.44, 4630.88, 30.85),
        },
        hours      = {},
        allowFlare = true,
    },
    {
        name         = 'Chumash / Küste West',
        enabled      = false,      -- Küstenzone: erst mit eigenen Punkten aktivieren
        weight       = 10,
        center       = vector3(-2100.0, 2200.0, 20.0),
        radius       = 700.0,
        preferPoints = true,
        points = {
            vector3(-1602.44, 2100.11, 75.28),
            vector3(-2288.10, 3000.47, 32.81),
        },
        hours      = {},
        allowFlare = true,
    },
    {
        name         = 'Cayo Perico',
        enabled      = false,      -- nur mit geladenem Cayo-DLC aktivieren
        weight       = 10,
        center       = vector3(5055.72, -4524.91, 7.27),
        radius       = 350.0,
        preferPoints = true,
        points = {
            vector3(5055.72, -4524.91, 2.27),
            vector3(4885.33, -5165.98, 2.11),
        },
        hours      = {},
        allowFlare = false,
    },
}

--[[  Bereiche, in denen NIEMALS gedroppt wird (Safezones, Stadt, PD, …)  ]]
Config.ZoneBlacklist = {
    { coords = vector3(441.0, -982.0, 30.0),    radius = 250.0, label = 'Mission Row PD' },
    { coords = vector3(-1037.0, -2737.0, 20.0), radius = 350.0, label = 'LSIA' },
    { coords = vector3(295.0, -1446.0, 29.0),   radius = 200.0, label = 'Pillbox Hospital' },
}

--[[  Regeln für die Auflösung des Abwurfpunkts  ]]
Config.ZoneRules = {
    -- Landet der gewählte Punkt im Wasser, wird ein neuer Punkt gesucht.
    -- Greift nur im Radius-Modus; feste Punkte gelten als vermessen.
    denyWater        = true,
    waterRetries     = 4,
    -- Plausibilitätsgrenzen für die vom Client gemeldete Boden-Höhe
    minGroundZ       = -50.0,
    maxGroundZ       = 1200.0,
    -- Maximale Abweichung von der Zonenhöhe, wenn nur EIN Client geantwortet
    -- hat. Sind sich mehrere Clients einig, gilt deren Wert ohne diese Grenze.
    maxGroundDeviation = 260.0,
    -- Wie viele Clients unabhängig befragt werden (Median gewinnt).
    -- 1 = aus. Höhere Werte kosten nichts und verhindern Manipulation.
    groundProbers    = 3,
}
config/loot.lua168 Zeilen · vollständige Datei
--[[--------------------------------------------------------------------------
    ancomox_airdrop  ·  Airdrop-Typen & Loot-Tabellen
    ------------------------------------------------------------------------
    LOOT-SYSTEM
      guaranteed : wird IMMER in die Kiste gelegt
      pool       : gewichtete Zufallsauswahl ("weight" = relatives Gewicht)
      rolls      : wie viele Einträge aus dem Pool gezogen werden
      unique     : true = Eintrag kann pro Kiste nur einmal gezogen werden

    GELD
      Für Bargeld/Schwarzgeld statt Item:  account = 'money' | 'black_money' | 'bank'
      Wird beim Aufnehmen direkt gutgeschrieben (ohne Item).

    WAFFEN
      ox_inventory  : 'WEAPON_PISTOL'  (Großschreibung, metadata für Munition)
      ESX/QB        : 'weapon_pistol'
      -> Die Bridge normalisiert das automatisch, schreibe einfach 'weapon_pistol'.

    TYP-AUSWAHL
      weight = relatives Gewicht bei automatischen Airdrops.
      Es müssen KEINE 100 % ergeben – das System normalisiert selbst.
----------------------------------------------------------------------------]]

Config.AirdropTypes = {

    ---------------------------------------------------------------------
    standard = {
        label       = 'Standard Airdrop',
        weight      = 55,
        plane       = 'titan',
        crateModel  = 'prop_mil_crate_01',
        blipColour  = 5,
        smokeColour = { r = 240, g = 240, b = 240 },
        container   = { slots = 12, maxWeight = 60000 },
        unlockTime  = 5000,
        announce    = true,
        loot = {
            rolls     = { min = 4, max = 6 },
            guaranteed = {
                { item = 'bandage', min = 2, max = 4 },
            },
            pool = {
                { item = 'water',            min = 2, max = 5,  weight = 120 },
                { item = 'bread',            min = 2, max = 5,  weight = 120 },
                { item = 'bandage',          min = 1, max = 3,  weight = 90  },
                { item = 'lockpick',         min = 1, max = 2,  weight = 60  },
                { item = 'repairkit',        min = 1, max = 1,  weight = 45  },
                { item = 'armor',            min = 1, max = 1,  weight = 40  },
                { item = 'weapon_pistol',    min = 1, max = 1,  weight = 25, unique = true,
                  metadata = { ammo = 40 } },
                { item = 'pistol_ammo',      min = 20, max = 60, weight = 55 },
                { account = 'money',         min = 1500, max = 4000, weight = 70,
                  label = 'Bargeld' },
                { account = 'black_money',   min = 2000, max = 6000, weight = 45,
                  label = 'Schwarzgeld' },
            },
        },
    },

    ---------------------------------------------------------------------
    rare = {
        label       = 'Seltener Airdrop',
        weight      = 28,
        plane       = 'titan',
        crateModel  = 'prop_mil_crate_01',
        blipColour  = 3,
        smokeColour = { r = 60, g = 140, b = 255 },
        container   = { slots = 16, maxWeight = 90000 },
        unlockTime  = 8000,
        announce    = true,
        loot = {
            rolls     = { min = 5, max = 7 },
            guaranteed = {
                { item = 'armor',   min = 1, max = 2 },
                { item = 'bandage', min = 3, max = 6 },
            },
            pool = {
                { item = 'weapon_smg',        min = 1, max = 1, weight = 30, unique = true,
                  metadata = { ammo = 60 } },
                { item = 'weapon_pumpshotgun',min = 1, max = 1, weight = 30, unique = true,
                  metadata = { ammo = 24 } },
                { item = 'weapon_carbinerifle',min = 1, max = 1, weight = 12, unique = true,
                  metadata = { ammo = 90 } },
                { item = 'smg_ammo',          min = 40, max = 120, weight = 70 },
                { item = 'rifle_ammo',        min = 40, max = 120, weight = 50 },
                { item = 'medikit',           min = 1, max = 2,  weight = 55 },
                { item = 'advancedlockpick',  min = 1, max = 2,  weight = 45 },
                { item = 'thermite',          min = 1, max = 1,  weight = 20, unique = true },
                { account = 'black_money',    min = 8000, max = 20000, weight = 60,
                  label = 'Schwarzgeld' },
                { item = 'goldbar',           min = 1, max = 3,  weight = 25 },
            },
        },
    },

    ---------------------------------------------------------------------
    exotic = {
        label       = 'Exotischer Airdrop',
        weight      = 12,
        plane       = 'velum',
        crateModel  = 'prop_mil_crate_01',
        blipColour  = 46,
        smokeColour = { r = 190, g = 60, b = 255 },
        container   = { slots = 18, maxWeight = 120000 },
        unlockTime  = 12000,
        announce    = true,
        loot = {
            rolls     = { min = 6, max = 8 },
            guaranteed = {
                { item = 'armor',   min = 2, max = 3 },
                { item = 'medikit', min = 2, max = 3 },
            },
            pool = {
                { item = 'weapon_specialcarbine', min = 1, max = 1, weight = 18, unique = true,
                  metadata = { ammo = 120 } },
                { item = 'weapon_heavysniper',    min = 1, max = 1, weight = 6,  unique = true,
                  metadata = { ammo = 20 } },
                { item = 'weapon_assaultrifle',   min = 1, max = 1, weight = 22, unique = true,
                  metadata = { ammo = 120 } },
                { item = 'rifle_ammo',            min = 90, max = 250, weight = 80 },
                { item = 'sniper_ammo',           min = 10, max = 30,  weight = 35 },
                { item = 'thermite',              min = 1, max = 2,   weight = 35 },
                { item = 'goldbar',               min = 3, max = 8,   weight = 45 },
                { item = 'diamond',               min = 1, max = 3,   weight = 25 },
                { account = 'black_money',        min = 25000, max = 60000, weight = 55,
                  label = 'Schwarzgeld' },
            },
        },
    },

    ---------------------------------------------------------------------
    medical = {
        label       = 'Sanitäts-Airdrop',
        weight      = 5,
        plane       = 'titan',
        crateModel  = 'prop_mil_crate_01',
        blipColour  = 2,
        smokeColour = { r = 60, g = 230, b = 90 },
        container   = { slots = 10, maxWeight = 50000 },
        unlockTime  = 3000,
        announce    = false,
        loot = {
            rolls     = { min = 3, max = 4 },
            guaranteed = {
                { item = 'medikit', min = 4, max = 8 },
                { item = 'bandage', min = 8, max = 15 },
            },
            pool = {
                { item = 'water',      min = 5, max = 10, weight = 100 },
                { item = 'bread',      min = 5, max = 10, weight = 100 },
                { item = 'painkillers',min = 2, max = 5,  weight = 70 },
                { item = 'armor',      min = 1, max = 2,  weight = 40 },
            },
        },
    },
}

--[[--------------------------------------------------------------------------
    ITEM-METADATEN für das eingebaute Loot-Fenster (nur relevant, wenn KEIN
    unterstütztes Inventar erkannt wird). Bild-Pfade sind optional.
----------------------------------------------------------------------------]]
Config.ItemDisplay = {
    imagePath = 'nui://ox_inventory/web/images/%s.png',  -- %s = Itemname
    labels = {
        -- ['goldbar'] = 'Goldbarren',
    },
}

Exports

Alle Exports sind serverseitig.

StartAirdrop(typeKey, coords, sourceType, triggeredBy) — startet einen Airdrop. Gibt die ID zurück, oder nil plus Fehlergrund.

  • typeKey — Schlüssel aus Config.AirdropTypes, oder nil für gewichteten Zufall
  • coordsvector3 für eine feste Position, oder nil für Zonenwahl
  • sourceType'auto' · 'admin' · 'flare' (nur fürs Logging)
  • triggeredBy — Server-ID des Auslösers oder nil
local id, err = exports.ancomox_airdrop:StartAirdrop('rare', nil, 'admin', source)
if not id then print('Fehlgeschlagen: ' .. tostring(err)) end

EndAirdrop(id, reason) — beendet einen Airdrop, entfernt Container und Entities.

exports.ancomox_airdrop:EndAirdrop(id, 'custom_reason')

GetActiveAirdrops() — liefert alle aktiven Airdrops als Tabelle { [id] = state } mit Typ, Zustand, Zone, Koordinaten und Despawn-Zeit.

for id, drop in pairs(exports.ancomox_airdrop:GetActiveAirdrops()) do
    print(id, drop.type, drop.state, drop.zoneName)
end

Events & Commands

Admin-Befehle

Berechtigung über ACE ancomox.airdrop oder eine Gruppe aus Config.Permissions.groups. Alle Befehle funktionieren auch aus der Serverkonsole.

Befehl Wirkung
/airdrop [typ] [hier] Airdrop starten. Ohne Typ: gewichteter Zufall. hier setzt ihn an die eigene Position (nicht aus der Konsole).
/airdropcancel <id> · /airdropcancel all Einzelnen Airdrop oder alle beenden.
/airdroplist Aktive Airdrops mit Zustand, Zone und Restlaufzeit.

Item-Export für ox_inventory

Flares werden bei ox_inventory nicht über RegisterUsableItem ausgelöst, sondern über das Export-Feld in der Item-Definition. Ohne diesen Eintrag passiert beim Benutzen nichts:

server = { export = 'ancomox_airdrop.useFlareItem' },

Discord-Logs

Fünf getrennte Webhooks, jeder einzeln aktivierbar. Ist eine URL leer, wird für diese Kategorie nichts gesendet.

Kategorie Wird ausgelöst bei
spawn Start eines Airdrops inklusive Typ, Zone, Koordinaten und Auslöser.
loot Erstes Öffnen der Kiste mit Spielername und Identifier.
collected Ende eines Airdrops mit Grund und Laufzeit.
admin Admin-gestartete Drops und benutzte Flares.
security Sicherheitsverstöße: Rate-Limit, fehlendes Unlock-Ticket, zu schnelles Aufbrechen, Distanzprüfung.
Config.Webhook.enabled = true
Config.Webhook.urls.spawn    = 'https://discord.com/api/webhooks/...'
Config.Webhook.urls.security = 'https://discord.com/api/webhooks/...'

Sicherheit

Was ein manipulierter Client nicht kann:

  • Koordinaten fälschen — die Position kommt immer aus GetEntityCoords(GetPlayerPed(src)) auf dem Server.
  • Aus der Ferne looten — jede Entnahme prüft Distanz und Höhenkorridor serverseitig neu.
  • Die Fortschrittsleiste überspringen — der Server vergibt ein einmaliges Unlock-Ticket mit Zeitstempel und lehnt zu schnelle Abschlüsse ab.
  • Ohne geöffneten Container Items nehmen — wer nicht regulär geöffnet hat, steht nicht in der Betrachterliste.
  • Items duplizieren — Mengen werden gegen NaN, ±inf und den Restbestand geprüft.
  • Cooldowns umgehen — Flare-Cooldowns liegen serverseitig und überleben mit SQL einen Neustart.
  • Events spammen — Rate-Limits gelten pro Spieler und pro Event-Typ; pro Zeitfenster wird höchstens ein Verstoß gemeldet.

Faire Behandlung bei unklarer Bodenhöhe: Solange die Höhe der Kiste noch nicht bestätigt ist, gilt eine stark erweiterte Höhentoleranz und es wird kein Sicherheitsverstoß protokolliert. Der Fehler läge dann beim Server, nicht beim Spieler.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
„Framework: standalone" obwohl ESX läuft Config.ResourceNames.esx prüfen (umbenannte Ressource) oder Config.Framework fest setzen.
Kiste schwebt oder steckt im Boden Zone auf preferPoints = true mit exakten Koordinaten umstellen. Die Bodensetzung wird zwar wiederholt, bis die Collision geladen ist — feste Punkte sind aber immer zuverlässiger.
Kein Flugzeug, Kiste erscheint sofort Config.Scene.enabled prüfen. Fehlt das Flugzeugmodell, steht eine Warnung in der Konsole.
Flugzeug auf der Karte, aber nicht am Himmel Normal, solange du weiter als Config.Scene.renderDistance (800 m) entfernt bist.
Kiste ist leer oder verschwindet direkt Die Item-Namen aus config/loot.lua existieren nicht in deinem Inventar. Das Script erkennt das, warnt in der Konsole und lässt die Kiste stehen, statt sie zu löschen.
Kein Rauch sichtbar Eines der Partikel-Dictionaries aus Config.Crate.smoke.variants muss ladbar sein. Die Liste wird der Reihe nach durchprobiert.
Flare tut nichts Bei ox_inventory fehlt server = { export = 'ancomox_airdrop.useFlareItem' } in der Item-Definition.
Spieler werden gekickt Config.Security.rateLimit erhöhen oder kickOnAbuse = false setzen.
Schwarzer Kasten hinter dem HUD Kommt von backdrop-filter — das rendert FiveMs CEF nicht transparent. Im Auslieferungszustand ist keines enthalten; eigene Ergänzungen entfernen und rgba(...) verwenden.

Ancomox Fishing — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Fishing
Zum Produkt

Ancomox Fishing — server-autoritatives Angelsystem mit eigenem Drill-Minigame, XP- und Levelsystem, dynamischem Markt, Turnieren und einem vollwertigen Bootsverleih. Das Markenzeichen: jeder Fang wird als echtes 3D-Modell präsentiert. ESX · QBCore · QBox.

Überblick

ancomox_fishing ist eine komplette Angel-Wirtschaft. Der Spieler kauft Rute und Köder, angelt am Wasser, kämpft im Drill-Minigame gegen den Fisch und verkauft den Fang legal am Fischmarkt oder illegal am Schwarzmarkt. Level, Zonen und Köder steuern, was überhaupt anbeißt.

Kernmerkmale:

  • 3D-Fang-Showcase — jeder gefangene Fisch erscheint als echtes Modell. Kleine Fische liegen neben dem Spieler am Boden, große Fänge wälzen sich im Wasser davor. Bei epischen und legendären Fängen fährt eine kurze Trophäen-Kamera darum herum.
  • Eigenes Drill-Minigame — der Fisch zieht in unregelmäßigen Fluchten, der Spieler hält mit der Leertaste dagegen und muss die Nadel in der grünen Zone halten. Rutenqualität und Fischstärke gehen direkt in die Physik ein; bei Überspannung reißt die Schnur.
  • 14 Arten in 5 Raritäten mit Gewichtsspannen, Tag-/Nachtzeiten, Wetter-Boni und Level-Anforderungen. Dazu Beifang wie Schrott, Perlen und eine Flaschenpost.
  • Einzigartige Fänge — der Buckelwal „Moby" kann pro Spieler nur ein einziges Mal gefangen werden.
  • XP und Level 1–50, persistent in der Datenbank, mit gebündelten Schreibvorgängen statt DB-Spam pro Fang.
  • Dynamischer Markt — Preise schwanken, Massenverkäufe drücken über die Marktsättigung den Preis, Gewicht und Qualität fließen ein.
  • Bootsverleih, komplett server-autoritativ, relog- und restartfest, mit drei Rückgabewegen.
  • Automatische Turniere nach schwerstem Gesamtgewicht, mit Zeitplan, Ankündigung, Mindestspielerzahl und Preisgeldern.

Voraussetzungen

Abhängigkeit Pflicht Zweck / Hinweis
oxmysql Ja XP, Statistiken und Bootsmieten. Als dependency im Manifest eingetragen.
es_extended, qb-core oder qbx_core Ja (eines) Wird über Config.Framework = 'auto' erkannt.
ox_inventory Empfohlen Nur dort gibt es echte Metadaten: individuelles Gewicht und Qualität pro Fisch sowie Ruten-Haltbarkeit. Alternativ qb-inventory oder das ESX-Inventar.
ox_target oder qb-target Optional Ohne Target-Script greift ein Marker-Fallback mit Tastendruck.

3D-Modelle: Das Script referenziert Basisspiel-Peds (a_c_fish, a_c_dolphin, a_c_sharkhammer, a_c_humpback …) ausschließlich per Modellname zur Laufzeit. Es wird keine Spieldatei mitgeliefert — geladen wird aus der GTA-V-Installation des Spielers.

Installation

  1. Ordner ancomox_fishing nach resources/ kopieren.
  2. ensure oxmysql und ensure ancomox_fishing in die server.cfg — nach dem Framework.
  3. Datenbank: nichts zu tun, beide Tabellen werden beim ersten Start automatisch angelegt.
  4. Items registrieren (siehe unten).
  5. Icons kopieren: die 28 Icons aus install/ox_inventory_images/ nach ox_inventory/web/images/ und dieselben Dateien zusätzlich nach html/images/ für die eigenen Menüs, plus boat.png.
  6. Admin-Rechte setzen:
add_ace group.admin ancomox_fishing.admin allow

Items registrieren

ox_inventory (empfohlen): Den kompletten Inhalt von install/ox_inventory_items.lua in ox_inventory/data/items.lua einfügen und ox_inventory neu starten. Die Definitionen enthalten bereits die server.export-Verknüpfungen — Ruten und Köder funktionieren sofort.

['fishing_rod_wood'] = {
    label = 'Holzrute',
    weight = 1500,
    stack = false,
    close = true,
    consume = 0,   -- das Script verwaltet den Verbrauch selbst
    description = 'Eine einfache Angelrute aus Holz.',
    server = { export = 'ancomox_fishing.fishing_rod_wood' },
},

QBCore: dieselben Items im QB-Format in qb-core/shared/items.lua.

ESX mit Datenbank-Items: die INSERT-Anweisungen am Ende von sql/ancomox_fishing.sql ausführen.

Warum stack = false bei Fischen? Jeder Fang trägt eigenes Gewicht und eigene Qualität als Metadata — beides fließt in den Verkaufspreis ein. Gestapelt ginge diese Information verloren.

Ingame-Zeit

Zeitgebundene Arten wie Wels und Schwertfisch brauchen die Ingame-Stunde. Der Server kennt sie nicht von sich aus — GetClockHours() ist ein reines Client-Native. Deshalb gibt es drei Quellen über Config.Time.source:

Wert Verhalten
'client' (Standard) Ein Spieler meldet die Ingame-Zeit periodisch. Autorität ist die niedrigste Server-ID, damit niemand dauerhaft „Nacht" faken kann. Zwischen zwei Meldungen läuft die Uhr serverseitig weiter.
'real' Echte Serverzeit über os.date.
'fixed' Immer Config.Time.fixedHour — zum Testen.

Wer einen Weather-Sync betreibt, kann Zeit und Regen auch direkt setzen:

exports.ancomox_fishing:SetGameTime(22, 30)
exports.ancomox_fishing:SetRaining(true)

Datenbank

Beide Tabellen werden beim Start automatisch angelegt. sql/ancomox_fishing.sql liegt für die manuelle Installation bei.

Tabelle Spalten Inhalt
ancomox_fishing identifier, xp, stats XP-Stand und Statistiken als JSON: Anzahl Fänge, schwerster Fang, Fänge pro Art und die Liste bereits gefangener Unikate.
ancomox_boat_rentals identifier, model, point, price, deposit, started_at, pending_refund Laufende Bootsmieten und offene Kautionen.

Kautionen können nicht verloren gehen. Nach einem Resource-Restart existiert kein Boot mehr; alle offenen Mieten werden in pending_refund umgewandelt und beim nächsten Login des Spielers automatisch ausgezahlt.

Bootsverleih

Das Boot wird vom Server erzeugt; der Client bekommt nur eine NetID. Kein Client kann ein fremdes Fahrzeug als Mietboot registrieren.

Vorgang Verhalten
Rückgabe am Dock Große grüne Zone auf dem Wasser (Standard 50 m). Funktioniert im Boot sitzend und daneben stehend. Volle Kaution zurück, abzüglich Schaden und Überziehung.
Fernrückgabe /bootabgeben von überall oder per Button im Menü. Das Boot wird abgeholt, es gibt die Kaution minus Abholgebühr (Standard 35 %).
Boot verloren Zerstört, gesunken oder verschwunden: die Miete wird automatisch geschlossen. Der Spieler hängt nie fest und kann sofort neu mieten.
Zurückholen Am Verleih gegen Gebühr — holt ein festgefahrenes Boot ans Dock zurück.
Schlüssel Nur der Mieter fährt (Statebag-basiert). Mitfahren dürfen alle.
Zeit & Schaden Nach maxRentalMinutes läuft eine Gebühr pro Minute — das Boot wird dem Spieler aber nie unter dem Hintern weggenommen, solange er darin sitzt. Verlassene Boote sammelt der Verleih nach despawnEmptyAfter Minuten ein.

Fünf Standorte sind vorkonfiguriert: Puerto Del Sol, Chumash Pier, Paleto Bay, Sonar Collections Dock und der Alamo-See. Die Platzierung korrigiert sich zur Laufzeit selbst — die Z-Höhe des NPCs wird auf den echten Boden gesetzt, der Wasser-Spawn per GetWaterHeight bestimmt und bei Bedarf spiralförmig bis 40 m weiter außen gesucht.

Konfiguration

Datei Inhalt
shared/config.lua Gameplay, Zeitquelle, Minigame, XP, Markt, Verkaufspunkte, Shops, Bootsverleih, Turniere, Webhooks, Sicherheit, Admin
shared/fish.lua Fischarten, Raritäten, Beifang, Ruten, Köder, Angelzonen
locales/*.lua Sprachen — Config.Locale = 'de' oder 'en'

Die wichtigsten Schalter

Key Standard Beschreibung
Config.BiteTime 600022000 Wartezeit bis zum Biss in ms, serverseitig gewürfelt.
Config.HookWindow 5000 Reaktionsfenster nach dem Biss, bevor der Fisch entkommt.
Config.Minigame.playerPull 3.2 Zugkraft des Spielers. Muss über dem maximalen Fischzug liegen, sonst sind starke Fische rechnerisch unfangbar.
Config.Minigame.minDurationRatio 0.6 Ein Erfolg unterhalb dieses Anteils der Solldauer ist physikalisch unmöglich und wird als Cheat gewertet.
Config.AutoRecast.enabled true Nach jedem Fang automatisch neu auswerfen, bis der Spieler mit X aufhört.
Config.RodDurability.fallbackBreakChance 3 Bruchchance in Prozent pro Fang bei Inventaren ohne Metadata. Mit ox_inventory greift stattdessen echte Haltbarkeit.
Config.Showcase.trophyCam true Kamerafahrt bei epischen und legendären Fängen.
Config.XP.maxLevel 50 Höchstlevel. base und exponent steuern die Kurve.
Config.Market.fluctuation 0.25 Preisschwankung von ±25 %, aktualisiert alle updateInterval Minuten.
Config.Market.saturation aktiv Massenverkäufe drücken den Preis einer Art bis auf floor und erholen sich langsam.
Config.BlackMoney.mode 'auto' ESX → Schwarzgeld-Konto, QB/QBox → Item falls vorhanden, sonst Bargeld. Der Schwarzmarkt zahlt dadurch immer aus.
Config.Security.minSecondsBetweenCatches 8 Mindestabstand zwischen zwei Fängen.
Config.Security.banOnRateLimit false Bewusst aus: ein fleißiger Spieler ist kein Cheater. Überschreitungen werden nur geloggt.

Eigene Fischart anlegen

Fish.Species['pike'] = {
    label = 'Hecht', rarity = 'uncommon', model = 'a_c_fish',
    strength = 4,                    -- 1–10, bestimmt Minigame und nötige Rute
    kg = { 1.5, 12.0 },              -- Gewichtsspanne, serverseitig gewürfelt
    price = 90,                      -- Basispreis, der Markt skaliert ihn
    habitats = { 'freshwater' },     -- coastal | pier | reef | freshwater | deep | protected
    time = { from = 5, to = 11 },    -- optional: nur morgens
    weather = 'rain',                -- optional: doppelte Chance bei Regen
    minLevel = 6,                    -- optional
}

Für den Verkauf und die Anzeige zusätzlich ein Item mit demselben Namen anlegen und pike.png in beide Bilderordner legen.

Zonen-Überlappung: Liegen zwei Zonen übereinander, gewinnt immer die kleinste. Das Ergebnis ist damit unabhängig von der Reihenfolge in der Config.

Vollständige Config-Dateien

shared/config.lua337 Zeilen · vollständige Datei
Config = {}

--------------------------------------------------------------------------------
-- ANCOMOX FISHING — HAUPTKONFIGURATION
--------------------------------------------------------------------------------
Config.Locale = 'de'                 -- 'de' | 'en'
Config.Debug  = false

-- Framework: 'auto' erkennt ESX / QBCore / Qbox automatisch
Config.Framework = 'auto'            -- 'auto' | 'esx' | 'qb' | 'standalone'

-- Inventar: 'auto' erkennt ox_inventory / qb-inventory / ESX
Config.Inventory = 'auto'            -- 'auto' | 'ox' | 'qb' | 'esx'

-- Optionale Integrationen (werden automatisch erkannt, wenn 'auto')
Config.Target = 'auto'               -- 'auto' | 'ox_target' | 'qb-target' | 'none' (Marker-Fallback)

--------------------------------------------------------------------------------
-- ZEITQUELLE
-- Zeitgebundene Fische (Wels nachts, Schwertfisch nachts ...) brauchen die
-- INGAME-Stunde. Der Server kennt sie nicht von sich aus, darum:
--   'client' = ein Spieler meldet die Ingame-Zeit periodisch (empfohlen)
--   'real'   = echte Serverzeit (os.date)
--   'fixed'  = immer Config.Time.fixedHour (zum Testen)
--------------------------------------------------------------------------------
Config.Time = {
    source    = 'client',
    fixedHour = 12,
    syncEvery = 60,      -- Sekunden zwischen zwei Zeit-Meldungen
}

--------------------------------------------------------------------------------
-- GAMEPLAY
--------------------------------------------------------------------------------
Config.Keys = {
    cancel = 73,   -- X: Angeln abbrechen
    reel   = 38,   -- E: Interaktionen
}

-- Wartezeit bis ein Fisch anbeißt (ms) — wird serverseitig gewürfelt & validiert
Config.BiteTime = { min = 6000, max = 22000 }

-- Modifikatoren auf die Bite-Zeit (multiplikativ, serverseitig angewandt)
Config.BiteModifiers = {
    rain  = 0.75,   -- bei Regen beißen Fische schneller
    night = 0.85,   -- 22:00–05:00 Ingame
    chum  = 0.6,    -- Anfutter (chum) aktiv
}

-- Zeitfenster (ms), um nach dem Biss zu reagieren, bevor der Fisch entkommt
Config.HookWindow = 5000

-- Minigame
Config.Minigame = {
    baseDuration     = 7000,   -- Basis-Zeit IN der grünen Zone (ms), skaliert mit Stärke
    lineBreakEnabled = true,
    -- Zugkraft des Spielers beim Halten der Leertaste. Wird mit tensionControl
    -- der Rute multipliziert. Muss > maximaler Fischzug sein, sonst sind starke
    -- Fische mathematisch unfangbar.
    playerPull       = 3.2,
    -- Serverseitige Toleranz: Ein Erfolg unterhalb dieses Anteils der Solldauer
    -- ist physikalisch unmöglich -> wird als Cheat gewertet.
    minDurationRatio = 0.6,
}

-- Rutenbruch/-abnutzung
Config.RodDurability = {
    enabled      = true,
    lossPerCatch = { min = 2, max = 5 },   -- Haltbarkeitsverlust pro Fang (von 100)
    breakOnZero  = true,                   -- Rute wird bei 0 zerstört
    -- Inventare OHNE Metadata (ESX/qb ohne Durability): prozentuale Bruchchance.
    -- Wird bewusst niedrig gehalten, damit Ruten nicht dauernd zerbrechen.
    fallbackBreakChance = 3,               -- Prozent pro Fang
}

-- Fische mit individuellem Gewicht/Qualität als Metadata (ox_inventory / qb metadata)
Config.UseMetadata = true

--------------------------------------------------------------------------------
-- 3D-FISCH-SHOWCASE (das Markenzeichen)
-- Kleine Fische landen neben dem Spieler auf dem Boden, die Angel bleibt in
-- der Hand. Große Fänge wälzen sich im Wasser vor dem Spieler.
--------------------------------------------------------------------------------
Config.Showcase = {
    enabled     = true,
    duration    = 5000,        -- Anzeige-Dauer kleiner Fische (ms)
    bigDuration = 7000,        -- Anzeige-Dauer großer Fänge (ms)
    trophyCam   = true,        -- kurze Kamera-Fahrt bei Epic/Legendary
    smallOffset = vector3(0.9, -0.4, 0.0),
    bigDistance = 7.0,
}

-- Auto-Recast: Nach jedem Fang/Fehlversuch wirft der Spieler automatisch
-- wieder aus, bis er selbst mit X aufhört.
Config.AutoRecast = {
    enabled = true,
    delay   = 3500,   -- ms Pause zwischen den Würfen
}

--------------------------------------------------------------------------------
-- XP & LEVEL
--------------------------------------------------------------------------------
Config.XP = {
    maxLevel  = 50,
    base      = 80,
    exponent  = 1.35,
    perRarity = { common = 5, uncommon = 12, rare = 30, epic = 75, legendary = 200 },
    perKg     = 1,
}

--------------------------------------------------------------------------------
-- GELD
-- QBCore/Qbox haben kein natives Schwarzgeld-Konto. Hier festlegen, wie
-- illegale Auszahlungen laufen. 'item' braucht ein existierendes Item!
--------------------------------------------------------------------------------
Config.BlackMoney = {
    mode = 'auto',            -- 'auto' | 'account' | 'item' | 'cash'
    item = 'black_money',     -- nur bei mode = 'item'
    -- 'auto': ESX -> black_money-Konto, QB/Qbox -> Item wenn vorhanden, sonst Bargeld
}

--------------------------------------------------------------------------------
-- VERKAUF & DYNAMISCHER MARKT
--------------------------------------------------------------------------------
Config.Market = {
    dynamic        = true,
    updateInterval = 30,        -- Minuten
    fluctuation    = 0.25,      -- ±25 %
    saturation     = { enabled = true, decayPerSale = 0.004, recoveryPerMin = 0.002, floor = 0.5 },
}

Config.SellPoints = {
    {
        id      = 'fishmarket',
        label   = 'Fischmarkt',
        type    = 'legal',                -- Auszahlung: Bargeld
        coords  = vector3(-1816.5, -1193.6, 14.3),
        ped     = 's_m_m_linecook',
        heading = 320.0,
        blip    = { sprite = 356, colour = 74, scale = 0.75 },
        -- Beifang & Sammlerstücke, die hier ebenfalls angenommen werden
        extraItems = {
            { item = 'pearl',      label = 'Perle',         price = 850 },
            { item = 'seaweed',    label = 'Seetang',       price = 5 },
            { item = 'trash_boot', label = 'Alter Stiefel', price = 2 },
            { item = 'trash_can',  label = 'Rostige Dose',  price = 2 },
        },
    },
    {
        id      = 'blackmarket',
        label   = 'Schwarzmarkt',
        type    = 'illegal',              -- Auszahlung: Schwarzgeld, kein Blip
        coords  = vector3(1522.9, 3911.0, 30.4),
        ped     = 'g_m_m_chigoon_01',
        heading = 200.0,
        blip    = nil,
        policeAlertChance = 0.15,
    },
}

--------------------------------------------------------------------------------
-- SHOPS (Ruten, Köder, Zubehör)
--------------------------------------------------------------------------------
Config.Shops = {
    {
        id      = 'baitshop_pier',
        label   = 'Angelladen',
        coords  = vector3(-1850.1, -1218.3, 13.0),
        ped     = 'a_m_m_beach_01',
        heading = 315.0,
        blip    = { sprite = 68, colour = 3, scale = 0.7 },
        items   = {
            { item = 'fishing_rod_wood',    price = 250 },
            { item = 'fishing_rod_fiber',   price = 1500,  minLevel = 5 },
            { item = 'fishing_rod_carbon',  price = 6500,  minLevel = 15 },
            { item = 'fishing_rod_pro',     price = 20000, minLevel = 30 },
            { item = 'bait_worms',          price = 15 },
            { item = 'bait_shrimp',         price = 45,    minLevel = 5 },
            { item = 'bait_squid',          price = 90,    minLevel = 12 },
            { item = 'bait_lure',           price = 400,   minLevel = 10 },
            { item = 'bait_chum',           price = 250,   minLevel = 20 },
        },
    },
}

--------------------------------------------------------------------------------
-- BOOTSVERLEIH  (komplett server-autoritativ)
--
-- WICHTIG ZU DEN POSITIONEN:
--   ped   = Interaktionspunkt an Land (Verleiher-NPC). Die Z-Höhe wird beim
--           Spawn automatisch auf den echten Boden korrigiert.
--   water = Punkt auf dem Wasser, an dem das Boot erscheint. Die Z-Höhe wird
--           per GetWaterHeight ermittelt; findet das Script dort kein Wasser,
--           sucht es spiralförmig bis 40 m weiter außen.
--   Dadurch sind kleine Abweichungen in den Koordinaten unkritisch.
--
--   Feinjustieren: als Admin ins Spiel, an die gewünschte Stelle stellen und
--   /fishingpos  eingeben -> die Konsole gibt einen fertigen Config-Block aus.
--------------------------------------------------------------------------------
Config.BoatRental = {
    enabled = true,

    -- Kaution: Anteil des Mietpreises, der bei ordentlicher Rückgabe zurückkommt
    depositShare = 0.5,

    -- Mietdauer in Minuten. Danach läuft die Überziehungsgebühr, das Boot wird
    -- aber NICHT einfach entfernt, solange der Spieler drin sitzt.
    maxRentalMinutes  = 90,
    overtimeFeePerMin = 20,

    -- Fernrückgabe ("Verleih anrufen", /boot zurückgeben von überall):
    -- Das Boot wird abgeholt, es gibt aber nur einen Teil der Kaution zurück.
    remoteReturn    = true,
    remoteReturnFee = 0.35,      -- 35 % Abzug von der Kaution

    -- Rückgabe-Radius am Dock. Bewusst großzügig: die Rückgabe klappt im Boot
    -- sitzend UND daneben stehend, solange Spieler und Boot im Radius sind.
    returnRadius = 50.0,

    -- Schaden mindert die Kaution (0.0 = heil, 1.0 = Schrott)
    damageDeduction    = true,
    damageMaxDeduction = 1.0,

    -- Boot zurückholen, falls es festhängt / weggetrieben ist.
    -- Der Spieler muss dafür am Verleih stehen.
    recall    = true,
    recallFee = 250,

    -- Nur der Mieter darf das Boot fahren (Statebag-basiert)
    keysOnly = true,

    -- Verlassenes Boot automatisch einsammeln (Minuten ohne Insassen).
    -- 0 = aus. Die Kaution wird dabei anteilig wie bei Fernrückgabe erstattet.
    despawnEmptyAfter = 15,

    -- Gesunkenes/zerstörtes Boot: Anteil der Kaution, der trotzdem zurückkommt
    destroyedRefundShare = 0.0,

    points = {
        {
            id    = 'marina_laputa',
            label = 'Puerto Del Sol — Bootsverleih',
            -- Slipanlage La Puerta, direkt an der Marina (Los Santos)
            ped   = vector4(-721.98, -1330.15, 1.60, 132.0),
            water = vector4(-728.44, -1341.30, 0.00, 132.0),
            npc   = 'a_m_m_beach_01',
            blip  = { sprite = 455, colour = 74, scale = 0.7 },
        },
        {
            id    = 'marina_chumash',
            label = 'Chumash Pier — Bootsverleih',
            ped   = vector4(-3426.70, 955.66, 7.35, 87.0),
            water = vector4(-3427.00, 942.00, 0.00, 180.0),
            npc   = 'a_m_m_beach_01',
            blip  = { sprite = 455, colour = 74, scale = 0.7 },
        },
        {
            id    = 'marina_paleto',
            label = 'Paleto Bay — Bootsverleih',
            ped   = vector4(-281.25, 6632.10, 7.60, 45.0),
            water = vector4(-306.00, 6650.00, 0.00, 45.0),
            npc   = 'a_m_m_beach_01',
            blip  = { sprite = 455, colour = 74, scale = 0.7 },
        },
        {
            id    = 'marina_sonar',
            label = 'Sonar Collections Dock — Bootsverleih',
            ped   = vector4(3855.00, 4463.70, 2.60, 210.0),
            water = vector4(3874.00, 4487.00, 0.00, 300.0),
            npc   = 'a_m_m_beach_01',
            blip  = { sprite = 455, colour = 74, scale = 0.7 },
        },
        {
            id    = 'marina_alamo',
            label = 'Alamo-See — Bootsverleih',
            -- Süßwasser: Zugang zum Alamo Sea bei Catfish View
            ped   = vector4(1298.00, 4216.00, 33.90, 180.0),
            water = vector4(1307.00, 4234.00, 28.50, 180.0),
            npc   = 'a_m_m_farmer_01',
            blip  = { sprite = 455, colour = 42, scale = 0.7 },
        },
    },

    boats = {
        { model = 'seashark', price = 500 },
        { model = 'dinghy',   price = 1000 },
        { model = 'tropic',   price = 1500 },
        { model = 'speeder',  price = 2000, minLevel = 10 },
        { model = 'toro',     price = 3500, minLevel = 20 },
        { model = 'marquis',  price = 5000, minLevel = 25 },
    },
}

--------------------------------------------------------------------------------
-- TURNIERE
--------------------------------------------------------------------------------
Config.Tournament = {
    enabled               = true,
    autoSchedule          = { '00:00' },   -- Serverzeit (Echtzeit), 24h
    duration              = 30,            -- Minuten
    minPlayers            = 3,             -- darunter wird nicht gestartet
    prizes                = { [1] = 15000, [2] = 7500, [3] = 3000 },
    announceMinutesBefore = 15,
}

--------------------------------------------------------------------------------
-- LOGGING (Discord Webhooks)
--------------------------------------------------------------------------------
Config.Webhooks = {
    catches = '',    -- URL eintragen, leer = aus (nur epic/legendary werden geloggt)
    sales   = '',
    boats   = '',
    admin   = '',
}

--------------------------------------------------------------------------------
-- ADMIN
--------------------------------------------------------------------------------
Config.Admin = {
    -- ACE-Berechtigung für /fishingpos und Admin-Befehle
    ace = 'ancomox_fishing.admin',
    -- Zusätzlich erlaubte Framework-Gruppen
    groups = { 'admin', 'superadmin', 'god' },
}

--------------------------------------------------------------------------------
-- ANTI-CHEAT / SICHERHEIT (serverseitig erzwungen)
--------------------------------------------------------------------------------
Config.Security = {
    minSecondsBetweenCatches = 8,     -- härter als jede legitime Spielweise
    maxCatchesPerHour        = 220,
    -- Bei Überschreitung nur loggen statt bannen. Ein Ban-Event hier ist
    -- gefährlich: ein fleißiger Spieler ist kein Cheater.
    banOnRateLimit           = false,
    banEventName             = '',    -- z. B. 'myac:ban'
}
shared/fish.lua240 Zeilen · vollständige Datei
--------------------------------------------------------------------------------
-- ANCOMOX FISHING — FISCH-DATENBANK, RUTEN, KÖDER, ZONEN
-- Alles hier ist shared: Client nutzt es für Anzeige, Server für Validierung.
--------------------------------------------------------------------------------
Fish = {}

-- Raritäten (Farbe fürs NUI, Basis-Gewichtung)
Fish.Rarities = {
    common    = { label = 'Gewöhnlich',   colour = '#9aa5b1', weight = 100 },
    uncommon  = { label = 'Ungewöhnlich', colour = '#4ecb71', weight = 45 },
    rare      = { label = 'Selten',       colour = '#3b9dff', weight = 16 },
    epic      = { label = 'Episch',       colour = '#b45cff', weight = 5 },
    legendary = { label = 'Legendär',     colour = '#ffb02e', weight = 1 },
}

--------------------------------------------------------------------------------
-- ARTEN
-- model     = 3D-Modell fürs Showcase (Basisspiel-Ped, wird zur Laufzeit geladen)
-- strength  = 1–10, beeinflusst Minigame-Schwierigkeit & benötigte Rute
-- kg        = Gewichtsspanne; Gewicht wird serverseitig gewürfelt
-- price     = Basispreis pro Stück (dynamischer Markt skaliert das)
-- habitats  = in welchen Zonentypen die Art vorkommt
-- time      = optional: { from = 22, to = 5 } (Ingame-Stunden), nil = immer
-- weather   = optional: 'rain' = bevorzugt bei Regen (Chance x2)
-- minLevel  = benötigtes Angel-Level
--------------------------------------------------------------------------------
Fish.Species = {
    -- COMMON ------------------------------------------------------------------
    sardine = {
        label = 'Sardine', rarity = 'common', model = 'a_c_fish',
        strength = 1, kg = { 0.05, 0.2 }, price = 8,
        habitats = { 'coastal', 'pier' },
    },
    mackerel = {
        label = 'Makrele', rarity = 'common', model = 'a_c_fish',
        strength = 2, kg = { 0.3, 1.2 }, price = 14,
        habitats = { 'coastal', 'pier', 'reef' },
    },
    perch = {
        label = 'Flussbarsch', rarity = 'common', model = 'a_c_fish',
        strength = 2, kg = { 0.2, 1.5 }, price = 12,
        habitats = { 'freshwater' },
    },

    -- UNCOMMON ----------------------------------------------------------------
    bass = {
        label = 'Wolfsbarsch', rarity = 'uncommon', model = 'a_c_fish',
        strength = 3, kg = { 1.0, 4.0 }, price = 45,
        habitats = { 'coastal', 'reef' },
    },
    salmon = {
        label = 'Lachs', rarity = 'uncommon', model = 'a_c_fish',
        strength = 4, kg = { 2.0, 8.0 }, price = 70,
        habitats = { 'freshwater' }, weather = 'rain',
    },
    catfish = {
        label = 'Wels', rarity = 'uncommon', model = 'a_c_fish',
        strength = 5, kg = { 3.0, 25.0 }, price = 85,
        habitats = { 'freshwater' }, time = { from = 21, to = 6 },
    },

    -- RARE --------------------------------------------------------------------
    stingray = {
        label = 'Stachelrochen', rarity = 'rare', model = 'a_c_stingray',
        strength = 6, kg = { 5.0, 35.0 }, price = 320,
        habitats = { 'coastal', 'reef', 'deep' }, minLevel = 8,
    },
    tuna = {
        label = 'Blauflossen-Thunfisch', rarity = 'rare', model = 'a_c_fish',
        strength = 7, kg = { 20.0, 120.0 }, price = 550,
        habitats = { 'deep' }, minLevel = 12,
    },
    swordfish = {
        label = 'Schwertfisch', rarity = 'rare', model = 'a_c_fish',
        strength = 8, kg = { 30.0, 150.0 }, price = 720,
        habitats = { 'deep' }, minLevel = 15, time = { from = 20, to = 7 },
    },

    -- EPIC --------------------------------------------------------------------
    dolphin = {
        label = 'Delfin', rarity = 'epic', model = 'a_c_dolphin',
        strength = 8, kg = { 80.0, 200.0 }, price = 1800, illegal = true,
        habitats = { 'deep', 'protected' }, minLevel = 20,
    },
    sharktiger = {
        label = 'Tigerhai', rarity = 'epic', model = 'a_c_sharktiger',
        strength = 9, kg = { 150.0, 500.0 }, price = 4200, illegal = true,
        habitats = { 'deep', 'protected' }, minLevel = 25,
    },

    -- LEGENDARY ---------------------------------------------------------------
    sharkhammer = {
        label = 'Hammerhai', rarity = 'legendary', model = 'a_c_sharkhammer',
        strength = 9, kg = { 200.0, 450.0 }, price = 8500, illegal = true,
        habitats = { 'deep', 'protected' }, minLevel = 32,
    },
    killerwhale = {
        label = 'Orca', rarity = 'legendary', model = 'a_c_killerwhale',
        strength = 10, kg = { 1500.0, 4000.0 }, price = 16000, illegal = true,
        habitats = { 'protected' }, minLevel = 40,
    },
    humpback = {
        label = 'Buckelwal "Moby"', rarity = 'legendary', model = 'a_c_humpback',
        strength = 10, kg = { 20000.0, 30000.0 }, price = 40000, illegal = true,
        habitats = { 'protected' }, minLevel = 48, unique = true, -- 1x pro Spieler
    },
}

-- BEIFANG (kein Fisch, aber Loot/Trash — hält das Loop spannend)
-- Verkauft wird das über Config.SellPoints[].extraItems
Fish.Bycatch = {
    { item = 'trash_boot',   label = 'Alter Stiefel', chance = 50 },
    { item = 'trash_can',    label = 'Rostige Dose',  chance = 35 },
    { item = 'seaweed',      label = 'Seetang',       chance = 30 },
    { item = 'treasure_map', label = 'Flaschenpost',  chance = 4 },
    { item = 'pearl',        label = 'Perle',         chance = 2 },
}

Fish.BycatchChance = 0.12   -- 12 % der Bisse sind Beifang

--------------------------------------------------------------------------------
-- RUTEN
-- maxStrength = stärkster Fisch, den die Rute halten kann. Alles darüber reißt.
-- tensionControl = Multiplikator auf die Zugkraft im Minigame.
--------------------------------------------------------------------------------
Fish.Rods = {
    fishing_rod_wood = {
        label = 'Holzrute', tier = 1, luck = 0,
        tensionControl = 1.0, maxStrength = 5,
    },
    fishing_rod_fiber = {
        label = 'Fiberglasrute', tier = 2, luck = 5,
        tensionControl = 1.12, maxStrength = 7, minLevel = 5,
    },
    fishing_rod_carbon = {
        label = 'Carbonrute', tier = 3, luck = 12,
        tensionControl = 1.22, maxStrength = 9, minLevel = 15,
    },
    fishing_rod_pro = {
        label = 'Hochsee-Profirute', tier = 4, luck = 20,
        tensionControl = 1.32, maxStrength = 10, minLevel = 30,
    },
}

--------------------------------------------------------------------------------
-- KÖDER
--------------------------------------------------------------------------------
Fish.Baits = {
    bait_worms  = { label = 'Würmer',      luck = 0 },
    bait_shrimp = { label = 'Garnelen',    luck = 5,  rarityBoost = { uncommon = 1.3 } },
    bait_squid  = { label = 'Tintenfisch', luck = 10, rarityBoost = { rare = 1.5 } },
    bait_lure   = { label = 'Kunstköder',  luck = 8,  reusable = 0.75,
                    rarityBoost = { uncommon = 1.2, rare = 1.2 } },
    bait_chum   = { label = 'Anfutter',    luck = 15, rarityBoost = { epic = 1.6, legendary = 1.4 } },
}

--------------------------------------------------------------------------------
-- ANGELZONEN
-- 'coastal' ist der Fallback: überall am Meer ohne spezielle Zone.
-- Die Radien dürfen Land überdecken — geangelt wird ohnehin nur am Wasser.
-- Wichtig ist nur, dass sich Zonen nicht ungewollt überlappen.
--------------------------------------------------------------------------------
Fish.DefaultHabitat = 'coastal'

Fish.Zones = {
    {
        id = 'pier_delperro', habitat = 'pier',
        label = 'Del Perro Pier',
        center = vector3(-1850.0, -1249.0, 8.6), radius = 120.0,
        blip = { sprite = 68, colour = 3 },
    },
    {
        id = 'reef_paleto', habitat = 'reef',
        label = 'Paleto-Riff',
        center = vector3(-1300.0, 6600.0, 0.0), radius = 250.0, minLevel = 5,
        blip = { sprite = 68, colour = 26 },
    },
    {
        id = 'lake_alamo', habitat = 'freshwater',
        label = 'Alamo-See',
        center = vector3(1300.0, 4230.0, 30.0), radius = 900.0,
        blip = { sprite = 68, colour = 42 },
    },
    {
        id = 'river_zancudo', habitat = 'freshwater',
        label = 'Zancudo-Fluss',
        center = vector3(-2200.0, 2400.0, 0.0), radius = 600.0,
    },
    {
        -- Bewusst weit draußen: so bleibt der Chumash Pier normale Küste und
        -- wird nicht fälschlich zur "nur mit Boot"-Hochseezone.
        id = 'deep_pacific', habitat = 'deep',
        label = 'Hochsee (Pazifik)',
        center = vector3(-5100.0, 1200.0, 0.0), radius = 1200.0,
        minLevel = 12, requiresBoat = true,
        blip = { sprite = 68, colour = 29 },
    },
    {
        id = 'deep_south', habitat = 'deep',
        label = 'Hochsee (Süd)',
        center = vector3(500.0, -4600.0, 0.0), radius = 1400.0,
        minLevel = 12, requiresBoat = true,
    },
    {
        id = 'protected_reserve', habitat = 'protected',
        label = 'Geschütztes Reservat',
        center = vector3(4200.0, 8000.0, 0.0), radius = 1200.0,
        minLevel = 20, requiresBoat = true, illegal = true,
        policeAlertChance = 0.2,
        -- kein Blip: muss entdeckt werden (Flaschenpost verrät die Koordinaten)
    },
}

--------------------------------------------------------------------------------
-- HILFSFUNKTIONEN (shared)
--------------------------------------------------------------------------------
-- Bei Überlappungen gewinnt die KLEINSTE Zone. Damit ist das Ergebnis
-- unabhängig von der Reihenfolge in der Config.
function Fish.GetZoneAt(coords)
    local best, bestRadius
    local flat = vector3(coords.x, coords.y, 0.0)
    for _, zone in ipairs(Fish.Zones) do
        local d = #(flat - vector3(zone.center.x, zone.center.y, 0.0))
        if d <= zone.radius and (not bestRadius or zone.radius < bestRadius) then
            best, bestRadius = zone, zone.radius
        end
    end
    return best
end

function Fish.SpeciesForHabitat(habitat)
    local out = {}
    for name, s in pairs(Fish.Species) do
        for _, h in ipairs(s.habitats) do
            if h == habitat then out[#out + 1] = name break end
        end
    end
    return out
end

Exports

Alle Exports sind serverseitig.

Export Wirkung
GetFishingLevel(source) Aktuelles Angel-Level des Spielers.
AddFishingXP(source, amount) XP vergeben, inklusive Level-Up-Behandlung.
StartTournament() Turnier sofort starten. Gibt false plus Grund zurück (running, disabled, players).
IsTournamentActive() Läuft gerade ein Turnier?
SetGameTime(hour, minute) Ingame-Zeit setzen (für Weather-Sync-Ressourcen).
SetRaining(state) Regen-Status setzen — beeinflusst Bisszeit und Lachs-Chance.
GetGameHour() Aktuell angenommene Ingame-Stunde.
HasRentalBoat(source) Hat der Spieler ein Boot gemietet?
ForceReturnBoat(source) Miete administrativ abschließen und Kaution abrechnen.
local level = exports.ancomox_fishing:GetFishingLevel(source)
if level >= 20 then
    exports.ancomox_fishing:AddFishingXP(source, 250)
end

Polizei-Hook

Illegales Angeln im geschützten Reservat und Verkäufe am Schwarzmarkt lösen ein Event aus. Hier bindest du dein Dispatch-System an:

AddEventHandler('ancomox_fishing:policeAlert', function(src, coords, reason)
    -- reason: 'illegal_fishing' | 'illegal_sale'
    exports['ps-dispatch']:CustomAlert({ coords = coords, message = 'Illegales Angeln' })
end)

Events & Commands

Befehl Wer Wirkung
/bootabgeben Spieler Mietboot von überall zurückgeben, gegen Abholgebühr.
/bootfinden Spieler Setzt eine Route zum eigenen Mietboot.
/fishingpos Admin Koordinaten und Heading für einen Shop- oder Verkaufspunkt in die F8-Konsole schreiben.
/fishingpos boat Admin Fertigen Bootsverleih-Block ausgeben, inklusive gemessener Wasserhöhe.
/fishtournament · /turnier Admin Turnier sofort starten. Beide Befehle sind identisch.

Discord-Logs

Vier getrennte Webhooks in Config.Webhooks. Leere URL bedeutet, dass für diese Kategorie nichts gesendet wird.

Kategorie Wird ausgelöst bei
catches Fänge der Raritäten episch und legendär, mit Art, Gewicht und Spieler.
sales Jeder Verkauf mit Summe und Art des Verkaufspunkts (legal / illegal).
boats Anmietung und Rückgabe inklusive Modus, Kaution, Schadensanteil und Gebühren.
admin Turnierergebnisse und Cheat-Verdachtsfälle: zu schnelles Minigame, zu kurzes Fangintervall, Rate-Limit.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Rute oder Köder reagiert nicht Bei ox_inventory fehlt server = { export = 'ancomox_fishing.<itemname>' } in der Item-Definition. Nur darüber löst ox_inventory das Item aus.
Nachtaktive Fische beißen nie Config.Time.source prüfen. Bei 'client' muss mindestens ein Spieler online sein, der die Zeit meldet. Zum Testen 'fixed' mit fixedHour = 23 setzen.
Minigame ist unschaffbar Config.Minigame.playerPull erhöhen. Der Spielerzug muss über dem maximalen Fischzug liegen, sonst wandert die Nadel zwangsläufig in den roten Bereich.
Ruten zerbrechen ständig Ohne Metadata-Inventar greift fallbackBreakChance. Wert senken oder auf ox_inventory wechseln.
Schwarzmarkt zahlt nichts aus Kein black_money-Item vorhanden. Im 'auto'-Modus wird automatisch Bargeld gezahlt und ein Hinweis in die Konsole geschrieben; alternativ Config.BlackMoney.mode fest setzen.
„Diese Zone erreichst du nur mit einem Boot" Die Position liegt in einer Zone mit requiresBoat = true. Der Spieler muss auf einem stehenden Boot sitzen oder darauf stehen.
Bilder fehlen im Verkaufs- oder Shop-Menü Die Menüs laden aus html/images/. Fehlt eine Datei, blendet das UI das Bild aus — funktionsfähig bleibt es.
Boot ist weg, Kaution scheinbar verloren Kann nicht passieren: Nach Restart oder Verlust landet der Betrag als pending_refund in der Datenbank und wird beim nächsten Login ausgezahlt.
Turnier startet nicht Config.Tournament.minPlayers prüfen. Die Serverkonsole schreibt bei jedem Versuch, wie viele Spieler online waren und warum abgebrochen wurde.
Peds oder Blips bleiben nach Restart liegen Sollte nicht auftreten — beim onResourceStop werden alle Peds und Blips entfernt. Tritt es doch auf, läuft eine alte Version.

DriveOS — Entwickler-Dokumentation

Im Ancomox-StoreDriveOS
Zum Produkt

DriveOS — vollautomatische Fahrschule für FiveM. Theorie- und Praxisprüfung laufen ohne Fahrlehrer, rund um die Uhr. ESX · QBCore · Qbox · ox_core · Standalone, ohne Pflicht-Framework und ohne Pflicht-Datenbank.

Überblick

Auf den meisten Servern scheitert der Führerschein daran, dass kein Fahrlehrer online ist. DriveOS ersetzt den menschlichen Prüfer: Der Spieler geht zur Fahrschule, wählt seine Klasse, absolviert die Theorie, fährt die Prüfungsstrecke und bekommt am Ende die Lizenz im Framework eingetragen.

Vier Führerscheinklassen sind fertig konfiguriert:

Klasse Fahrzeug Theorie Praxis Voraussetzung
A — Motorrad pcj 12 Fragen · 80 % 70 Punkte
B — PKW asterope 15 Fragen · 80 % 70 Punkte
C — LKW mule 12 Fragen · 85 % 75 Punkte Klasse B
D — Bus coach 12 Fragen · 85 % 75 Punkte Klasse B

Fahrzeuge, Gebühren, Fragenzahl, Bestehensgrenzen und Voraussetzungen stehen alle in der config.lua. Eine neue Klasse entsteht durch Kopieren eines Blocks.

Theorieprüfung

  • Die Lösungen liegen ausschließlich auf dem Server. Der Client erhält Frage und Antwortmöglichkeiten, nie die richtige Antwort — weder auslesbar noch über einen NUI-Trick manipulierbar.
  • Zufälliger Fragenbogen pro Versuch. Zwei Prüfungen sind nie identisch.
  • Über 60 deutsche Fragen sind hinterlegt, thematisch nach Klasse sortiert.
  • Zeitlimit mit Server-Watchdog: Läuft die Zeit ab, wertet der Server aus — auch wenn der Spieler das Spiel schließt.
  • Fenster schließen zählt als nicht bestanden (abschaltbar), danach greift eine Sperrzeit.
  • Eine bestandene Theorie verfällt nach X Tagen (Standard 14) und wird nach bestandener Praxis verbraucht.

Praktische Prüfung

Jeder startet mit 100 Punkten:

Ereignis Abzug
Leichter Tempoverstoß (ab 8 km/h über Limit) −5
Grober Tempoverstoß (ab 21 km/h über Limit) −15
Kollision −10
Schwere Kollision −25

Sofort durchgefallen bei zerstörtem oder brennendem Fahrzeug, Fahndungslevel während der Prüfung, Verlassen der Route (Standard: mehr als 200 m zum nächsten Checkpoint), mehr als 20 Sekunden außerhalb des Fahrzeugs oder überschrittenem Zeitlimit.

Für alle vier Klassen sind Checkpoint-Routen hinterlegt — mit GPS-Route, Restzeit, Tempolimit je Abschnitt und Hinweistexten. Die Busprüfung enthält Haltestellen-Checkpoints, an denen wirklich vollständig angehalten werden muss. Checkpoints werden automatisch auf die Straße gesnappt, ungenaue Koordinaten sind also unkritisch.

Voraussetzungen

Pflicht: keine. DriveOS läuft standalone. Alles Folgende wird automatisch erkannt, wenn es vorhanden ist.

Ressource Wofür
oxmysql Fortschritt in der Datenbank statt in einer JSON-Datei.
ox_target Interaktion am Ped per Target statt E-Taste.
ox_lib / okokNotify Benachrichtigungen. Reihenfolge: okokNotify → ox_lib → Chat.
es_extended / qb-core / qbx_core / ox_core Geldabbuchung und Framework-Lizenz.
esx_society / qb-management Prüfungsgebühren in eine Society-Kasse buchen.
licenseos Lizenzverwaltung über das LicenseOS-Panel.

Gebühren ohne Framework: Auf ESX, QBCore und Qbox werden Prüfungsgebühren abgebucht. Auf ox_core und Standalone gibt es keine einheitliche Geld-API — dort sind die Prüfungen kostenlos, bis eine eigene Geldanbindung eingehängt wird.

Installation

  1. Ordner driveos nach resources/ kopieren.
  2. In die server.cfg eintragen:
ensure driveos
  1. ACE-Recht für den Routen-Recorder setzen:
add_ace group.admin driveos.admin allow
  1. Fertig. Mit oxmysql wird die Tabelle beim Start selbst angelegt, ohne oxmysql speichert DriveOS in einer JSON-Datei — beides ohne Handarbeit.

Datenbank

DriveOS nutzt genau eine Tabelle: driveos_progress. Sie wird beim Start automatisch angelegt, fehlende Spalten werden per Migration ergänzt.

Ohne oxmysql läuft der komplette Fortschritt über eine JSON-Datei — voll standalone, ohne jede Einrichtung.

Die Framework-Lizenz wird zusätzlich dort gesetzt, wo dein Framework sie erwartet:

Framework Ablage
ESX user_licenses
QBCore / Qbox metadata.licences
ox_core Charakter-Metadaten

Konfiguration

Trotz Escrow-Schutz bleiben diese Dateien offen und frei editierbar:

Datei Inhalt
config.lua Klassen, Fahrzeuge, Gebühren, Punktabzüge, Sperrzeiten, Fristen, Framework, Webhook
server/questions.lua Theoriefragen — eine Zeile je Frage: Frage, drei Antworten, Nummer der richtigen Antwort
server/routes.lua Prüfungsstrecken mit Checkpoints, Tempolimits und Haltestellen
README.md Installation und Anpassung

Eigene Routen aufnehmen

Der eingebaute Recorder nimmt eine Strecke auf, während du sie abfährst. Abgesichert über die ACE-Berechtigung driveos.admin — kein Spieler kann das auslösen.

Befehl Wirkung
/dsroute start Aufnahme starten, Checkpoints werden automatisch gesetzt.
/dsroute point 30 Manueller Checkpoint mit Tempolimit 30.
/dsroute stophere Haltestelle setzen — hier muss vollständig angehalten werden.
/dsroute stop Fertiges Lua-Snippet in der Serverkonsole ausgeben.

Snippet kopieren, in server/routes.lua einfügen, fertig.

LicenseOS-Anbindung

DriveOS ist eine reine Ingame-Ressource ohne eigenes Panel. Läuft zusätzlich LicenseOS auf dem Server, übergibt DriveOS jede Prüfung dorthin — die Lizenz wird dann über LicenseOS inklusive Lizenznummer ausgestellt, und auf Wunsch werden die dort hinterlegten Pflicht-Fahrstunden auch für die Selbstbedienung durchgesetzt.

Die Anbindung ist ein Bonus, keine Voraussetzung — DriveOS funktioniert vollständig ohne LicenseOS.

Exports

Serverseitig:

exports['driveos']:HasPassedTheory(identifier, classId)
exports['driveos']:GetProgress(identifier, classId)

Events für eigene Scripts

AddEventHandler('driveos:examCompleted', function(identifier, classId, examType, passed, score)
    -- examType: 'theory' | 'practical'
end)

AddEventHandler('driveos:licenseGranted', function(identifier, classId, src)
    -- Lizenz wurde im Framework eingetragen
end)

Events & Commands

Befehl Wer Wirkung
/dsroute start Admin Routenaufnahme starten.
/dsroute point <limit> Admin Checkpoint mit Tempolimit setzen.
/dsroute stophere Admin Pflicht-Haltestelle setzen.
/dsroute stop Admin Aufnahme beenden und Snippet ausgeben.

Sicherheit

  • Der Client entscheidet nichts. Checkpoints gelten erst als erreicht, wenn der Server sie bestätigt — kein Vorspulen, kein Desync-Trick.
  • Der Server misst unabhängig mit. Position und Geschwindigkeit werden serverseitig gegengeprüft.
  • Plausibilitätsprüfung der Fahrzeit. Wer die Strecke schneller abschließt, als sie physikalisch fahrbar ist, bekommt die Prüfung als ungültig gewertet.
  • Reason-Whitelist — gefälschte Fail-Events vom Client werden abgefangen.
  • Doppelstart-Schutz verhindert eine doppelte Abbuchung.
  • Automatische Erstattung, wenn eine Prüfung technisch nicht starten konnte — eng abgesichert gegen Missbrauch.
  • Die Theoriebewertung passiert zu 100 % serverseitig.

Discord-Logs

Ein Webhook protokolliert jede bestandene und nicht bestandene Prüfung. Die URL wird in der config.lua eingetragen; ohne Eintrag wird nichts gesendet.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Framework wird nicht erkannt Config.Framework von Hand setzen. Auf Standalone funktioniert alles außer den Framework-Funktionen.
Gebühren werden nicht abgebucht Auf ox_core und Standalone gibt es keine einheitliche Geld-API — dort sind Prüfungen kostenlos, bis eine eigene Anbindung eingehängt ist. Siehe README.
Kein Target am Fahrschul-Ped Ohne ox_target greift automatisch die E-Taste mit Marker. Das ist kein Fehler.
Checkpoints liegen neben der Straße Unkritisch — Checkpoints werden automatisch auf die Straße gesnappt.
Prüfung wird als ungültig gewertet Die Plausibilitätsprüfung der Fahrzeit hat angeschlagen. Bei sehr kurzen eigenen Routen die Streckenlänge prüfen.
Theorie ist plötzlich weg Eine bestandene Theorie verfällt nach der eingestellten Frist (Standard 14 Tage) und wird nach bestandener Praxis verbraucht.
Einzelne Anzeigen bleiben leer Sehr alte FXServer-Artefakte kennen einzelne Natives nicht. Das Script fängt das ab, statt abzustürzen — Artefakt aktualisieren.

Leistungsaufnahme: Im Leerlauf läuft ein Thread mit 800 ms Intervall; das Marker-Rendering startet erst im Umkreis von 15 m um die Fahrschule. Während einer Prüfung läuft die Bewertungsschleife alle 150 ms auf dem Client des Prüflings und alle 2,5 Sekunden serverseitig — nur für diesen einen Spieler.

Ancomox Admin Suite — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Admin Suite
Zum Produkt

Ancomox Admin Suite — vollständiges Admin-Panel für FiveM, bedienbar im Spiel per Tastendruck und im Browser über das mitgelieferte Web-Panel. Framework und Inventar werden selbst erkannt. ESX · QBCore · Qbox · Standalone.

Überblick

ancomox_adminsuite bündelt Spielerverwaltung, Sanktionen, Wirtschaft, Live-Werkzeuge und Datenschutz in einem Panel. Jede Aktion wird serverseitig gegen das Rechtesystem geprüft und protokolliert — das Interface entscheidet nie über Berechtigungen.

Funktionsumfang

Spielerverwaltung

  • Live-Liste mit Ping, Leben, Weste, Dimension und Statusmarkern, Aktualisierung im Sekundentakt
  • Vollständige Spielerakte: Kennungen, Beruf, Gang, Bargeld, Bank, Inventar, Position
  • Interne Team-Notiz pro Spieler mit Auto-Speichern
  • Goto, Bring, Freeze, Handschellen, Mute, Revive, Heal, Skin-Editor
  • Massenaktionen: alle heilen, alle wiederbeleben, alle herholen, alle Fahrzeuge reparieren
  • Support-Modus: Admin und Spieler in eine isolierte Dimension und zurück

Sanktionen

  • Kick, Ban mit Stundenangabe oder permanent, Offline-Ban über jede beliebige Kennung
  • Ban-Prüfung beim Verbinden über license, license2, Discord, Steam, FiveM und Hardware-Token
  • Ban-Manager mit Suche, Ablaufdatum und Entsperren
  • Verwarnungen mit Auto-Ban nach frei wählbarer Anzahl
  • Gefängnis mit Zonenüberwachung und automatischer Entlassung — übersteht Serverneustarts
  • Vorgefertigte Ban-Gründe, Einspruchs-Link im Kick-Screen, Begründungspflicht

Wirtschaft, Fahrzeuge, Welt

  • Geld und Items geben oder entziehen, Waffen verwalten, Inventar leeren, Job und Gang setzen
  • Item-Bilder aus dem eigenen Inventar — ox_inventory, qs-inventory, codem-inventory, qb-inventory und Framework-HUDs werden automatisch erkannt
  • Fahrzeug-Verwaltung in der Spielerakte: alle Fahrzeuge des Charakters aus der Datenbank mit Kennzeichen, Modell und Garagen-Status — herbeirufen, ein- und ausparken, vergeben oder löschen
  • Wetter, Zeit, Blackout, Lockdown, Ankündigungen, NPCs und Objekte aufräumen

Live-Werkzeuge

  • Live-Karte mit allen Spielern, Zoom, Verschieben und Schnellzielen
  • Live-Stream des Spielerbildschirms mit Beweisbildern samt Zeitstempel und Wasserzeichen
  • Verdeckte Kamerabeobachtung über die Engine, ohne den eigenen Charakter zu bewegen
  • Werkzeugleiste: Noclip, ESP, Vanish, Godmode, Blips, Lösch-Laser

Voraussetzungen

Punkt Anforderung
Pflicht-Abhängigkeiten Keine. Das Panel läuft ohne weitere Ressourcen, auch auf einem Standalone-Server.
Server Aktuelles FXServer-Artefakt (empfohlen: die jeweils aktuelle Recommended-Version) und Zugriff auf die server.cfg für die ACE-Rechte.
Empfohlen oxmysql mit MySQL oder MariaDB. Muss vor dem Panel starten.
Optional screenshot-basic — nur für Live-Stream und Beweisbilder. Ohne die Ressource fehlen genau diese zwei Funktionen, sonst nichts.
Framework ESX, QBCore oder Qbox — oder gar keines. Ohne Framework fehlen nur die Framework-Funktionen (Geld, Job, Gang, Inventar).
Inventare ox_inventory · qs-inventory · codem-inventory · qb-inventory · Framework-nativ
Sprachen Deutsch · Englisch · Französisch · Spanisch (307 Schlüssel je Sprache)
Internetzugriff Nicht nötig — kein CDN, keine Webfonts, kein Phone-Home. Vue liegt lokal im Paket.

Ohne Datenbank läuft das Panel im KVP-Modus: Alles funktioniert, aber Protokoll und Historie überleben keinen Neustart. Für einen ernsthaft betriebenen Server ist der Datenbankmodus die richtige Wahl.

Installation

  1. Ordner nach resources/ entpacken.
  2. In die server.cfg eintragen:
ensure oxmysql
ensure ancomox_adminsuite
  1. ACE-Rechte setzen:
add_ace group.mod        ancomox.mod        allow
add_ace group.admin      ancomox.admin      allow
add_ace group.superadmin ancomox.superadmin allow
  1. Fertig. Die Tabellen legt das Skript beim ersten Start selbst an; die .sql-Datei liegt für die manuelle Einrichtung bei.

Rechteprobleme in einer Minute geklärt: /ancomoxperms zeigt für jeden Spieler, welcher ACE-Eintrag greift und welches Level dabei herauskommt.

Datenbank

Alle Tabellen werden beim ersten Start automatisch angelegt. Die passende .sql-Datei liegt bei, falls du sie lieber von Hand einspielst.

Ohne oxmysql arbeitet das Panel im KVP-Modus — vollständig funktionsfähig, aber Protokoll und Historie gehen beim Neustart verloren.

Rechte-System

Das ist der Kern des Panels. Die meisten Admin-Menüs lassen dich festlegen, dass ein Moderator nicht bannen darf. Hier bestimmst du zusätzlich, welches Feld der Spielerakte ein Rang überhaupt zu sehen bekommt.

Config.DataPermissions = {
    ['identifiers'] = 1,   -- License, Steam, Discord
    ['cash']        = 1,   -- Bargeld
    ['bank']        = 2,   -- Bankguthaben erst für Admins
    ['inventory']   = 2,
    ['note']        = 2,   -- interne Team-Notiz
    ['vehicles']    = 2,
}

Ein gesperrtes Feld wird nicht ausgeblendet — es verlässt den Server nicht. Ein manipulierter Client sieht es deshalb ebenfalls nicht. Dasselbe gilt für ganze Bereiche über Config.ViewPermissions; auch Befehlspalette und Tastenkürzel laufen durch dieselbe Sperre.

Abschalten und Beschränken funktioniert auf vier Ebenen:

Schalter Umfang
Config.Features 16 Module einzeln an- und abschaltbar
Config.ActionPermissions 67 Aktionen, je mit Mindest-Level
Config.ViewPermissions 12 Bereiche — wer welchen Tab öffnen darf
Config.DataPermissions 13 Datenfelder — wer welches Feld der Akte sieht

Konfiguration

config.lua, die vier Sprachdateien, die SQL-Datei und die gesamte Dokumentation sind über escrow_ignore von der Verschlüsselung ausgenommen und bleiben dauerhaft offen.

Bereich Inhalt
Config.Framework Automatische Erkennung; bei Bedarf fest vorgeben.
Config.Features 16 Module.
Config.ActionPermissions 67 Aktionen mit Mindest-Level.
Config.ViewPermissions / Config.DataPermissions 12 Bereiche, 13 Datenfelder.
Config.Theme Anzeigename, Untertitel, Icon, drei Presets, alle Akzentfarben, Eckenradius und Animationen.
Web-Panel Port, Passwort, IP-Allowlist. Ab Werk abgeschaltet.

Web-Panel

Dasselbe Interface im Browser — moderieren ohne laufendes Spiel, auch vom Handy. Absicherung über Session-Token, Login-Sperre nach Fehlversuchen, IP-Bindung der Sitzung und IP-Allowlist.

Ab Werk deaktiviert und ohne gesetztes Passwort gar nicht erreichbar. Für den Betrieb im offenen Internet brauchst du einen TLS-Reverse-Proxy — der FiveM-HTTP-Endpunkt selbst ist unverschlüsselt.

Datenschutz

Ein eigener Tab bündelt, was auf einem EU-Server früher oder später gebraucht wird:

  • IP-Anzeige abschaltbar — bei showIP = false verlässt die IP den Server gar nicht erst
  • Automatische Löschfristen für Protokoll, Verwarnungen, Notizen, Tickets, Gefängnisdaten und abgelaufene Sperren — jede Frist einzeln einstellbar
  • Kennungen im Protokoll pseudonymisiert (PBKDF2 mit serverindividuellem Salt)
  • Discord-Webhooks maskieren Kennungen und IP-Adressen vor dem Versand
  • Export- und Löschfunktion für Anfragen einzelner Spieler, mit Pflichtbegründung und Protokoll
  • Spielerbefehle /meinedaten und /datenloeschung, Namen frei wählbar

Datenbank-Scan

Ein Spieler ist gelöscht, aber sein Auto steht noch in der Garage. Oder jemand fragt, was über ihn gespeichert ist. Das Panel liest information_schema, findet jede Spalte, die nach einer Spielerkennung aussieht, und zählt die Treffer:

phone_messages    citizenid            187
owned_vehicles    owner                  6
player_vehicles   citizenid, license     6
ox_inventory      owner                  1
users             identifier             1

Erkannt werden die License mit und ohne license:-Präfix sowie die QBCore-citizenid. Gefunden werden auch Tabellen von Skripten, an die niemand mehr denkt — ohne Konfiguration.

Der Scan zählt nur. Keine Inhalte im Export, kein DELETE, kein UPDATE. Außerhalb der Panel-Tabellen wird nichts gelöscht: Wer die users-Zeile eines Spielers entfernt, zerstört seinen Charakter.

Kein Ersatz für Rechtsberatung. Die mitgelieferte PRIVACY.md ist eine technische Beschreibung und Orientierungshilfe. Das Script macht einen Server nicht automatisch DSGVO-konform.

Events & Commands

Befehl Wer Wirkung
/ancomoxperms Admin Zeigt für jeden Spieler, welcher ACE-Eintrag greift und welches Level dabei herauskommt.
/meinedaten Spieler Datenauskunft anfordern. Name frei wählbar.
/datenloeschung Spieler Löschung der eigenen Daten beantragen. Name frei wählbar.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Panel öffnet nicht / Rechte greifen nicht /ancomoxperms ausführen — der Befehl zeigt den greifenden ACE-Eintrag und das resultierende Level.
Framework wird nicht erkannt Config.Framework von Hand setzen. Auf Standalone funktioniert alles außer Geld, Job und Inventar.
Protokoll ist nach dem Neustart leer Das Panel läuft im KVP-Modus. oxmysql installieren und vor dem Panel starten lassen.
Live-Stream und Beweisbilder fehlen screenshot-basic nachinstallieren. Alle anderen Funktionen sind davon nicht betroffen.
Keine Item-Bilder Das Inventar wurde nicht erkannt. Unterstützt sind ox_inventory, qs-inventory, codem-inventory, qb-inventory und Framework-nativ.
Web-Panel nicht erreichbar Es ist ab Werk deaktiviert und ohne gesetztes Passwort gar nicht erreichbar. Port freigeben, Passwort setzen — im offenen Internet zwingend hinter einen TLS-Reverse-Proxy.
Einzelne Anzeigen bleiben leer Sehr alte FXServer-Artefakte kennen einzelne Natives nicht. Das Panel fängt das ab, statt abzustürzen — Artefakt aktualisieren.

Ancomox Activity Suite — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Activity Suite
Zum Produkt

Ancomox Activity Suite — Player Analytics und Moderations-Zentrale. Trackt Spielzeiten, Serverauslastung und Wirtschaft automatisch und legt Reports, Bans, Verwarnungen und Spielerakten daneben. ESX · QBCore · Qbox · Standalone.

Überblick

ancomox_activitysuite beantwortet die Fragen, die ein Serverbetrieb wirklich stellt: Wann ist der Server voll? Wer sind die Stammspieler? Kippt gerade die Wirtschaft? Welche Fraktion stirbt? Die passenden Moderationswerkzeuge liegen direkt daneben.

Abgrenzung zur Admin Suite: Die Ancomox Admin Suite ist das klassische Admin-Panel für den Eingriff im laufenden Betrieb. Die Activity Suite ist das Analytics-Gegenstück — Auswertung, Verläufe und Spielerakten. Beide laufen unabhängig voneinander.

Analytics

  • Server-Auslastung — automatisches Population-Tracking mit 24-Stunden- und 7-Tage-Verlauf
  • Aktivitäts-Heatmap — 7×24-Raster der letzten 30 Tage, für Event- und Teamplanung
  • Spielzeit-Analytics — pro Spieler und pro Fraktion, mit Datumsfilter, Leaderboard und CSV-Export
  • Fraktions-Radar — Spielzeit, Mitglieder und Live-Online-Zahlen aller Jobs im Vergleich

Economy Watch

  • Gesamt-Geldmenge, Bargeld gegen Bank, durchschnittliches Vermögen — live aus der Datenbank
  • Vermögensverteilung nach Klassen (unter 10.000 bis über 10 Mio.) — Money-Glitches werden sichtbar, bevor sie die Wirtschaft kippen
  • Top 25 der Reichsten mit Direktsprung in die Akte, dazu eine Konzentrationsanzeige („Top 10 halten X %")
  • Vermögensverlauf pro Spieler über 30 Tage oder den gesamten Zeitraum

Spielerakte

Ein Klick auf einen Spieler öffnet Finanzen, Inventar und Waffen, den kompletten Fuhrpark mit Bildern, Verwarnungen, Aktivitäts-Chart, Admin-Notizen und Strafregister. Dazu ein Trust-Score aus echten Verwarnungen, Dossier-Export und eine Funktion zum Kopieren aller Kennungen. Geld geben, nehmen und wipen funktioniert online wie offline.

Moderation

  • Report-/Ticket-System — Spieler tippen /report, Admins bekommen einen Live-Push: annehmen, antworten, hinteleportieren, abschließen
  • Internes Ban-System — Zeit-Bans von 1 Stunde bis 30 Tage, Offline-Bans und Unban direkt im Panel, Ban-Prüfung beim Verbinden
  • Vollbild-Verwarnungen — die Warnung erscheint unübersehbar auf dem Bildschirm des Spielers
  • Live-Screenshot direkt in den Discord (über screenshot-basic)
  • Lückenlose Admin-Logs und drei getrennte Discord-Webhooks
  • Kick, Heal, Freeze, Spectate, TP und Bring, Massenaktionen, Server-Ankündigungen, Nuke und Soft-Wipe

Voraussetzungen

Punkt Anforderung
Pflicht oxmysql
Framework ESX, QBCore oder Qbox (automatisch erkannt) — oder keines (Standalone über Ace-Permissions)
Optional ox_inventory für Item-Labels, screenshot-basic für Live-Screenshots
Ordnername Muss ancomox_activitysuite bleiben (Escrow-Auslieferung).
Server Aktuelles FXServer-Artefakt.

Installation

  1. Ordner nach resources/ kopieren — der Name muss ancomox_activitysuite bleiben.
  2. In die server.cfg eintragen:
ensure oxmysql
ensure ancomox_activitysuite
  1. Rechte vergeben. Bei ESX über users.group, bei QBCore/Qbox über das Permission-Level, im Standalone-Betrieb über ACE:
add_ace group.admin ancomox.admin allow
  1. Fertig — alle neun Datenbanktabellen legt das Script beim ersten Start selbst an.

Wichtig zum ACE-Betrieb: Der Gruppenname hinter ancomox. muss in Config.Permissions als Schlüssel existieren. ancomox.admin greift also nur, wenn es dort einen Eintrag ['admin'] gibt.

Datenbank

Das Script legt beim ersten Start neun Tabellen selbst an — SQL-Kenntnisse sind nicht nötig. Der Ban-Check beim Verbinden läuft über die Tabelle panel_bans.

Zwei Aufbewahrungsfristen sind einstellbar:

Option Standard Wirkung
Config.PopulationInterval 10 Alle X Minuten wird die aktuelle Spielerzahl gespeichert.
Config.PopulationRetentionDays 14 So viele Tage werden die Populationsdaten aufbewahrt.

Rechte-System

Zwölf granulare Rechte je Gruppe. Jede einzelne Aktion wird serverseitig geprüft — das Interface blendet Verbotenes komplett aus, statt es nur zu deaktivieren.

Recht Erlaubt
view_panel Das Panel überhaupt öffnen.
view_team Den Reiter „Team & Staff" sehen.
view_logs Die Admin-Logs lesen.
mass_actions Alle heilen, Chat leeren, Autos löschen.
announce Server-Ankündigungen senden.
nuke Spieler-Accounts komplett vernichten.
eco Geld geben, abziehen, wipen und den Wirtschafts-Tab öffnen.
inv Items spawnen und gezielt löschen.
set_job Fraktionen ändern.
kick_ban Kicken, Bannen, Heilen und Verwarnen.
spectate Live-Spectate und Teleports.
reports Spieler-Reports sehen und bearbeiten.

Fünf Gruppen sind vorkonfiguriert: superadmin und god (alles erlaubt), admin (alles außer nuke), mod (keine Wirtschafts- und Item-Rechte, kein Team-Reiter, keine Logs) sowie support (darf verwarnen, aber nicht kicken).

Konfiguration

Key Standard Beschreibung
Config.Framework 'auto' 'auto' · 'esx' · 'qb' · 'standalone'
Config.CommandName "activitysuite" Befehl zum Öffnen des Panels.
Config.Keybind "F10" Taste zum Öffnen. Leerstring = nur per Befehl.
Config.ServerName "Ancomox Roleplay" Anzeigename im Panel.
Config.ReportCommand "report" Befehl, mit dem Spieler einen Report erstellen.
Config.ReportCooldown 60 Sekunden zwischen zwei Reports pro Spieler (Spam-Schutz).
Config.NotifyAdminsOnReport true Admins mit reports-Recht im Chat benachrichtigen.
Config.UseInternalBans true Internes Ban-System: Offline-Bans, Zeit-Bans und Unban direkt im Panel.
Config.UseTxAdminBans false Permanente Online-Bans zusätzlich über txAdmin. Experimentell — der txAdmin-Export ist nicht offiziell; Fallback ist immer das interne System.
Config.WebhookURL "" Admin-Aktionen. Wird auch für den Live-Screenshot-Upload verwendet.
Config.EcoWebhookURL "" Economy-Logs. Leer lassen, wenn alles in denselben Channel soll.
Config.ReportWebhookURL "" Report-Logs. Leer lassen, wenn alles in denselben Channel soll.

Escrow — was offen bleibt

Ausgeliefert wird über das offizielle FiveM Asset Escrow. Diese Dateien bleiben unverschlüsselt und frei editierbar:

Datei Anpassbar
config.lua Alle Einstellungen — Framework, Rechtesystem, Webhooks, Commands, Keybind
bridge/server.lua Komplette Framework-Anbindung serverseitig — eigene Frameworks und Inventare andocken
bridge/client.lua Framework-Anbindung clientseitig — Notifications, Revive, Status
html/ (komplett) Das gesamte Interface — Farben, Texte, Logo, Branding

Verschlüsselt ist ausschließlich der Logik-Kern (server.lua, aggregator.lua, client.lua). Für ein Custom-Framework musst du also nie in eine verschlüsselte Datei — die offene Bridge ist die einzige Schnittstelle.

Vollständige Config-Datei

config.lua156 Zeilen · vollständige Datei
Config = {}

-- ------------------------------------------------------------------
-- FRAMEWORK
-- ------------------------------------------------------------------

-- 'auto'       = Framework wird automatisch erkannt (empfohlen)
-- 'esx'        = ESX Legacy (es_extended)
-- 'qb'         = QBCore / Qbox (qb-core / qbx_core)
-- 'standalone' = Kein Framework (Session-Tracking + Ace-Permissions)
Config.Framework = 'auto'

-- ------------------------------------------------------------------
-- ALLGEMEIN
-- ------------------------------------------------------------------

-- Mit welchem Befehl wird das Panel im Spiel geöffnet?
Config.CommandName = "activitysuite"

-- Mit welcher Taste soll die Activity Suite geöffnet werden? (z.B. F10, F9, K)
-- Lass es leer (""), falls du ausschließlich den Command verwenden möchtest.
Config.Keybind = "F10"

Config.ServerName = "Ancomox Roleplay"

-- ------------------------------------------------------------------
-- REPORT / TICKET SYSTEM
-- ------------------------------------------------------------------

-- Command mit dem Spieler einen Report erstellen (z.B. /report Spieler XY rammt mich)
Config.ReportCommand = "report"

-- Cooldown in Sekunden zwischen zwei Reports pro Spieler (Spam-Schutz)
Config.ReportCooldown = 60

-- Sollen Admins (mit reports-Recht) im Chat benachrichtigt werden, wenn ein neuer Report eingeht?
Config.NotifyAdminsOnReport = true

-- ------------------------------------------------------------------
-- POPULATION TRACKING (Spieler-Verlauf Chart)
-- ------------------------------------------------------------------

-- Alle wieviel Minuten soll die aktuelle Spielerzahl gespeichert werden?
Config.PopulationInterval = 10

-- Wieviele Tage sollen die Populations-Daten aufbewahrt werden?
Config.PopulationRetentionDays = 14

-- ------------------------------------------------------------------
-- BAN SYSTEM
-- ------------------------------------------------------------------

-- Soll das Skript für PERMANENTE Online-Banns zusätzlich txAdmin verwenden?
-- (Experimentell - der txAdmin-Export ist nicht offiziell. Fallback ist immer das interne System.)
Config.UseTxAdminBans = false

-- Internes Ban-System aktivieren? (Ermöglicht Offline-Bans, Zeit-Bans & Unban direkt im Panel)
-- Der Ban-Check läuft beim Verbinden über die panel_bans Tabelle.
Config.UseInternalBans = true

-- ------------------------------------------------------------------
-- WEBHOOKS FÜR DISCORD LOGGING
-- ------------------------------------------------------------------

-- Standard Admin Aktionen (Bans, Kicks, Teleports, Nuke, Warns)
-- Wird auch für den Live-Screenshot-Upload verwendet!
Config.WebhookURL = ""

-- Economy Logs (Geld geben/nehmen, Items spawnen/löschen, Account Wipes)
-- Leer lassen, wenn alles in den gleichen Channel soll.
Config.EcoWebhookURL = ""

-- Report Logs (Neue Reports, Claims, Abschlüsse)
-- Leer lassen, wenn alles in den gleichen Channel soll.
Config.ReportWebhookURL = ""

-- ------------------------------------------------------------------
-- GRANULARES RECHTE-SYSTEM (RBAC)
-- Definiere hier exakt, welche Gruppe was im Panel darf.
--
-- ESX:        Gruppen aus users.`group` (superadmin, admin, mod, ...)
-- QBCore/Qbox: Permission-Level (god, admin, mod) - unten bereits enthalten
-- Standalone: Ace-Permissions - vergib z.B.:  add_ace group.admin ancomox.admin allow
--             Der Gruppenname hinter "ancomox." muss hier als Key existieren.
-- ------------------------------------------------------------------
Config.Permissions = {
    -- HÖCHSTE STUFE: Darf absolut alles
    ['superadmin'] = {
        view_panel   = true,    -- Darf das Panel überhaupt öffnen?
        view_team    = true,    -- Darf den "Team & Staff" Tab sehen?
        view_logs    = true,    -- Darf die Admin-Logs lesen?
        mass_actions = true,    -- Darf Alle heilen, Chat leeren, Autos löschen?
        announce     = true,    -- Darf rote Server-Ankündigungen senden?
        nuke         = true,    -- Darf Spieler-Accounts komplett vernichten?
        eco          = true,    -- Darf Geld geben / abziehen / wipen + Wirtschafts-Tab?
        inv          = true,    -- Darf Items spawnen & gezielt löschen?
        set_job      = true,    -- Darf Fraktionen ändern?
        kick_ban     = true,    -- Darf Spieler Kicken, Bannen, Heilen & Verwarnen?
        spectate     = true,    -- Darf das Live-Spectate Auge nutzen & TPs?
        reports      = true     -- Darf Spieler-Reports sehen & bearbeiten?
    },

    -- QBCore/Qbox Äquivalent zu superadmin
    ['god'] = {
        view_panel = true, view_team = true, view_logs = true, mass_actions = true,
        announce = true, nuke = true, eco = true, inv = true, set_job = true,
        kick_ban = true, spectate = true, reports = true
    },

    -- MITTLERE STUFE: Admin
    ['admin'] = {
        view_panel   = true,
        view_team    = true,
        view_logs    = true,
        mass_actions = true,
        announce     = true,
        nuke         = false,       -- Admin darf keinen Nuke nutzen
        eco          = true,
        inv          = true,
        set_job      = true,
        kick_ban     = true,
        spectate     = true,
        reports      = true
    },

    -- UNTERSTE STUFE: Moderator / Supporter
    ['mod'] = {
        view_panel   = true,
        view_team    = false,   -- Sieht den Team-Reiter nicht
        view_logs    = false,   -- Darf andere Admins nicht überwachen
        mass_actions = false,
        announce     = false,
        nuke         = false,
        eco          = false,        -- Mod darf kein Geld manipulieren
        inv          = false,        -- Mod darf keine Items manipulieren
        set_job      = false,
        kick_ban     = true,
        spectate     = true,
        reports      = true
    },
    ['support'] = {
        view_panel   = true,
        view_team    = false,
        view_logs    = false,
        mass_actions = false,
        announce     = false,
        nuke         = false,
        eco          = false,
        inv          = false,
        set_job      = false,
        kick_ban     = false,   -- Darf nur verwarnen, nicht kicken
        spectate     = true,
        reports      = true
    }
}

Events & Commands

Befehl Wer Wirkung
/activitysuite Team Panel öffnen. Name über Config.CommandName frei wählbar.
F10 Team Panel per Tastendruck öffnen. Über Config.Keybind änderbar, Leerstring deaktiviert die Taste.
/report <Text> Spieler Report erstellen. Name über Config.ReportCommand frei wählbar.
STRG + K Team Command-Palette im Panel.

Discord-Logs

Drei getrennte Webhooks. Bleibt einer leer, landet nichts in diesem Kanal — sollen alle Meldungen in denselben Channel, trägst du dieselbe URL mehrfach ein.

Webhook Inhalt
Config.WebhookURL Bans, Kicks, Teleports, Nuke, Warns — und der Upload der Live-Screenshots.
Config.EcoWebhookURL Geld geben und nehmen, Items spawnen und löschen, Account-Wipes.
Config.ReportWebhookURL Neue Reports, Claims, Abschlüsse.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Panel öffnet nicht Die Gruppe fehlt in Config.Permissions oder view_panel steht auf false. Im ACE-Betrieb muss der Name hinter ancomox. exakt einem Schlüssel in Config.Permissions entsprechen.
Taste F10 reagiert nicht Config.Keybind prüfen. Belegt eine andere Ressource dieselbe Taste, eine freie wählen oder den Leerstring setzen und nur den Befehl verwenden.
Reiter fehlen im Panel Kein Fehler — das Interface blendet Bereiche ohne Recht vollständig aus, statt sie ausgegraut anzuzeigen.
Live-Screenshot funktioniert nicht screenshot-basic muss installiert sein, und Config.WebhookURL darf nicht leer sein — der Upload läuft über diesen Webhook.
Keine Item-Labels ox_inventory nicht erkannt. Ohne diese Ressource wird das Framework-Inventar genutzt.
Bans greifen nicht Config.UseInternalBans prüfen. Der Ban-Check läuft beim Verbinden über panel_bans.
Kollision mit txAdmin Keine. Das interne Ban-System arbeitet unabhängig; die txAdmin-Anbindung ist optional und ausdrücklich experimentell.
Populations-Chart bleibt leer Die erste Messung erfolgt erst nach Config.PopulationInterval Minuten. Aussagekräftig wird der Verlauf nach einigen Stunden.

Ancomox Börse — Entwickler-Dokumentation

Im Ancomox-StoreDynamisches Börsensystem
Zum Produkt

Ancomox Börse — dynamisches Börsen-, Kauf- und Verkaufssystem. Jedes Item hat einen eigenen Kurs, der sich alle fünf Minuten bewegt. Kein SQL, keine Pflicht-Abhängigkeiten. ESX · QBCore · Qbox · Standalone.

Überblick

ancomox_boerse ersetzt den üblichen Verkaufsautomaten durch einen echten Markt. Statt eines festen Betrags pro Item bewegt sich jeder Kurs aus vier Kräften, die zusammenspielen:

  • ein Gummiband zum Basispreis, damit nichts entgleist
  • ein eigener Zyklus je Item, mit eigener Phase und Geschwindigkeit
  • Rauschen, damit kein Verlauf vorhersehbar wird
  • der Verkaufsdruck der Spieler — wer 500 Stück auf einmal abstößt, drückt den Preis für die nächsten Einheiten

Das Ergebnis sind Kurse mit Trends, Einbrüchen und Erholungen. Wer den Markt liest, verdient mehr als wer blind ablädt.

Marktereignisse

Ereignis Wirkung
Markt-Event Zufällig wird ein Rohstoff um ±30 % nach oben oder unten geschleudert. Alle Spieler bekommen die Meldung mit Item-Icon.
Heißer Markt Stündlich fragt ein Abnehmer einen zufälligen Rohstoff mit bis zu +50 % nach. Über dem Börsen-NPC steht, welcher gerade gefragt ist.
Live-Charts Jede Zeile der Kursübersicht hat eine Sparkline; ein Klick öffnet den großen Chart mit vollem Verlauf, Hoch/Tief, Trendanzeige und Fortschreibung.

Handelspunkte

Jeder Handelspunkt ist ein Config-Block. Festgelegt wird dort, ob dort gekauft, verkauft oder beides wird, welche Items gehandelt werden, welche Jobs Zugriff haben und welcher Anteil jedes Trades in eine Fraktionskasse fließt.

Ein Schwarzmarkt nur für Mafia und Kartell ist damit eine Zeile. Fraktionskassen funktionieren mit esx_addonaccount, qb-management, qbx_management und Renewed-Banking.

Voraussetzungen

Keine Pflicht-Abhängigkeiten. Alles wird automatisch erkannt und ist in der Config überschreibbar.

Bereich Unterstützt
Framework ESX Legacy · ESX 1.2+ · QBCore · Qbox · Standalone
Inventar ox_inventory · qs-inventory · qb-inventory · lj-inventory · ps-inventory · ESX Standard
Interaktion ox_target · qb-target · klassische E-Taste
Notify ox_lib · okokNotify · ESX · QBCore · g5mp · GTA-nativ
Banking esx_addonaccount · qb-management · qbx_management · Renewed-Banking

Installation

  1. Ordner nach resources/ legen.
  2. In die server.cfg eintragen:
ensure ancomox_boerse
  1. Server starten. Die Konsole zeigt, was erkannt wurde:
[ancomox_boerse] Framework: esx | Inventar: ox | Target: ox | Notify: ox
[ancomox_boerse] Markt initialisiert: 0 wiederhergestellt, 45 neu erzeugt.
[ancomox_boerse] Bereit. 45 Items · Tick alle 5 Min · Bonus alle 60 Min
  1. In der config.lua die eigenen Item-Namen und NPC-Positionen eintragen. Rechne mit etwa 10 Minuten.

Die Config wird beim Start geprüft. Fehlende Items, leere Shops und riskante Preis-Einstellungen werden namentlich in der Konsole gemeldet, bevor sie auf dem Live-Server auffallen.

Datenbank

Keine Datenbank, kein SQL-Import. Die Kurse überleben Neustarts über den KVP-Store von FiveM.

Änderst du später einen Basispreis, rechnet das Script den gespeicherten Kurs automatisch proportional um — der Markt springt also nicht zurück auf null.

Konfiguration

Eine einzige durchkommentierte config.lua — keine andere Datei muss angefasst werden. Mitgeliefert sind 45+ vorkonfigurierte Items und 4 fertige Handelspunkte als Startpunkt.

Bereich Inhalt
Erkennung Framework, Inventar, Interaktion, Notify, Banking — jeweils auto oder fest.
Marktmechanik Tick-Intervall (Standard 5 Minuten), Kurshistorie, Ober- und Untergrenzen, Speicherintervall.
Markt-Events An/aus, Stärke (Standard ±30 %), Häufigkeit.
Heißer Markt Intervall (Standard 60 Minuten), Bonushöhe (bis +50 %), Dauer.
Handelspunkte Position, NPC, Blip, Modus (kaufen/verkaufen/beides), Item-Liste, Job-Beschränkung, Fraktionsanteil.
Items Basispreis, Kategorie, Volatilität, Handelbarkeit.
VIP Ränge und Prozentsätze, Erkennung über ACE-Permission oder Framework-Gruppe.
Steuern & Spanne Abgaben je Handelspunkt und die garantierte Mindest-Spanne.
Webhooks Discord-Logging.

VIP-Ränge

Donatoren kaufen günstiger und verkaufen teurer. Ränge und Prozentsätze legst du selbst fest, die Erkennung läuft wahlweise über ACE-Permissions oder die Framework-Gruppe — ein Mehrwert für den eigenen Store, ohne Pay-to-Win.

Eigenes Inventar anbinden

Steht dein Inventar nicht auf der Liste, reichen fünf Funktionen in bridge/custom_server.lua. Fertige, kommentierte Beispiele liegen bei:

function ANC.Bridge.GetItemCount(src, item)
    return exports['mein_inventar']:GetItemCount(src, item) or 0
end

Dasselbe gilt für Geld-Systeme, Fraktionskassen, VIP-Erkennung, Notifications und die Interaktion.

Escrow — was offen bleibt

Frei bearbeitbar Inhalt
config.lua Sämtliche Einstellungen, Shops, Items, Preise, VIP, Steuern
locales/ Alle Texte, inklusive einer leeren Datei für eigene Formulierungen
bridge/custom_server.lua Eigenes Inventar, Geld-System, Banking, VIP-Erkennung
bridge/custom_client.lua Eigene NPCs, Interaktion, Notifications
html/ Die komplette Oberfläche: HTML, CSS und JavaScript

Geschützt sind die Bridge, die Marktlogik, die Handelsabwicklung und die Admin-Commands.

Wirtschaftssicherheit

Ein Script mit Kauf und Verkauf ist genau dann gefährlich, wenn sich die beiden Preise überkreuzen — dann kaufen Spieler und verkaufen sofort zurück und drucken Geld. Das ist hier ausgeschlossen:

  • Der Kaufpreis liegt immer über dem Verkaufspreis desselben Spielers. Auch bei aktivem Bonus, auch mit VIP-Rang, auch wenn Kauf und Verkauf auf zwei verschiedene Shops verteilt werden.
  • Eine garantierte Mindest-Spanne verhindert, dass sich VIP-Boni zu reibungslosem Hin- und Herhandeln addieren.
  • Rollback bei jedem Fehlschlag. Scheitert die Auszahlung, bekommt der Spieler seine Items zurück; scheitert die Item-Übergabe, sein Geld.
  • Alles wird am Server entschieden — Distanz, Job, Shop, Item, Menge und Preis. Der Client schickt nur, was er möchte.

Exports

Sieben Exports, um den Markt aus eigenen Scripts anzusprechen:

exports.ancomox_boerse:GetPrice('iron')                    -- aktueller Kurs
exports.ancomox_boerse:GetMarket()                         -- komplette Tabelle
exports.ancomox_boerse:GetBonus()                          -- aktueller Bonus
exports.ancomox_boerse:SetPrice('gold_ingot', 9500)        -- Kurs setzen
exports.ancomox_boerse:FireMarketEvent('diamond', 'crash') -- Event auslösen
exports.ancomox_boerse:ApplyPressure('copper', 200, false) -- Marktdruck
exports.ancomox_boerse:GetPlayerVIP(source)                -- VIP-Werte

Damit kann ein Mining-Script den Kupferpreis fallen lassen, wenn eine ganze Fraktion gleichzeitig abbaut — oder ein Heist-Script den Diamantmarkt crashen lassen, sobald die Beute auf den Markt kommt.

Auf jeden abgeschlossenen Handel gibt es zusätzlich ein Event, an das sich eigene Statistiken oder Achievements hängen lassen.

Events & Commands

Berechtigung über ACE-Permission oder Framework-Gruppe.

Befehl Wirkung
/boerse price <item> <preis> Kurs setzen.
/boerse reset Alle Kurse auf den Basiswert zurücksetzen.
/boerse event <item> <boom|crash> Kurssprung auslösen.
/boerse bonus <item> [prozent] Heißen Markt setzen.
/boerse list [suche] Alle Kurse in der Konsole ausgeben.
/boerse tick Einen Markt-Tick sofort auslösen.
/boerse save Kurse sofort speichern.
/boerse debug Debug-Modus umschalten.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Konsole meldet fehlende Items beim Start Genau dafür ist die Prüfung da: Die Item-Namen in config.lua existieren nicht in deinem Inventar. Namen angleichen.
Kurse stehen nach dem Neustart auf Basiswert Der KVP-Store wurde geleert, oder es ist der erste Start. Danach bleiben die Kurse erhalten.
Kurse springen nach einer Preisänderung Kein Fehler — bei geändertem Basispreis wird der gespeicherte Kurs proportional umgerechnet.
Falsches Inventar erkannt Erkennung in der config.lua fest überschreiben. Für nicht gelistete Inventare fünf Funktionen in bridge/custom_server.lua ausfüllen.
Fraktionskasse bekommt nichts Die Banking-Ressource wurde nicht erkannt. Unterstützt sind esx_addonaccount, qb-management, qbx_management und Renewed-Banking.
Spieler handeln Geld hin und her Kann durch die garantierte Mindest-Spanne nicht auftreten. Tritt es doch auf, ist die Spanne in der Config zu niedrig gesetzt.
NPC oder Blip bleibt nach Restart stehen Sollte nicht auftreten — NPCs, Blips und Target-Einträge werden beim Stoppen sauber entfernt.

Leistungsaufnahme: 0.00 ms im Leerlauf, keine Dauerschleifen mit Wait(0). Der Interaktions-Loop schläft, sobald kein NPC in Reichweite ist. Markt-Updates werden gebündelt verschickt, gespeichert wird nur bei echten Änderungen.

Ancomox Architect — Entwickler-Dokumentation

Im Ancomox-StoreArchitect
Zum Produkt

Ancomox Architect — Bauwerkzeug mit Live-3D-Vorschau, Blueprints, Undo/Redo, Job-Rechten und MySQL-Persistenz. Objekte werden lokal gestreamt statt genetzwerkt. Standalone · ESX · QBCore · Qbox.

Überblick

ancomox_architect ist ein Platzierungswerkzeug, das drei typische Probleme dieser Script-Gattung vermeidet: Objekte liegen nicht in einer JSON-Datei, die beim FTP-Upload verschwindet, sie werden nicht genetzwerkt (und stehen deshalb bei jedem Spieler in derselben Rotation), und es gibt keine Grenze bei 200 Objekten.

Das Event-Team stellt die Bühne selbst, die Polizei sperrt eine Straße in dreißig Sekunden ab, der Mechaniker richtet seine Werkstatt ein — jeder mit genau den Rechten, die er haben soll.

Live-3D-Vorschau

Neben dem Katalog steht ein Fenster, dessen Fläche vollständig durchsichtig ist — dahinter rendert das Spiel das tatsächliche Prop.

  • Kein hinterlegtes Bild nötig. Neue Props tauchen sofort mit korrekter Ansicht auf, ohne dass irgendwo eine Grafik nachgepflegt werden muss.
  • Drehen und zoomen wie in einem 3D-Programm. Wer nichts anfasst, sieht das Objekt langsam rotieren.
  • Stabil in jeder Drehung. Die Ausrichtung läuft über Quaternionen um die Kameraachse statt über Euler-Winkel um die Objektachse — das Prop taumelt nicht und liegt nie plötzlich auf dem Rücken.
  • Die Ausrichtung wird übernommen: Wie das Objekt in der Vorschau gedreht wurde, so beginnt die Platzierung.

Vorschaubilder auf Knopfdruck

/builder thumbs fährt den Katalog selbst ab: Jedes Prop wird vor einer Studio-Kamera fotografiert, zugeschnitten und gespeichert. Von jedem Prop entstehen zwei Aufnahmen aus derselben Position — eine mit und eine ohne das Objekt. Was auf beiden gleich ist, ist Hintergrund und wird transparent. Das funktioniert unabhängig von Farbe, Wolken oder Horizont, auch ein pechschwarzes Prop wird sauber freigestellt.

Die Bilder liegen als PNG mit Alphakanal in einer JSON-Datei im Resource-Ordner — kein externer Dienst, kein CDN. Clients halten sie lokal vor und laden erst nach, wenn sie neu erzeugt wurden.

Erkennt der Freisteller kein sinnvolles Ergebnis, behält er das Originalbild, statt eine leere Fläche abzulegen.

Bauen und Verwalten

  • Zwei Modi: Frei am Fadenkreuz oder ped-relativ, und Präzision mit Feinbewegung über die Pfeiltasten samt Achsenkreuz.
  • Volle 3-Achsen-Rotation: Mausrad dreht, STRG+Rad neigt, ALT+Rad rollt, SHIFT+Rad rastet auf 45°.
  • Drei Snap-Hilfen: Raster (frei von 0,05 bis 2 m), Wand-Snap richtet an der Hausfassade aus, Boden-Snap setzt das Objekt physikalisch korrekt auf den Untergrund.
  • Undo und Redo mit 30 Schritten pro Spieler — auch das Einfügen eines kompletten Blueprints ist ein einziger Schritt.
  • Klonen mit übernommener Rotation. Zwölf gleiche Absperrungen in einer Reihe sind zwölf Klicks, nicht zwölf Ausrichtungen.
  • X-Ray-Markierung: Beim Anfahren eines Listeneintrags zieht eine Linie durch die Welt zum Objekt — auch durch Wände.
  • Edit-in-Place: Beim Bearbeiten bleibt das Original serverseitig bestehen, bis du bestätigst. Crash oder Disconnect mittendrin gehen nicht verloren, die ID bleibt dieselbe.
  • Koordinaten kopieren: Ein Klick legt vec3(...) in die Zwischenablage, mit SHIFT als vec4(...) inklusive Drehwinkel.
  • Dauerhaft oder nur fürs Event: Ein Schalter entscheidet, ob ein Objekt in die Datenbank wandert oder beim nächsten Wipe verschwindet.

Blueprints

Ganze Bauwerke im Radius speichern — Marktplatz, Unfallstelle, Straßensperre, Bühne. Bis zu 300 Objekte pro Blueprint, Radius bis 150 Meter. Beim Einfügen dreht sich das Bauwerk mit der Blickrichtung mit, kein Objekt muss nachjustiert werden. Ein Blueprint mit 93 Objekten falsch platziert? Ein Tastendruck, alles weg.

Voraussetzungen

Punkt Anforderung
Zwingend FiveM-Server ab Build 5848 (fx_version 'cerulean', lua54) mit aktivem OneSync. Sonst nichts — kein ox_lib, kein Framework, keine Datenbank.
oxmysql Empfohlen. Ohne läuft der JSON-Modus, dessen Daten ein FTP-Update überschreiben kann.
screenshot-basic Nur für /builder thumbs. Liegt jedem FiveM-Server bereits bei.
ox_lib Optional, nur für Benachrichtigungen. Sonst läuft das über das Framework oder GTA-Natives.
Framework ESX (legacy und modern), QBCore und Qbox werden automatisch erkannt, per Config überschreibbar. Oder Standalone mit reinen ACE-Rechten.

Installation

  1. Ordner nach resources/[local]/ancomox_architect legen.
  2. In die server.cfg eintragen:
ensure ancomox_architect
  1. Rechte vergeben:
add_ace group.admin ancomox_architect.admin allow
  1. Ingame /builder eingeben — das war's.
  2. Optional einmalig /builder thumbs laufen lassen, damit der Katalog echte Objektbilder statt Platzhalter zeigt.

Umstieg von einem anderen Placement-Script: Eine vorhandene objects.json wird beim ersten Start eingelesen, ins neue Format übersetzt und in die Datenbank übernommen — mit Backup der alten Datei. Ein Migrations-Handbuch liegt bei.

Datenbank

Objekte landen in MySQL und überleben jedes Resource-Update, jeden Re-Deploy und jeden FTP-Upload. Die Tabellen legt Architect beim ersten Start selbst an; sql/ancomox_architect.sql liegt für Datenbanken ohne CREATE-Recht bei.

Fehlt oxmysql, arbeitet Architect im JSON-Modus weiter und schreibt in die Konsole, was das bedeutet.

Streaming und Performance

  • Lokales Streaming: Der Server ist reine Datenquelle und erzeugt keine Entities. Jeder Client rendert selbst — mit exakt derselben Rotation für alle, ohne Ownership-Probleme und ohne den Netzwerk-Entity-Pool zu verbrauchen.
  • Raster-Streaming mit Zellen: Geladen wird nur, was in der Nähe ist, mit Hysterese gegen Flackern und einer Obergrenze neuer Objekte pro Zyklus gegen Ruckler.
  • Standardlimit 5.000 Objekte — eine Config-Zeile. Die Last hängt an der Sichtweite, nicht an der Gesamtzahl.
  • Routing-Bucket-Support: Objekte erscheinen nur in der Dimension, in der sie gebaut wurden — passend für Housing und Instanzen.

Rechte & Sicherheit

Kein Modell, keine Koordinate und keine Berechtigung wird dem Client geglaubt.

  • Rechte nach deinem System: ACE-Rechte, klassische ACE-Gruppen, ESX- und QBCore-Gruppen oder Jobs.
  • Job-Rechte bis ins Detail: pro Job erlaubte Kategorien, Mindestrang, eigenes Objektlimit und die Frage, ob dauerhaft gebaut werden darf. Beispiel: Polizei ab Rang 2 darf nur Absperrungen setzen, maximal 60 Stück, nie dauerhaft.
  • Der Server validiert alles: Modellname, Koordinaten, Rotation, Platzierungsdistanz, Kategorie-Rechte, globales und persönliches Objektlimit.
  • Rate-Limits gegen Event-Spam: Mindestabstand zwischen Aktionen und eine harte Obergrenze pro Minute und Spieler.
  • Gesperrte und Admin-Modelle: Modelle, die auf dem Server nichts zu suchen haben, sind gesperrt. Freie Modellnamen sind für Nicht-Admins abschaltbar.
  • Kein Datenleck über das Menü: Die Objektliste bekommt nur, wer sie öffnen darf — wer keine Rechte hat, bekommt keine Daten, nicht nur keinen Knopf.

Konfiguration

Die Kernlogik ist verschlüsselt, alles zum Anpassen bleibt offen:

Datei Inhalt
config/config.lua Sämtliche Einstellungen: Limits, Streaming, Steuerung, Rechte, Jobs, Logging, Vorschau und Bild-Generator.
config/categories.lua Der komplette Katalog: 9 Kategorien, 122 Props, beliebig erweiterbar.
locales/ Alle Sprachdateien. Deutsch und Englisch vollständig, je 130 Texte; Französisch und Spanisch liegen als Bonus bei. Fehlt ein Schlüssel, greift automatisch Englisch statt eines leeren Feldes.
html/ Die gesamte Oberfläche inklusive Styles.
sql/, docs/, README SQL-Datei, API-Dokumentation und Migrationshandbuch.

Eigene Props ergänzt du in config/categories.lua — neue Kategorie oder neuer Eintrag, eine Zeile pro Prop. Der Blickwinkel für die Vorschaubilder ist global einstellbar, mit Ausnahmen für einzelne Modelle, falls ein Prop anders herum modelliert ist.

Exports

Zwölf serverseitige und acht clientseitige Exports — Objekte anlegen, ändern, löschen, im Radius suchen, Blueprints einfügen, Rechte abfragen sowie Entity zu einer ID holen und umgekehrt.

Events für eigene Scripts

AddEventHandler('objectPlaced',  function(...) end)
AddEventHandler('objectUpdated', function(...) end)
AddEventHandler('objectRemoved', function(...) end)

Target-Anbindung: Fertige Beispiele für ox_target und qb-target liegen der API-Dokumentation bei. Gestreamte Objekte lassen sich sauber ansprechen, weil du zu jeder Entity die Objekt-ID bekommst — ein Target-Script ist aber keine Voraussetzung.

Events & Commands

Befehl Wirkung
/builder Bau-Menü öffnen.
/builder thumbs Vorschaubilder für den kompletten Katalog erzeugen (einmalig).

Dazu Konsolenbefehle für Statistik, sofortiges Speichern und Bild-Verwaltung. Beim Start meldet Architect, welches Framework erkannt wurde, wo gespeichert wird und wie viele Objekte geladen sind.

Discord-Logs

Platzieren, Löschen, Bearbeiten, Wipe, Blueprints und abgelehnte Zugriffsversuche — jede Kategorie einzeln schaltbar.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Objekte sind nach einem Update weg Der JSON-Modus lief ohne Datenbank, und ein FTP-Upload hat die Datei überschrieben. oxmysql installieren — nur dort überleben Objekte ein Resource-Update.
Katalog zeigt nur Platzhalter /builder thumbs einmalig laufen lassen. Dafür wird screenshot-basic benötigt.
Freisteller liefert unsaubere Bilder Blickwinkel in der Config anpassen oder eine Ausnahme für das betroffene Modell setzen. Erkennt der Freisteller kein sinnvolles Ergebnis, behält er automatisch das Original.
Spieler sehen Objekte unterschiedlich Kann bauartbedingt nicht auftreten: Jeder Client rendert dieselben Daten lokal, die Rotation ist deshalb überall identisch. Genau das geht bei genetzwerkten Objekten regelmäßig schief.
Ruckler beim Betreten eines Bereichs Die Obergrenze neuer Objekte pro Streaming-Zyklus in der Config senken.
Objekte fehlen in einer Instanz Objekte erscheinen nur in der Dimension, in der sie gebaut wurden (Routing-Bucket-Support).
Ein Job darf zu viel oder zu wenig Job-Rechte prüfen: erlaubte Kategorien, Mindestrang, Objektlimit und die Erlaubnis für dauerhafte Objekte werden je Job einzeln gesetzt.
Alte objects.json wurde nicht übernommen Die Migration läuft nur beim ersten Start mit erkannter Datei. Die Konsolenausgabe beim Start zeigt, was passiert ist; das Backup der Originaldatei bleibt erhalten.

Testabdeckung: Server-Logik, Datenbankpfad, Vorschau-Mathematik und Freisteller laufen gegen eine Suite aus 310 automatischen Prüfungen, die dem Paket beiliegt und selbst gestartet werden kann.

Ancomox HUD — Entwickler-Dokumentation

Im Ancomox-StoreAncomox HUD
Zum Produkt

Ancomox HUD — ein HUD für alle Frameworks, bei dem jeder Spieler sein eigenes Layout bekommt: drei Themes, freies Drag & Drop, eigene Farben, Skalierung und eigene Tick-Raten. Ohne Abhängigkeiten. ESX · QBCore · QBox · Standalone.

Überblick

Der Kerngedanke: 200 Spieler müssen nicht mit demselben Layout leben. Jeder stellt sein HUD über /hud live im Spiel ein, alles wird pro Spieler gespeichert. Für den Serverbetreiber bleibt es bei Ordner rein, ensure, fertig — Framework, Tanksystem und Voice werden automatisch erkannt.

Drei Themes

Theme Charakter
Apex Circles Animierte Glass-Ringe.
Modern Bars Balken mit Live-Werten.
Minimal Ultraschlank, mit kompaktem Digital-Tacho.

Der Spieler wechselt das Theme selbst. Die Auswahl gilt automatisch auch für alle eigenen Status-Elemente, die du über die API registrierst — du musst also nichts pro Theme nachbauen.

Funktionsumfang

  • Vitals mit Smart-Fade und konfigurierbaren Warnschwellen
  • Geld-Animationen und Schwarzgeld, abschaltbar
  • Animierter RPM-Tacho in km/h oder mph, mit EV-Erkennung
  • Gurt mit realistischem Windschutzscheiben-Rauswurf, dazu eine Harness-API
  • Tempomat, Blinker, Warnblinker
  • Tank- und Motorwarnung, Flughöhe
  • Dynamische Minimap
  • Waffen-Widget mit Low-Ammo-Warnung
  • Linear-Kompass, Standort und Uhrzeit
  • Komplettes Notification-System mit ESX-/QB-Intercept und Redirect-Snippet
  • Progressbar
  • Voice: pma-voice und SaltyChat werden automatisch erkannt — mit Mute-Taste, Funk-Badge und Custom-Voice-API
  • Streamer-Modus über /streamermode und ein Kino-Modus
  • Deutsch und Englisch inklusive, auch im Einstellungsmenü

Voraussetzungen

Keine Abhängigkeiten. Das HUD läuft auf ESX, QBCore, QBox, einem eigenen Framework oder ganz ohne Framework. Die Erkennung passiert automatisch.

Bereich Unterstützt
Framework ESX Legacy (vollständig event-basiert) · QBCore · QBox (nativ, kein Workaround) · Standalone bzw. Custom über Events, Exports oder zwei Config-Getter
Tanksysteme ox_fuel · LegacyFuel · ps-fuel · cdn-fuel · lj-fuel · okokGasStation · ti_fuel · lc_fuel · esx-sna-fuel — oder jede Ressource mit einem GetFuel(vehicle)-Export
Voice pma-voice · SaltyChat — automatisch erkannt, dazu eine Custom-Voice-API
Speicherung Einstellungen pro Spieler über den KVP-Store — keine Datenbank nötig
Sonstiges OneSync-kompatibel

Bestehende Scripts müssen nicht angefasst werden. Legacy-Events wie hud:client:UpdateNeeds und hud:client:UpdateStress funktionieren weiter — vorhandene Consumable- und Stress-Scripts laufen unverändert.

Installation

  1. Ordner nach resources/ kopieren.
  2. In die server.cfg eintragen:
ensure ancomox_hud
  1. Server starten. Framework, Tanksystem und Voice werden beim Start selbst erkannt.
  2. Ingame /hud eingeben — das Einstellungsmenü öffnet sich, alles Weitere stellt jeder Spieler selbst ein.

Konfiguration

Trotz Escrow-Schutz bleibt alles offen, was zur Integration gebraucht wird:

Datei Inhalt
config.lua Sämtliche Servereinstellungen und Vorgaben.
Beide Sprachdateien Deutsch und Englisch, jeder Text änderbar.
Notification- und API-Schicht Vollständig offen.
Custom-Framework-Adapter Anbindung eines eigenen Frameworks über zwei Config-Getter.

Ein eigenes Framework lässt sich damit vollständig integrieren, ohne verschlüsselten Code anzufassen.

Spielerseitige Einstellungen

Über /hud stellt jeder Spieler selbst ein: Theme, Position aller Elemente per Drag & Drop, Farben, Skalierung und die eigene Tick-Rate zwischen 16 und 1000 ms. Wer auf schwacher Hardware spielt, dreht die Update-Frequenz herunter, ohne dass der Serverbetreiber es für alle festlegen muss. Alles wird automatisch pro Spieler gespeichert.

Exports & Developer-API

Eigene Status-Anzeigen — Drogen-Level, Strahlung, Energie und Ähnliches — lassen sich in drei Zeilen registrieren. Die Theme-Anpassung passiert automatisch: Das neue Element erscheint in Apex Circles, Modern Bars und Minimal, ohne dass du es dreimal baust.

Dazu kommt eine vollständige Export-Suite auf Client und Server. Dokumentiert sind unter anderem:

Bereich Zweck
Notify Benachrichtigung über das HUD-eigene System ausgeben.
Progress Fortschrittsleiste anzeigen.
SetPlayerInfo Spielerdaten wie Job und Geld setzen — der Weg für Custom-Frameworks.
ForceHide HUD vorübergehend ausblenden, etwa während einer Cutscene.
Seatbelt / Harness Gurt- und Harness-Zustand setzen und abfragen.
VoiceState Eigenes Voice-System anbinden.

Die genauen Signaturen aller Exports stehen in der mitgelieferten README. Sie ist vollständig dokumentiert und liegt unverschlüsselt bei.

Events & Commands

Befehl Wirkung
/hud Einstellungsmenü öffnen — Theme, Layout, Farben, Skalierung, Tick-Rate.
/streamermode Streamer-Modus umschalten.

Kompatibilitäts-Events

TriggerEvent('hud:client:UpdateNeeds',  hunger, thirst)
TriggerEvent('hud:client:UpdateStress', stress)

Beide Events sind absichtlich beibehalten, damit bestehende Consumable- und Stress-Scripts ohne Anpassung weiterlaufen.

Performance

  • Event-getrieben statt Polling — Geld und Job werden über Events aktualisiert, nicht in einer Dauerschleife abgefragt.
  • Diff-basierte Updates — gesendet wird nur, was sich tatsächlich geändert hat.
  • Tick-Rate pro Spieler zwischen 16 und 1000 ms, vom Spieler selbst einstellbar.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Tankanzeige bleibt leer Das Tanksystem wurde nicht erkannt. Neun Systeme sind eingebaut; jede andere Ressource funktioniert, sobald sie einen GetFuel(vehicle)-Export anbietet.
Kein Voice-Badge Erkannt werden pma-voice und SaltyChat. Ein anderes System bindest du über die Custom-Voice-API an.
Hunger und Durst bewegen sich nicht Das Consumable-Script muss hud:client:UpdateNeeds auslösen. Bei ESX Legacy und QBCore passiert das in der Regel bereits.
Geld oder Job werden nicht angezeigt Bei einem Custom-Framework müssen die beiden Config-Getter gefüllt oder SetPlayerInfo aufgerufen werden.
Doppelte Benachrichtigungen Das HUD fängt ESX- und QB-Notifications ab. Läuft parallel ein weiteres Notify-Script, den Intercept in der config.lua abschalten oder das Redirect-Snippet verwenden.
Einstellungen sind nach dem Neustart weg Die Einstellungen liegen im KVP-Store des Clients. Ein geleerter FiveM-Cache setzt sie zurück.
Element sitzt an einer unpassenden Stelle Position, Skalierung und Farbe stellt jeder Spieler selbst über /hud ein — es gibt keine serverseitige Zwangsposition.

GPS + Job + Admin Blips — Entwickler-Dokumentation

Im Ancomox-StoreGPS + Job + Admin Blips
Zum Produkt

GPS + Job + Admin Blips — Blip-Verwaltung für ESX-Server: Kollegen desselben Jobs sehen sich gegenseitig auf der Karte, Admins sehen alle. Fahrzeugtyp, Sirenenstatus und Entfernung steuern Darstellung und Transparenz.

Überblick

Das Script verwaltet die Kartenblips aller Spieler nach Job-Zugehörigkeit. Wer im selben Job ist, sieht seine Kollegen in Echtzeit; Admins sehen unabhängig davon jeden. Aktiviert und deaktiviert wird automatisch anhand des Jobstatus und der Config.

Funktionsumfang

Bereich Verhalten
Job-Tracking Blips werden anhand von Jobstatus und Config-Einstellung automatisch an- und abgeschaltet. Die Positionen aktualisieren sich in Echtzeit.
Sichtbarkeit für Kollegen Alle Spieler im selben Job sehen einander.
Admin-Sichtbarkeit Admins sehen sämtliche Blips — auch ohne selbst einen Admin-Job zu haben. Die Erkennung läuft wahlweise über einen Admin-Job oder eine Admin-Benutzergruppe.
Fahrzeugunterstützung Unterschiedliche Blip-Sprites und Farben je Fahrzeugtyp. Die Darstellung richtet sich zusätzlich nach dem Fahrzeugstatus und danach, ob die Sirene läuft.
Transparenz Dynamisch nach Entfernung, über die Config einstellbar.
Spieler-Events Blips werden bei Tod und Fahrzeugwechsel automatisch mitgeführt.
Manuelles Umschalten Das GPS lässt sich per Befehl ein- und ausschalten.

Voraussetzungen

Abhängigkeit Pflicht Hinweis
es_extended Ja Das Script ist speziell für ESX-basierte Server gebaut. QBCore und Qbox werden nicht unterstützt.

Zur Leistungsaufnahme: Der Wert im Leerlauf steigt mit der Spielerzahl, weil für jeden sichtbaren Spieler ein Blip geführt und dessen Position aktualisiert wird. Auf einem vollen Server ist das spürbar mehr als bei drei Spielern — das ist bauartbedingt und betrifft jedes Blip-Script dieser Art.

Installation

  1. Ordner nach resources/ kopieren.
  2. Mit ensure in die server.cfg eintragen — nach es_extended.
  3. In der config.lua die Jobs eintragen, für die Blips geführt werden sollen, und festlegen, wie Admins erkannt werden (Job oder Benutzergruppe).
  4. Server neu starten.

Konfiguration

Die gesamte Einrichtung passiert in der config.lua:

Bereich Einstellbar
Jobs Welche Jobs Blips bekommen und ob sie automatisch aktiviert werden.
Admin-Erkennung Admin-Job oder Admin-Benutzergruppe. Beides ist möglich; Admins sehen alle Blips auch ohne passenden Job.
Fahrzeug-Blips Sprite und Farbe je Fahrzeugtyp, dazu abweichende Darstellung bei aktiver Sirene.
Transparenz Wie stark Blips mit zunehmender Entfernung ausblenden.

Events & Commands

Das GPS lässt sich manuell per Befehl umschalten. Den genauen Befehlsnamen legst du in der config.lua fest — dort steht auch die Standardbelegung.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Keine Blips sichtbar Der Job ist nicht in der config.lua eingetragen, oder das automatische Aktivieren ist für diesen Job abgeschaltet.
Admin sieht nicht alles Prüfen, ob die Admin-Erkennung auf Job oder auf Benutzergruppe eingestellt ist und ob der Account zur gewählten Variante passt.
Blips verschwinden auf Distanz Kein Fehler — die Transparenz nimmt mit der Entfernung ab. Der Verlauf ist in der Config einstellbar.
Falsches Symbol am Fahrzeug Sprite und Farbe werden je Fahrzeugtyp gesetzt. Für ein neues Fahrzeugmodell den passenden Typ in der Config ergänzen.
Blip bleibt nach dem Tod stehen Sollte nicht auftreten — Tod und Fahrzeugwechsel werden mitgeführt. Tritt es doch auf, läuft eine ältere Version.
Höhere Last bei vielen Spielern Bauartbedingt: Je mehr sichtbare Spieler, desto mehr Blips werden geführt und aktualisiert. Über die Job-Auswahl lässt sich die Menge begrenzen.
Läuft nicht auf QBCore Das Script setzt ESX voraus und bindet sich direkt an es_extended.

Ancomox Banking — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Banking
Zum Produkt

Überblick

ancomox_banking stellt Filialen, Geldautomaten, Spar- und Firmenkonten, Kredite sowie übertragbare Bankkarten bereit. Das Girokonto spiegelt das Framework-Bankguthaben — es entsteht kein zweiter Geldbestand. Spar-, Firmen- und Kartenkonten führt das Script selbst.

Modul Funktion
Girokonto Spiegelt das Framework-Guthaben. Überweisung per IBAN, auch an offline Spieler.
Sparkonto Eigenes Konto mit automatischer Verzinsung in einstellbaren Intervallen, wahlweise einfach oder mit Zinseszins, optional mit Höchstbetrag.
Kredite Vermögensbasierte Bonitätsprüfung, wählbare Laufzeiten, automatischer Raten-Einzug in Echtzeit-Intervallen, Verzugsgebühren und Ausfall-Logik.
Firmen- & Gang-Konten Drei Rollen (Inhaber, Verwalter, Mitglied) und vier einzeln schaltbare Rechte: einzahlen, abheben, überweisen, verwalten.
Bankkarten Inventar-Item mit Metadaten, an genau ein Konto gebunden.
Kontakte Adressbuch (bis 30 Einträge), zuletzt genutzt, Spieler in der Nähe, eigene IBAN teilen.
Bilanz Wochenverlauf über 7, 14 oder 30 Tage, je Unternehmen und je Kategorie.
Überfall ATM und Tresor, drei Modi.

Jedes Modul lässt sich einzeln abschalten und verschwindet dann vollständig aus der Oberfläche.

Neun Filialen sind vorkonfiguriert, davon eine große mit Tresor. Weitere werden in der Config angelegt: Koordinate, Name, Blip und Ped.

Bankkarten

Eine Karte ist ein Inventar-Item mit Metadaten, kein Datenbankeintrag:

  • Beim Bestellen wählt der Spieler das Konto (Giro, Spar oder Firma). Die Karte öffnet ausschließlich dieses Konto.
  • Wer Karte und PIN hat, kann am Automaten ein- und auszahlen — auch wenn der Kontoinhaber offline ist.
  • Die PIN wird mit einem Server-Secret gehasht, kein Klartext in der Datenbank. Fehlversuche werden gezählt, danach sperrt sich die Karte selbst.
  • Der Besitz wird bei jeder Transaktion neu geprüft — Weitergeben oder Diebstahl wirkt sofort.
  • Beim Kündigen ist die Karte sofort tot, laufende Automaten-Sitzungen werden beendet, der Kartenplatz wird frei. Das Item bleibt beim Halter.
  • Der Automat zieht tote Karten ein: gekündigte Karten, Karten auf gesperrten Konten und nicht mehr existierende Karten werden behalten und das Item vernichtet.

Überfall

Modus Ablauf
Thermit (Standard) Ladung wird an der Vorderseite des Automaten befestigt — die Seite wird aus der Bounding Box des Props ermittelt, funktioniert also unabhängig von der Drehung. Zündschnur brennt, Explosion, Feuer bleibt am Boden, Beute anschließend abholen.
ox_lib Skill-Check Minispiel statt Sprengung.
Fortschrittsbalken Reiner Timer.

Einstellbar sind Cooldowns, Polizei-Mindestanzahl, Alarm-Blips, Werkzeug-Verbrauch (beim Platzieren oder erst bei Erfolg) und ein Zeitfenster fürs Einsammeln.

Die Explosion nutzt den Parameter für kosmetische Detonationen — sichtbar und hörbar, aber ungefährlich. Auf Wunsch scharf schaltbar. Fehlt ein DLC-Prop auf deinem Build, läuft der Überfall mit Effekten weiter, statt abzubrechen.

Voraussetzungen

Abhängigkeit Pflicht Hinweis
FiveM-Server Ja Build 6683 oder neuer (fx_version 'cerulean', lua54).
oxmysql Ja MySQL 5.7+ oder MariaDB 10.2+.
ox_lib Ja Nicht optional — wird für Callbacks, Fortschrittsbalken, Kontextmenüs und Benachrichtigungen genutzt.
Framework Nein ox_core, es_extended, qb-core oder qbx_corein dieser Erkennungsreihenfolge, per Config überschreibbar. Alternativ der mitgelieferte Standalone-Modus.
ox_inventory Empfohlen Ohne Item-Metadaten funktionieren Bankkarten nur beim eigenen Besitzer. Das Script meldet das beim Start in der Konsole.
ox_target / qb-target Optional Ohne beides schaltet das Script auf einen integrierten DrawText-Modus mit Tastendruck um.
OneSync Optional Nur für den Tab „In der Nähe" nötig, weil dort Positionen serverseitig gemessen werden.

Installation

  1. Ordner nach resources/ legen und in die server.cfg eintragen:
ensure ancomox_banking
  1. install.sql einmalig importieren — sieben Tabellen. Alle späteren Änderungen migrieren sich selbst.
  2. In der server.cfg ein eigenes Geheimnis für die Karten-PINs setzen. Das Script warnt beim Start, bis das erledigt ist.
  3. Bei ox_inventory: das Item bank_card mit stack = false eintragen. Der fertige Ausschnitt liegt in der install.sql und im README.

Datenbank

Sieben Tabellen, angelegt über install.sql. Anders als bei den übrigen Ancomox-Ressourcen ist dieser Import Pflicht — nur die späteren Schema-Änderungen laufen automatisch als Migration beim Start.

Konfiguration

Frei bearbeitbar Inhalt
config.lua Filialen, Automaten, Limits, Gebühren, Zinssätze, Kreditlaufzeiten, Überfall-Parameter
locales/ Deutsch und Englisch, je 325 Texte. Fehlt ein Schlüssel, greift automatisch Englisch.
install.sql Das Datenbankschema
README, CHANGELOG, LICENSE, docs/ Sechs Handbücher: Installation, Kompatibilitätsmatrix, Standalone-Betrieb, Überfall-Modi, Bilanz-Tab, Kontaktsystem

Verschlüsselt ist ausschließlich die Kernlogik.

Eigenes Framework anbinden

Die komplette Framework-Anbindung liegt in sechs lesbaren Bridge-Dateien mit dokumentierter Schnittstelle. Der Standalone-Modus bringt eine eigene Wirtschaft mit Bargeld, Bank und Job mit — inklusive atomarer Buchungen und Exports zur Anbindung eines eigenen Systems. Er greift auch automatisch als Fallback, wenn das Framework nicht startet.

Sicherheit

  • Positionsgeprüfte Sitzungen: Eine UI-Sitzung entsteht erst, wenn der Server bestätigt, dass der Spieler an der gemeldeten Filiale beziehungsweise genau an diesem Automaten steht. Jeder Geld-Callback verlangt eine gültige Sitzung.
  • Atomare Kontobuchungen: Abbuchungen laufen als bedingtes SQL-Update (WHERE balance >= ?). Zwei parallele Abhebungen können das Konto nicht ins Minus ziehen.
  • Reserviertes Tageslimit: Das Überweisungslimit wird vor der Buchung reserviert, damit zwei gleichzeitige Überweisungen nicht dasselbe Restkontingent verbrauchen.
  • Rate-Limits pro Aktionstyp — getrennt nach Einzahlen, Abheben, Überweisen und Kartenaktionen.
  • Karten-PINs gehasht mit dem Server-Secret aus der server.cfg, plus Fehlversuchs-Sperre auf der Karte und Brute-Force-Sperre pro Spieler.
  • Verdächtige Anfragen werden mit Name und Grund in der Konsole protokolliert, optional zusätzlich an Discord.

Discord-Logs

Sieben Kategorien, einzeln schaltbar: Überweisung, Abhebung, Einzahlung, Kredit, Überfall, Firmenkonto, Sicherheit.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Warnung zum fehlenden Geheimnis beim Start Das Server-Secret für die Karten-PINs ist nicht in der server.cfg gesetzt. Die Warnung bleibt, bis es gesetzt ist.
Karten funktionieren nur beim eigenen Besitzer Ohne Item-Metadaten fehlt die Kontobindung. ox_inventory nutzen — das Script weist beim Start darauf hin.
Tab „In der Nähe" bleibt leer OneSync wird benötigt, weil die Entfernung serverseitig gemessen wird.
Automat zieht die Karte ein Beabsichtigt bei gekündigten Karten, gesperrten Konten und nicht mehr existierenden Karten. So verschwindet eine gestohlene Karte aus dem Verkehr.
Kein Target am Automaten Ohne ox_target oder qb-target greift der eingebaute DrawText-Modus mit Tastendruck.
Guthaben doppelt vorhanden Kann nicht auftreten — das Girokonto spiegelt das Framework-Guthaben. Nur Spar-, Firmen- und Kartenkonten sind eigenständig.
Historie aus dem alten Bankscript fehlt Guthaben wird über die Spiegelung übernommen, Historien anderer Skripte werden nicht importiert.
Tabellen fehlen nach dem Start install.sql wurde nicht importiert. Der Erstimport ist bei diesem Script Pflicht.

Ancomox Crafting — Entwickler-Dokumentation

Im Ancomox-StoreAncomox Crafting
Zum Produkt

Überblick

ancomox_crafting bindet Rezepte an Stationen statt an eine globale Liste. Jede Werkbank führt genau die Kategorien oder Rezepte, die ihr zugewiesen sind. Aufträge liegen in der Datenbank und laufen weiter, während der Spieler offline ist.

Ab Werk enthalten: 32 Rezepte in 9 Kategorien, 8 vorkonfigurierte Stationen (öffentliche Werkbank, gang-gesperrte Waffenbank, Chemielabor, Medizinlabor, Mechaniker-Werkbank, Elektronikwerkstatt, mobile Werkbank, Lagerfeuer) und 7 Baupläne.

Stationen

Betriebsart Verhalten
active Davorstehen und arbeiten.
queue Auftrag abgeben und später abholen.
both Beides, der Spieler entscheidet.

Zugriff wird nach Beruf und Gang mit Mindestrang geregelt — pro Station und pro Rezept. Ein leeres Feld bedeutet: jeder darf. Optional lässt sich ein Gegenstand verlangen, den der Spieler dabeihaben muss.

Drei Ausbaustufen je Station, jeweils mit Geld- und Materialkosten. Sie heben Tempo, Qualitätschance und die Zahl paralleler Warteschlangen-Plätze und schalten Rezepte frei, die eine ausgebaute Station voraussetzen.

Die Progression lässt sich pro Station und pro Kategorie abschalten — dort gibt es dann weder XP-Anforderungen noch XP-Gewinn.

Platzierbare Werkbänke

Als Item aus dem Inventar im Spiel ausrichten und abstellen. Übersteht Neustarts, hat eine eigene Kiste und eine eigene Ausbaustufe. Fünf Zugriffsmodi: nur der Besitzer, eine Freigabeliste, der eigene Beruf, der eigene Gang oder offen für alle.

Einstellbar sind Mindestabstand, Höchstzahl pro Charakter sowie Sperren im Fahrzeug und im Wasser. Beim Einpacken kommt das Item zurück ins Inventar.

Fertigkeiten & Qualität

  • Fertigkeiten pro Kategorie: 50 Stufen, 7 Ränge. Die XP-Kurve liegt offen; der Config-Prüfer meldet beim Start, wenn sie nicht mehr monoton steigt.
  • Fünf Qualitätsstufen von Grob bis Meisterwerk, ausgewürfelt aus Fertigkeit, Ausbaustufe der Station und Minispiel-Ergebnis. Die Stufe landet als Metadaten am Gegenstand: Rang, Punktzahl, Haltbarkeitsbonus, Name des Herstellers, Zeitstempel und ein Label-Zusatz.
  • Drei Fortschritts-Boni mit je eigener Startstufe und Deckel: Materialersparnis, Bonusausbeute und kürzere Herstellungszeit — alle als Zahlen in der Config.
  • Baupläne in drei Varianten: einmal lesen und dauerhaft freischalten, begrenzte Anzahl Nutzungen, oder schlicht dabeihaben müssen.
  • Zerlegen: Jedes Rezept läuft rückwärts. Die Rückgabequote hängt an der Fertigkeit — 35 % beim Neuling bis 80 % beim Meister.
  • Werkzeuge mit Verschleiß: Eine Zutat lässt sich als Werkzeug markieren — nötig, aber nicht verbraucht, dafür mit Abnutzung pro Herstellung.
  • Nebenprodukte mit eigener Chance je Rezept.

Vier Minispiel-Profile: drei fertige über ox_lib (ruhig, präzise, hektisch) plus ein dokumentierter Einhängepunkt für ein Fremd-Minispiel. Das Ergebnis beeinflusst Erfolgschance und Qualität, nie, ob überhaupt ein Gegenstand entsteht.

Warteschlange & Materialquellen

  • Zutaten kommen wahlweise aus dem Inventar des Spielers oder aus der Kiste der Werkbank. Welche Quelle zuerst durchsucht wird, ist einstellbar.
  • Aufträge liegen in der Datenbank, laufen offline weiter und überstehen Serverneustarts. Die Zahl paralleler Aufträge wächst mit der Ausbaustufe.
  • Überlauf-Kette bei vollem Inventar: Kiste der Station → persistentes Abholfach → Abruf über /craftclaim. Gelöscht wird nichts.
  • Kisten wahlweise gemeinsam für die ganze Station oder als eigenes Fach pro Charakter. Slots und Gewicht werden pro Station gesetzt.

Voraussetzungen

Abhängigkeit Pflicht Hinweis
FiveM-Server Ja Build 7290 oder neuer.
OneSync Ja Muss aktiviert sein.
oxmysql Ja
ox_lib Ja Callbacks, Fortschrittsleisten, Minispiele, Benachrichtigungen.
Framework Nein qbx_core, qb-core, es_extended, ox_core, ND_Core oder Standalone.
Inventar Ja ox_inventory (empfohlen, vollständig integriert) · qb-inventory, ps-inventory, lj-inventory · qs-inventory, codem-inventory, origen_inventory, tgiann-inventory, core_inventory · ESX-Standardinventar · jedes andere über die Bridge.
Third-Eye Optional ox_target, sleepless_interact, qb-target oder interact. Ohne eines davon greift ein eingebauter Hinweis mit Tastendruck.

Erkennungsreihenfolge: qbx_core meldet sich gegenüber anderen Ressourcen als qb-core. QBox wird deshalb zuerst geprüft.

Inventar-Fähigkeiten: Fehlt die Kisten-Unterstützung, greift das Script still auf den Rucksack zurück. Fehlt die Metadaten-Unterstützung, lassen sich die Qualitätsstufen mit einem Schalter deaktivieren.

Installation

  1. Ordner ancomox_crafting nach resources/ kopieren.
  2. In der server.cfg nach ox_lib, oxmysql und dem Framework eintragen:
ensure ancomox_crafting
  1. Die neuen Items ins Inventar eintragen — fertige Copy-Paste-Blöcke für jedes unterstützte System stehen in docs/installation.md.
  2. Server starten. Die acht Datenbanktabellen legt das Script beim ersten Start selbst an; die SQL-Datei liegt nur für Datenbankbenutzer ohne CREATE-Recht bei.
  3. Ingame /craftbuilder öffnen und die erste Werkbank setzen.

Der Ordner darf umbenannt werden — die Oberfläche fragt ihren eigenen Ressourcennamen ab.

Datenbank

Acht Tabellen, beim ersten Start automatisch angelegt. Die beiliegende SQL-Datei wird nur gebraucht, wenn dem Datenbankbenutzer die Rechte zum Anlegen fehlen.

Fertigkeiten und Rezepte liegen im Arbeitsspeicher; geschrieben wird nur bei echten Änderungen.

Baukasten

Stationen, Rezepte, Zugriffsrechte und Werkbank-Positionen werden über /craftbuilder im laufenden Betrieb angelegt — ohne Neustart und ohne Config-Datei.

  • Stationen live platzieren: Ein durchscheinendes Geister-Prop erscheint vor dem Spieler. Linke Maustaste setzt, rechte bricht ab, das Mausrad dreht in 8-Grad-Schritten, die Pfeiltasten justieren die Höhe. Koordinaten und Ausrichtung landen ausgefüllt im Formular.
  • Item-Browser an jedem Item-Feld: das komplette Item-Verzeichnis als Raster mit Bild, Anzeigename und internem Namen, Suche filtert beim Tippen.
  • Die Config wird überlagert, nie überschrieben. Ein Baukasten-Eintrag mit derselben ID legt sich über den Config-Eintrag; wird er gelöscht, gilt wieder das Original. Datei-Anpassungen bleiben auch nach einem Update erhalten.
  • Übertragbar: Alles Gebaute wird zusätzlich als data/custom.json geschrieben. Datei kopieren, /ancoimport ausführen.
  • Rechte und Eingaben serverseitig: Zugang über ACE-Berechtigung, feste Identifier-Liste oder die Framework-Admins. Jedes Feld wird beim Speichern serverseitig geprüft und begrenzt.

Konfiguration

Frei bearbeitbar Inhalt
Alle Config-Dateien Rezepte, Stationen, Kategorien, Fortschritt, Minispiele, Hauptkonfiguration
Alle Bridge-Adapter 6 Framework-, 8 Inventar- und 4 Target-Adapter mit identischer Schnittstelle
Komplette Oberfläche HTML, CSS und JavaScript
Balancing-Mathematik Qualitätsstufen und Boni
Vier Sprachdateien Deutsch, Englisch, Französisch, Spanisch. Englisch ist immer der Rückfall.
SQL-Datei und Dokumentation Über 4.500 Zeilen in neun Kapiteln

Ein eigenes Inventar anzubinden sind rund 60 Zeilen: zählen, hinzufügen, entfernen, Traglast prüfen. Fähigkeitsschalter am Dateianfang sagen dem Script, was das Inventar kann — fehlt eine Fähigkeit, schaltet sich genau diese Funktion ab, statt Fehler zu werfen.

Config-Prüfer

Beim Start werden unbekannte Kategorien, doppelte Rezept-IDs, nicht existierende Items, von keiner Station erreichbare Rezepte und eine nicht monoton steigende XP-Kurve gemeldet — jeweils mit Datei und Zeile.

Exports

24 Exports stehen bereit, unter anderem für: Fertigkeit abfragen, XP vergeben, ein Doppel-XP-Wochenende schalten, einen Bauplan als Beute verteilen, Rezepte und Stationen zur Laufzeit anlegen, vorab prüfen ob ein Spieler etwas bauen darf, sowie eine Herstellung aus einem anderen Script heraus starten.

Die vollständige Liste mit Signaturen steht im Exports-Kapitel der mitgelieferten Dokumentation.

Events & Commands

Insgesamt 13 Befehle. Die wichtigsten:

Befehl Wirkung
/craftbuilder Baukasten öffnen — Stationen und Rezepte im Spiel anlegen.
/craftclaim Ergebnisse aus dem Abholfach holen.
/craftclose Notausgang, falls die Oberfläche hängt.
/ancoreload Config neu laden, nachdem an den Dateien gearbeitet wurde.
/ancoimport data/custom.json von einem anderen Server einlesen.

Dazu Befehle zum XP-Vergeben, Baupläne verschenken, Statistik ausgeben, Werkbänke auflisten und Stationszonen sichtbar machen.

Ein Wächter gibt den NUI-Fokus automatisch zurück, falls die Oberfläche stehenbleibt.

Sicherheit

  • Material wird beim Start verbraucht, nie am Ende. Damit existiert das Zeitfenster nicht, aus dem die bekannten Crafting-Dupes entstehen.
  • Vollständiger Rollback: Schlägt das Abziehen mitten in einer Zutatenliste fehl, wird alles bereits Entnommene zurückgelegt und der Auftrag abgelehnt.
  • Speedhack-Sperre: Der Server merkt sich den Startzeitpunkt und lehnt zu frühe Fertigmeldungen ab — mit voller Rückerstattung und Eintrag im Sicherheitslog.
  • Distanzprüfung doppelt — beim Start und beim Abschluss, mit einstellbarer Toleranz. Bei Stationen mit mehreren Standorten wird gegen den nächstgelegenen gemessen.
  • Rate-Limit: Mindestabstand zwischen zwei Anfragen plus rollendes Minutenfenster.
  • Anti-Makro: optional erzwungenes Minispiel alle N Herstellungen, auch bei Rezepten ohne reguläres Minispiel.
  • Rechte werden zweimal geprüft — beim Öffnen der Oberfläche und bei jedem einzelnen Auftrag. Wer die Uniform mitten im Craft auszieht, produziert nicht weiter.

Dem Paket liegen 87 automatisierte Tests bei, die die echte Konfiguration und den Servercode mit nachgebildeten FiveM-Funktionen laden — darunter Anti-Dupe-Rollback, Speedhack-Sperre, Mehrfachnutzung eines Auftrags-Tokens, Bauplan-, Job- und Gang-Sperren, Stash-Sourcing und die Qualitätsmathematik.

Discord-Logs

Fünf Kanäle: Herstellung, Warteschlange, Verwertung, Baukasten und Auffälligkeiten. Gebündelt versendet, damit Discord nicht drosselt, wahlweise zusätzlich in die Datenbank.

Ein eigener Statistik-Tab wertet tagesgenau nach Rezept und Kategorie aus und zeigt meistgebaute Gegenstände sowie laufende Aufträge.

Hinweise / Troubleshooting

Symptom Ursache und Lösung
Rezept taucht nirgends auf Es ist keiner Station zugewiesen. Der Config-Prüfer meldet das beim Start mit Datei und Zeile.
Item wird nicht gefunden Der interne Name stimmt nicht mit dem Inventar überein. Im Baukasten den Item-Browser statt manueller Eingabe verwenden.
Aufträge ziehen nicht aus der Kiste Das Inventar unterstützt keinen Kistenzugriff. Das Script fällt still auf den Rucksack zurück.
Keine Qualitätsstufen am Gegenstand Das Inventar unterstützt keine Metadaten. Die Stufen lassen sich per Schalter deaktivieren.
QBox wird als QBCore erkannt Sollte nicht auftreten — qbx_core wird zuerst geprüft. Andernfalls das Framework in der Config erzwingen.
Ergebnis scheinbar verloren Bei vollem Inventar wandert es in die Stationskiste, dann ins Abholfach. Abruf über /craftclaim.
Config-Änderung wirkt nicht Ein Baukasten-Eintrag mit derselben ID überlagert die Config. Eintrag im Baukasten löschen, dann gilt wieder das Original.
Cursor bleibt sichtbar /craftclose ausführen. Der eingebaute Wächter sollte den Fokus ohnehin zurückgeben.
Auftrag wird als ungültig abgelehnt Die Speedhack-Sperre hat angeschlagen. Das Material wird vollständig erstattet, der Vorgang steht im Sicherheitslog.
© Ancomox