Fast jede Funktion jenseits des Kerns ist ein Plugin: ein eigenes Paket unter packages/gta5os-<name>/, das nur gegen die öffentliche Core-API arbeitet. Der Core kennt kein Plugin; ein Plugin darf nichts verwenden, was der Core nicht ausdrücklich exportiert.
Das Manifest
Jedes Plugin beschreibt sich in einem Manifest. Das Zielbild aus der Architektur sieht so aus:
id: gta5os.inventory
name: Inventory
version: 0.1.0
dependencies:
- gta5os.core
permissions:
- inventory.manage
commands:
- id: inventory.give
ui:
routes:
- /inventory
migrations:
- 001_inventory_init.sql
Der Core prüft das Manifest mit einem Zod-Schema und kann es aus einem Objekt oder einer JSON-Datei laden. Ein Verstoß führt zu einem PluginManifestError, nicht zu stillem Fehlverhalten.
Lebenszyklus
Die Plugin-Runtime ruft vier Hooks auf:
| Hook | Aufgabe |
|---|---|
onRegister(ctx) |
Statische Registrierung von Commands, Items, Berechtigungen und UI |
onStart(ctx) |
Dienste starten, Event-Handler binden |
onPlayerLoaded(player) |
Spielerbezogene Initialisierung |
onStop(ctx) |
Sauber abmelden, leeren, Messwerte abschließen |
Die Runtime prüft vor dem Start die Abhängigkeiten. Commands, Berechtigungen und UI-Routen darf ein Plugin nur registrieren, wenn sie im Manifest stehen; der Lebenszyklus wird protokolliert.
Services
Dienste werden über die Service-Registry verbunden. Tokens sind typisiert, die Registry bietet register, resolve, tryResolve und has. Eine doppelte Registrierung oder ein fehlender Dienst wirft einen Fehler, statt still zu scheitern.
Wer einen neuen Service im Core ergänzt, geht so vor:
- Ordner
src/<domain>/mit<domain>-service.tsanlegen. - Eine eigene Fehlerklasse
<Domain>Errordefinieren statt generischerError. - Werden Berechtigungen gebraucht: eine Konstante
<DOMAIN>_PERMISSIONSundregister<Domain>Permissions(registry)exportieren und den Aufruf an der gebündelten Stelle der Resource ergänzen. - Die öffentliche API in
src/index.tsexportieren. Das ist die einzige Paket-Oberfläche; was dort nicht steht, ist für andere Pakete nicht erreichbar. - Smoke-Szenarien ergänzen.
- Hängt eine Tabelle an einer Charakter-ID, wird sie in die Charakter-Kaskade eingetragen, damit beim Löschen eines Charakters nichts verwaist.
Berechtigungen
- Berechtigungen sind namespaced:
plugin.aktion. - Es gibt Rollen mit Wildcards, direkte Grants und Denies; ein ausdrückliches Deny widerruft sofort, auch gegen einen Wildcard-Grant.
- Geprüft wird serverseitig mit
assertCan. Spielerrechte sind von höheren Rechten getrennt. - Rechte kommen oft nicht aus Rollen, sondern aus dem Job: Job-Capabilities koppeln Funktionen an eine Fraktion oder einen Beruf.
Daten und Migrationen
Plugins speichern über den DataStore-Vertrag des Core und besitzen ihr eigenes Schema. Migrationen laufen kontrolliert über den Core. Gameplay-Code greift nie direkt auf die Datenbank zu, sondern über Repositories; Geld, Inventar und Fahrzeuge werden in Transaktionen geändert.
UI-Routen
Die Oberfläche kennt pro Plugin-Fenster eine Route. Zwei Dinge gehören dazu:
- Der Core-Service Route-Registry der UI-Shell nimmt die im Manifest deklarierten Routen entgegen.
- Im Browser-Teil der Shell sind für eine neue Route drei Stellen nötig: die Route-Datei mit
render<Name>Route(...), ein Eintrag in der Router-Liste (Pfad, Titel) und ein Eintrag bei den Renderern der Shell. Dazu kommt das öffnende Command oder die Tastenbelegung in der Resource.
Wird eine der drei Stellen vergessen, existiert die Datei zwar, die Route rendert aber nichts oder ist nicht erreichbar. Jede Route braucht außerdem Demo-Daten für die Browser-Vorschau, damit sie sich ohne Spielclient ansehen lässt.
Routen werden bei jedem Öffnen komplett neu gerendert. Die Shell meldet die Handler der vorigen Instanz selbst ab; Timer und eigene Listener muss die Route aber selbst beenden.
Beispiel: ein Plugin ohne Rechte, Commands und Routen
Das Kompatibilitäts-Plugin gta5os.compat.esxqb hat bewusst weder Berechtigungen noch Commands oder UI-Routen. Es bildet ESX- und QBCore-artige Aufrufe (Geld, Konten, Inventar) auf die Core-Dienste ab, ohne eigene Datenbankzugriffe und ohne Änderung am Core. Reale ESX-Resources lassen sich so mit einem dünnen Lua-Shim weiterverwenden.