Admin paneli
AdminPanel ve DataFile: script’inin ayarlarını ai-core’un admin paneline koy, onları oku, düzenlemelere tepki ver ve panel içeriğini data/*.json dosyalarına kaydet.
import { AdminPanel, DataFile } from "@aicore-fivem/lib/server";Her ai-* script’i, sunucu sahibinin ayarladığı ayarları bir config dosyasında değil, ai-core’un admin panelinde tutar. Veriyi ve formu resource/server/schema.ts içinde tanımlarsın (PanelSchema: dosyalar, sekmeler, kartlar, widget’lar; bkz. Script oluşturmak), ondan tek bir AdminPanel oluşturursun ve değerleri onun üzerinden okursun. Sahibin tarafı (paneli açmak, düzenlemek, yetkiler) Admin paneli sayfasındadır.
Lua karşılığı yok: admin paneli yalnızca TypeScript’tir.
AdminPanel
Resource’unun panel içeriği. Her data/<file>.json dosyasını açılışta bir kez yükler, bellekte tutar, her panel düzenlemesini şemaya göre kontrol eder, dosyayı kaydeder (atomik, art arda gelen düzenlemeler tek seferde yazılır) ve senin kodunu, açık panelleri ve sync: "all" dosyaları için her client’ı haberdar eder. Ayrıca bir editörün elle yapılan düzenlemeyi kontrol edebilmesi için data/schemas/<file>.schema.json dosyasını yazar.
Taraf: Server · Bekler: hayır
new AdminPanel(schema: PanelSchema)Prop
Type
Tam olarak bir tane oluştur, modül seviyesinde, genellikle resource/server/content.ts içinde: export const panel = new AdminPanel(schema);. Ayar dosyaları yüklenirken eksik bölümlerini defaults’tan alır. ai-core paneli oluşturulduktan bir tick sonra listeler.
AdminPanel.get
Bir dosyanın güncel içeriği.
get<T = Record<string, unknown>>(file: string): TDönüş değeri: ayar nesnesi (bir settings dosyası) ya da { key: record } (bir records dosyası); bilinmeyen bir dosya için {}. Salt okunur: dönen nesneyi asla değiştirme, commit kullan.
const settings = panel.get<{ prices: { repair: number } }>("settings");AdminPanel.view
Senin okuyucunla ayrıştırılan ve her değişiklikte güncel tutulan bir ayar dosyası (onChange handler’ları çalışmadan önce ayrıştırılır).
view<T>(file: string, read: (raw: unknown) => T): { readonly value: T }Dönüş değeri: value’su her zaman en son ayrıştırılmış içerik olan bir nesne. .value’yu her seferinde oku; bir kez kopyalayıp saklama.
import { int, record } from "@aicore-fivem/lib/shared";
const settings = panel.view("settings", (raw) => ({
repairPrice: int(record(record(raw).prices).repair, 250),
}));
// daha sonra: settings.value.repairPriceAdminPanel.records
Kayıt kayıt ayrıştırılan bir records dosyası. Bir değişiklik yalnızca değişen kaydı yeniden okur.
records<T>(file: string, readOne: (key: string, raw: Record<string, unknown>) => T | null): ReadonlyMap<string, T>Dönüş değeri: kayıt anahtarından ayrıştırılmış değerine canlı bir map. readOne null döndürürse kayıt dışarıda kalır.
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
Bir dosya, bir panel düzenlemesiyle ya da senin kendi commit’inle değiştikten sonra handler’ını çalıştırır.
onChange(file: string, handler: (key: string | undefined, value: unknown) => void): voidProp
Type
panel.onChange("shops", (key, value) => {
if (value === null) console.log(`shop ${key} deleted`);
});AdminPanel.rule
Bir düzenleme saklanmadan önce çalışan, alanlar arası bir kontrol. Şema tek tek değerleri kontrol eder; bir kural değerlerin birbirine nasıl uyduğunu kontrol eder.
rule(file: string, rule: (key: string | undefined, next: Record<string, unknown>) => string | null): voidDönüş değeri (kuralından): kabul etmek için null ya da reddetmek için bir lang anahtarı (admin’e gösterilir). Dosya başına bir kural; ikinci bir çağrı onun yerine geçer.
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
Bir admin bir action widget’ına (bir buton: gönder, sıfırla, at) bastığında çalışır. Buton için hiçbir şey saklanmaz.
onAction(id: string, handler: (admin: number, record: Record<string, unknown>, key: string | undefined) => string | null): voidProp
Type
import { notify } from "@aicore-fivem/lib/server";
panel.onAction("testAlert", (admin) => {
notify(admin, "Test alert sent", "success");
return null;
});AdminPanel.commit
Script’inin kendisinin yaptığı bir değişikliği (panelin gösterdiği oyun durumu) doğrulama olmadan saklar. Bir memory dosyası onu yalnızca bellekte tutar.
commit(file: string, key: string | undefined, value: unknown): voidProp
Type
panel.commit("vaults", "bank_1", { ...panel.get<Record<string, object>>("vaults").bank_1, robbedAt: Date.now() });AdminPanel.commitAfter
Güncel değişiklik panele ulaştıktan sonra commit. Bir admin gönderdikten sonra kendini sıfırlayan bir form için kullan.
commitAfter(file: string, key: string | undefined, value: unknown): voidpanel.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: "" }); // formu boşalt
return null;
});Aynı dosya için commit ya da commitAfter’ı asla o dosyanın kendi onChange handler’ı içinden çağırma: commit onChange’i yeniden tetikler, sonsuza kadar.
AdminPanel.editor
editor: numberDüzenlemesi ya da aksiyonu uygulanan admin’in server id’si; rule, onChange ve onAction handler’ları içinde okunabilir; senin kendi commit’in için 0. Yalnızca lib tarafından ayarlanır.
panel.onChange("settings", () => {
if (panel.editor) console.log(`settings changed by ${panel.editor}`);
});ai-core’un kullandıkları
snapshot(), dataSnapshot(), summary() ve apply(admin, patch), ai-core’un panelin kaydettiği export’lar (AdminPanelSnapshot, AdminPanelData, AdminPanelSummary, AdminPanelApply) üzerinden çağırdıklarıdır. Script’in onları çağırmaz.
Örnek
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 });
});data/*.json dosyasını asla kendin okuma ve ona asla yazma: dosyanın sahibi paneldir. Dünyanın client’larda ihtiyaç duyduğu veriler (yerler, blip’ler, oyuncuların gördüğü fiyatlar) client’ta AdminWorld ile okunan bir sync: "all" dosyasıdır; geri kalan her şey server’da kalır.
DataFile
Resource’unun güvenli yazmalı tek bir data/<name>.json dosyası. AdminPanel her dosya için onu kullanır; onu doğrudan yalnızca panel dışındaki içerik için kullan. Tek okuyan ve yazan server’dır: açılışta bir kez okur ve doğruyu bellekte tutar. Bir yazma, sonra gerçek dosyanın yerine geçen geçici bir dosyaya gider (bir çökme asla yarım dosya bırakmaz), önceki dosya .bak olarak saklanır ve bozuk bir dosya açılışta .bak’a geri döner.
Taraf: Server · Bekler: hayır
Lua karşılığı yok.
new DataFile(resource: string, name: string, log: Logger)Prop
Type
DataFile.read
read(): unknownDönüş değeri: $schema anahtarı olmadan ayrıştırılmış dosya, dosya bozuksa .bak ya da ikisi de yoksa null. Sonucu kullanmadan önce ayrıştır.
DataFile.write
write(value: unknown): voidvalue’yu saklar; son çağrı kazanır. Art arda gelen çağrıların ilkinden 250 ms sonra, önceki yazmalarla sıralı olarak diske yazılır. Başarısız bir yazma loglanır.
DataFile.schemaRef
schemaRef: string | nullAyarlandığında (./schemas/<name>.schema.json), dosyanın ilk anahtarı olarak "$schema" şeklinde yazılır; asla verinin parçası değildir.
DataFile.hasSchemaRef
hasSchemaRef(): booleanDönüş değeri: diskteki dosyanın şemasını belirtip belirtmediği.
Örnek
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);
}Oyuncu verisi asla bir data dosyasına girmez: veritabanına aittir (Veritabanı). Sırlar (webhook’lar, API anahtarları) git’e girmeyen data/secrets.json dosyasına gider.
Alan yayını
AreaStream: dünya nesneleri (yere atılan eşyalar, bitkiler, cesetler, prop’lar) yalnızca yakınındaki oyunculara, bir snapshot ve ardından yalnızca değişiklikler olarak gönderilir.
Veritabanı
createDatabaseTables açılışta sql/main.sql dosyanı kurar; WriteQueue veritabanı yazmalarını birbiri ardına çalıştırır.