AI Core
API-ReferenzLib: Server

Admin-Panel

AdminPanel und DataFile: die Einstellungen deines Skripts ins Admin-Panel von ai-core bringen, sie lesen, auf Änderungen reagieren und Panel-Inhalte in data/*.json speichern.

TypeScript
import { AdminPanel, DataFile } from "@aicore-fivem/lib/server";

Jedes ai-*-Skript hält die Einstellungen, die ein Serverbetreiber anpasst, im Admin-Panel von ai-core, nicht in einer Konfigurationsdatei. Du beschreibst die Daten und das Formular in resource/server/schema.ts (PanelSchema: Dateien, Tabs, Karten, Widgets; siehe Ein Skript erstellen), erstellst daraus ein AdminPanel und liest die Werte darüber. Die Seite des Betreibers (Panel öffnen, bearbeiten, Berechtigungen) steht unter Admin-Panel.

Kein Lua-Zwilling: Das Admin-Panel gibt es nur in TypeScript.

AdminPanel

Die Panel-Inhalte deiner Ressource. Es lädt jede data/<file>.json einmal beim Start, hält sie im Speicher, prüft jede Panel-Änderung gegen das Schema, speichert die Datei (atomar, eine Reihe schneller Änderungen wird einmal geschrieben) und informiert deinen Code, die offenen Panels und, bei Dateien mit sync: "all", jeden Client. Es schreibt außerdem data/schemas/<file>.schema.json, damit ein Editor eine Handbearbeitung prüfen kann.

Seite: Server · Wartet: nein

Signatur
new AdminPanel(schema: PanelSchema)

Prop

Type

Erstelle genau eins, auf Modulebene, meist in resource/server/content.ts: export const panel = new AdminPanel(schema);. Settings-Dateien bekommen fehlende Abschnitte beim Laden aus defaults. ai-core listet das Panel einen Tick nach der Erstellung.

AdminPanel.get

Der aktuelle Inhalt einer Datei.

Signatur
get<T = Record<string, unknown>>(file: string): T

Rückgabewert: das Settings-Objekt (eine settings-Datei) oder { key: record } (eine records-Datei); {} für eine unbekannte Datei. Nur lesen: Ändere das zurückgegebene Objekt nie, nutze commit.

const settings = panel.get<{ prices: { repair: number } }>("settings");

AdminPanel.view

Eine Settings-Datei, von deinem Reader geparst und bei jeder Änderung aktuell gehalten (geparst, bevor onChange-Handler laufen).

Signatur
view<T>(file: string, read: (raw: unknown) => T): { readonly value: T }

Rückgabewert: ein Objekt, dessen value immer der neueste geparste Inhalt ist. Lies .value jedes Mal; kopiere es nicht einmalig heraus.

import { int, record } from "@aicore-fivem/lib/shared";

const settings = panel.view("settings", (raw) => ({
  repairPrice: int(record(record(raw).prices).repair, 250),
}));
// später: settings.value.repairPrice

AdminPanel.records

Eine Records-Datei, Eintrag für Eintrag geparst. Eine Änderung liest nur den geänderten Eintrag neu.

Signatur
records<T>(file: string, readOne: (key: string, raw: Record<string, unknown>) => T | null): ReadonlyMap<string, T>

Rückgabewert: eine Live-Map vom Eintragsschlüssel zu deinem geparsten Wert. Gibt readOne null zurück, wird ein Eintrag ausgelassen.

const shops = panel.records("shops", (key, raw) =>
  typeof raw.label === "string" ? { key, label: raw.label } : null,
);
for (const shop of shops.values()) console.log(shop.label);

AdminPanel.onChange

Führt deinen Handler aus, nachdem sich eine Datei geändert hat, durch eine Panel-Änderung oder durch dein eigenes commit.

Signatur
onChange(file: string, handler: (key: string | undefined, value: unknown) => void): void

Prop

Type

panel.onChange("shops", (key, value) => {
  if (value === null) console.log(`shop ${key} deleted`);
});

AdminPanel.rule

Eine feldübergreifende Prüfung, die läuft, bevor eine Änderung gespeichert wird. Das Schema prüft einzelne Werte; eine Regel prüft, wie Werte zusammenpassen.

Signatur
rule(file: string, rule: (key: string | undefined, next: Record<string, unknown>) => string | null): void

Rückgabewert (aus deiner Regel): null zum Akzeptieren, oder ein Sprachschlüssel zum Ablehnen (wird dem Admin angezeigt). Eine Regel pro Datei; ein zweiter Aufruf ersetzt sie.

panel.rule("settings", (_key, next) => {
  const prices = next.prices as { min: number; max: number };
  return prices.min > prices.max ? "ERR_MIN_ABOVE_MAX" : null;
});

AdminPanel.onAction

Läuft, wenn ein Admin ein action-Widget drückt (einen Button: senden, zurücksetzen, kicken). Für den Button wird nichts gespeichert.

Signatur
onAction(id: string, handler: (admin: number, record: Record<string, unknown>, key: string | undefined) => string | null): void

Prop

Type

import { notify } from "@aicore-fivem/lib/server";

panel.onAction("testAlert", (admin) => {
  notify(admin, "Test alert sent", "success");
  return null;
});

AdminPanel.commit

Speichert eine Änderung, die dein Skript selbst gemacht hat (Spielzustand, den das Panel anzeigt), ohne Validierung. Eine memory-Datei hält sie nur im Speicher.

Signatur
commit(file: string, key: string | undefined, value: unknown): void

Prop

Type

panel.commit("vaults", "bank_1", { ...panel.get<Record<string, object>>("vaults").bank_1, robbedAt: Date.now() });

AdminPanel.commitAfter

commit, nachdem die aktuelle Änderung das Panel erreicht hat. Nutze es für ein Formular, das sich selbst zurücksetzt, nachdem ein Admin es abgeschickt hat.

Signatur
commitAfter(file: string, key: string | undefined, value: unknown): void
panel.onAction("sendBroadcast", (admin, form) => {
  if (typeof form.message !== "string" || form.message === "") return "ERR_EMPTY_MESSAGE";
  for (const player of getPlayers()) notify(Number(player), form.message, "info", 10_000);
  panel.commitAfter("broadcast", undefined, { message: "" }); // das Formular leeren
  return null;
});

Rufe commit oder commitAfter für dieselbe Datei nie aus ihrem eigenen onChange-Handler auf: Der Commit löst onChange erneut aus, endlos.

AdminPanel.editor

Signatur
editor: number

Die Server-ID des Admins, dessen Änderung oder Aktion gerade angewendet wird, lesbar in rule-, onChange- und onAction-Handlern; 0 für dein eigenes commit. Wird nur von der Lib gesetzt.

panel.onChange("settings", () => {
  if (panel.editor) console.log(`settings changed by ${panel.editor}`);
});

Von ai-core genutzt

snapshot(), dataSnapshot(), summary() und apply(admin, patch) ruft ai-core über die Exports auf, die das Panel registriert (AdminPanelSnapshot, AdminPanelData, AdminPanelSummary, AdminPanelApply). Dein Skript ruft sie nicht auf.

Beispiel

resource/server/content.ts
import { AdminPanel, aiLog } from "@aicore-fivem/lib/server";
import { int, record } from "@aicore-fivem/lib/shared";
import { schema } from "./schema";

export const panel = new AdminPanel(schema);

export const settings = panel.view("settings", (raw) => ({
  repairPrice: int(record(record(raw).prices).repair, 250),
}));

panel.onChange("settings", () => {
  aiLog("admin", `repair price is now ${settings.value.repairPrice}`, { source: panel.editor || undefined });
});

Lies data/*.json nie selbst und schreibe nie hinein: Das Panel besitzt sie. Daten, die die Welt auf Clients braucht (Orte, Blips, Preise, die Spieler sehen), sind eine Datei mit sync: "all", die auf dem Client mit AdminWorld gelesen wird; alles andere bleibt auf dem Server.

DataFile

Eine data/<name>.json-Datei deiner Ressource mit sicheren Schreibvorgängen. AdminPanel nutzt sie für jede Datei; nutze sie direkt nur für Inhalte außerhalb des Panels. Der Server ist der einzige Leser und Schreiber: Er liest einmal beim Start und hält die Wahrheit im Speicher. Ein Schreibvorgang geht in eine temporäre Datei, die dann die echte ersetzt (ein Absturz hinterlässt nie eine halbe Datei), die vorherige Datei wird als .bak behalten, und eine kaputte Datei fällt beim Start auf die .bak zurück.

Seite: Server · Wartet: nein

Kein Lua-Zwilling.

Signatur
new DataFile(resource: string, name: string, log: Logger)

Prop

Type

DataFile.read

Signatur
read(): unknown

Rückgabewert: die geparste Datei ohne ihren $schema-Schlüssel, die .bak, wenn die Datei kaputt ist, oder null, wenn keine von beiden existiert. Parse das Ergebnis, bevor du es nutzt.

DataFile.write

Signatur
write(value: unknown): void

Speichert value; der letzte Aufruf gewinnt. Es wird 250 ms nach dem ersten Aufruf einer Reihe auf die Platte geschrieben, in Reihenfolge mit früheren Schreibvorgängen. Ein fehlgeschlagener Schreibvorgang wird geloggt.

DataFile.schemaRef

Signatur
schemaRef: string | null

Wenn gesetzt (./schemas/<name>.schema.json), wird es als erster Schlüssel "$schema" der Datei geschrieben, nie als Teil der Daten.

DataFile.hasSchemaRef

Signatur
hasSchemaRef(): boolean

Rückgabewert: ob die Datei auf der Platte ihr Schema nennt.

Beispiel

import { DataFile } from "@aicore-fivem/lib/server";
import { createLogger, isRecord } from "@aicore-fivem/lib/shared";

const resource = GetCurrentResourceName();
const file = new DataFile(resource, "routes", createLogger(resource));

const raw = file.read();
const routes: Record<string, unknown> = isRecord(raw) ? raw : {};

function saveRoute(id: string, route: unknown) {
  routes[id] = route;
  file.write(routes);
}

Spielerdaten kommen nie in eine Datendatei: Sie gehören in die Datenbank (Datenbank). Geheimnisse (Webhooks, API-Schlüssel) kommen in die von git ignorierte data/secrets.json.

Auf dieser Seite