Interaktionspunkte
Points - jeder Ort, den ein Spieler nutzt (Theken, Shops, Türen, NPCs, Geldautomaten, Kofferräume), als Prompt oder Target-Option, mit Peds, Blips und Punkten aus Panel-Einträgen.
Ein Punkt ist jeder Ort, den ein Spieler nutzt: eine Ladentheke, eine Tür, ein NPC, ein Rückgabeplatz, jeder Geldautomat, der Kofferraum jedes Autos. Du beschreibst ihn einmal (wo, welcher Text, was passiert), und die Lib zeichnet ihn so, wie der Serverbetreiber es gewählt hat.
Prompt oder Target
Der Betreiber wählt den Interaktionsstil im Admin-Panel von ai-core (Interaction). Dasselbe onPressed läuft in beiden Fällen, daher ist es deinem Skript egal, welcher aktiv ist.
| Stil | Was der Spieler sieht |
|---|---|
| Prompt | Ein „Drücke E“-Feld rechts auf dem Bildschirm, solange der Spieler im Radius des Punkts steht. Die Taste ist ein einziges neu belegbares Keybind in ai-lib (Standard E), daher fragt nichts in jedem Frame Tasten ab. |
| Target | Eine Option im Target-Auge auf der Stelle, dem NPC, dem Objekt oder dem Fahrzeug. Dafür muss eines der Target-Skripte laufen, für die die Lib einen Adapter hat. Wenn mehrere laufen, gewinnt das erste in dieser Reihenfolge: ai-target, tgiann-radialmenu-target, tgiann-target, sleepless_interact, ox_target, qb-target, interact. |
Wenn der Betreiber Target gewählt hat, aber kein Target-Skript läuft, ist jeder Punkt ein Prompt. Ein Punkt kann die Wahl des Betreibers überschreiben: promptOnly behält einen Prompt (Dinge, die aus einem Fahrzeug heraus getan werden: Zapfsäulen, Waschanlagen), preferTarget nutzt eine Target-Option, sobald ein Target-Skript läuft (Sitze). Wenn ein Target-Skript startet oder stoppt oder der Betreiber den Stil wechselt, zieht jeder Punkt von selbst um.
Es läuft immer nur eine Punktaktion deiner Ressource zur Zeit. Wenn onPressed ein Promise zurückgibt, ignorieren die anderen Punkte deiner Ressource Tastendrücke, bis es abgeschlossen ist, daher brauchst du nie ein eigenes busy-Flag. Der Spieler muss außerdem im Moment des Drückens canShow bestehen.
new Points
Erstellt eine Gruppe von Punkten. Erstelle eine Gruppe pro Feature am Anfang der Datei und füge ihr Punkte hinzu.
Seite: Client · Wartet: nein
new Points()local points = aiLib.Points.new()Rückgabewert
Eine Points-Gruppe mit den Methoden unten.
Beispiel
import { Points } from "@aicore-fivem/lib/client";
const points = new Points();points.add
Fügt einen Punkt an einem Ort hinzu oder ersetzt den Punkt mit derselben ID. Ein Punkt kann seinen eigenen NPC (ped) und Karten-Blip (blip) mitbringen.
Seite: Client · Wartet: nein
points.add(id: string, options: PointOptions): voidpoints:add(id, options)Parameter
Prop
Type
PointOptions
Prop
Type
PointPed
Das Feld ped nimmt die Optionen von LocalPed außer onSpawn und onDespawn (die der Punkt nutzt):
Prop
Type
PointBlip
Das Feld blip nimmt die Blip-Optionen; coords ist standardmäßig der Punkt:
Prop
Type
PromptAnchor
Was eine world-Funktion zurückgibt (PromptAnchor), eines von:
Prop
Type
Rückgabewert
void.
Beispiel
import { callAction, createLocale, hasJob, onMembershipChange, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
// Eine Ladentheke mit Ladenbesitzer und Karten-Blip.
points.add("shop", {
coords: { x: 25.7, y: -1347.3, z: 29.5 },
label: Locale("OPEN_SHOP"),
icon: "basket-shopping",
ped: { model: "mp_m_shopkeep_01", coords: { x: 24.5, y: -1346.6, z: 29.5, heading: 270 } },
blip: { sprite: 52, color: 2, label: Locale("SHOP") },
onPressed: () => callAction("myshop:open", [], { fallback: "SHOP_FAILED" }),
});
// Ein Lager, das nur die Polizei öffnen kann; der Text folgt einem Zustand.
let locked = true;
points.add("evidence", {
coords: { x: 474.9, y: -996.4, z: 26.3 },
label: () => (locked ? Locale("UNLOCK") : Locale("LOCK")),
icon: "box-archive",
canShow: () => hasJob(["police"], 2),
onPressed: async () => {
const reply = await callAction("myscript:toggle", [], { fallback: "TOGGLE_FAILED" });
if (reply) locked = !locked;
points.refresh("evidence");
},
});
// canShow liest den Job: erneut prüfen, wenn sich Jobs, Dienst oder Gangs ändern.
onMembershipChange(() => points.refresh());canShow entscheidet nur, was der Spieler sieht. Der Server muss in seinem eigenen Handler erneut prüfen (Job, Entfernung, Bestand). Ein Cheater kann dein Server-Event ohne jeden Punkt aufrufen.
points.addModel
Fügt einen Punkt auf jedem Weltobjekt bestimmter Modelle hinzu (Geldautomaten, Verkaufsautomaten, Müllcontainer, Stühle) oder ersetzt den Punkt mit derselben ID. Im Target-Modus ist es eine Target-Option auf diesen Modellen; im Prompt-Modus erscheint ein Prompt in der Nähe des nächsten. Die Suche nach dem nächsten Objekt läuft einmal in ai-lib für jede Ressource, zweimal pro Sekunde, und pausiert, solange der Spieler stillsteht.
Seite: Client · Wartet: nein
points.addModel(id: string, options: ModelPointOptions): voidpoints:addModel(id, options)Parameter
Prop
Type
ModelPointOptions
Prop
Type
Rückgabewert
void.
Beispiel
import { callAction, createLocale, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
points.addModel("atm", {
models: ["prop_atm_01", "prop_atm_02", "prop_atm_03", "prop_fleeca_atm"],
label: Locale("USE_ATM"),
icon: "credit-card",
onPressed: (atm) => callAction("mybank:atm", [NetworkGetNetworkIdFromEntity(atm)], { fallback: "ATM_FAILED" }),
});Zwei Zustände eines Objekts (ein Boot festmachen und losmachen) sind zwei Modellpunkte mit gegensätzlichem canShow, nicht eine Label-Funktion. Ein Punkt mit mehr als 6 Modellen findet seine Objekte über einen Index des Objekt-Pools, daher kosten Hunderte von Modellen so viel wie ein paar.
points.addVehicle
Fügt einen Punkt auf jedem vernetzten Fahrzeug hinzu (Kofferraum, Motorhaube, schieben, reparieren) oder ersetzt den Punkt mit derselben ID. Im Target-Modus ist es die globale Fahrzeugoption des Target-Skripts; im Prompt-Modus erscheint ein Prompt am nächsten vernetzten Fahrzeug innerhalb von radius.
Seite: Client · Wartet: nein
points.addVehicle(id: string, options: VehiclePointOptions): voidpoints:addVehicle(id, options)Parameter
Prop
Type
Rückgabewert
void.
Beispiel
import { cache, createLocale, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
points.addVehicle("trunk", {
label: Locale("OPEN_TRUNK"),
icon: "box-open",
radius: 2.5,
canShow: (vehicle) => vehicle !== 0 && cache.vehicle === false && GetVehicleDoorLockStatus(vehicle) < 2,
onPressed: (vehicle) => SetVehicleDoorOpen(vehicle, 5, false, false),
});points.remove
Entfernt einen Punkt mit seinem Prompt, seiner Target-Option, seinem NPC und Blip. Funktioniert für Punkte, die mit add, addModel und addVehicle erstellt wurden. Eine unbekannte ID tut nichts.
Seite: Client · Wartet: nein
points.remove(id: string): voidpoints:remove(id)Parameter
Prop
Type
Rückgabewert
void.
Beispiel
points.remove("shop");points.clear
Entfernt jeden Punkt der Gruppe.
Seite: Client · Wartet: nein
points.clear(): voidpoints:clear()Rückgabewert
void.
Beispiel
onServerEvent("myscript:jobEnded", () => points.clear());Baue Panel-Punkte nie bei jeder Panel-Änderung mit clear() + alles neu hinzufügen auf: Bei Tausenden von Einträgen würde eine Änderung auf jedem Client alle neu aufbauen. Nutze RecordObjects: Eine Änderung baut nur ihren eigenen Eintrag neu auf.
points.refresh
Liest canShow und Label-Funktionen erneut. Rufe es auf, wenn sich etwas geändert hat, das sie lesen: der Job, der Dienst, ein Fahrzeug, ein Zustand, den der Server gesendet hat. Prompts aktualisieren sich sofort; Target-Optionen lesen canShow, wenn das Auge öffnet, und ein geändertes Label wird neu gesetzt.
Seite: Client · Wartet: nein
points.refresh(id?: string): voidpoints:refresh() -- jeder Punkt der GruppeParameter
Prop
Type
Rückgabewert
void.
Beispiel
import { EntityState, onCache, onMembershipChange, Points, stateBoolean } from "@aicore-fivem/lib/client";
const points = new Points();
const robbed = new EntityState("robbed", stateBoolean);
onCache("vehicle", () => points.refresh());
onMembershipChange(() => points.refresh());
// Eine Kasse hat sich geändert: nur ihren Punkt aktualisieren.
robbed.onChange((netId) => points.refresh(`register:${netId}`));In einem rohen AddStateBagChangeHandler ist der neue Wert noch nicht lesbar: Rufe refresh dort in einem setTimeout(..., 0) auf. Die State-Klassen der Lib (EntityState.onChange) geben dir den neuen Wert bereits.
points.isInside
Ob der Spieler gerade im Radius eines Punkts steht (Punkte, die mit add erstellt wurden).
Seite: Client · Wartet: nein
points.isInside(id: string): booleanKein Lua-Zwilling.
Parameter
Prop
Type
Rückgabewert
boolean: true, solange der Spieler drin ist; false draußen, oder bei einer unbekannten ID oder einem Modellpunkt.
Beispiel
if (points.isInside("garage")) notify(Locale("PARK_HERE"), "info");points.restyle
Stellt jeden Punkt der Gruppe auf den aktuellen Interaktionsstil um. Die Lib ruft es selbst auf, wenn der Betreiber den Stil wechselt oder ein Target-Skript startet oder stoppt; du brauchst es nie.
Seite: Client · Wartet: nein
points.restyle(): voidKein Lua-Zwilling (Lua stellt ebenfalls von selbst um).
Rückgabewert
void.
Punkte aus Panel-Einträgen
Die meisten Punkte kommen aus einer Records-Datei des Admin-Panels deines Skripts (Shops, Garagen, Mietschalter). Spiegle die Datei auf dem Client mit AdminWorld und baue dann die Punkte jedes Eintrags mit RecordObjects: Ein bearbeiteter Eintrag baut nur seine eigenen Punkte neu auf, ein gelöschter entfernt sie.
new RecordObjects
Hält die Weltobjekte einer Records-Datei (Punkte, Blips, Peds, Zonen) Eintrag für Eintrag im Gleichschritt mit der Datei. build(key, record) erstellt die Objekte eines Eintrags und gibt zurück, wie sie entfernt werden. Es baut jeden Eintrag in seinem Konstruktor.
Seite: Client und Server (aus @aicore-fivem/lib/shared) · Wartet: nein
new RecordObjects<T>(
source: RecordSource,
file: string,
records: RecordReader<T> | ReadonlyMap<string, T>,
build: (key: string, record: T) => (() => void) | void,
)Kein Lua-Zwilling.
Parameter
Prop
Type
Mitglieder
Prop
Type
Beispiel
import { AdminWorld, callAction, createLocale, Points } from "@aicore-fivem/lib/client";
import { readVec3, RecordObjects, str } from "@aicore-fivem/lib/shared";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const world = new AdminWorld(["settings", "shops"]);
const points = new Points();
interface Shop { label: string; coords: { x: number; y: number; z: number } }
const readShop = (_key: string, raw: Record<string, unknown>): Shop | null => {
const coords = readVec3(raw.coords);
return coords ? { label: str(raw.label, "Shop"), coords } : null;
};
const shops = new RecordObjects(world, "shops", readShop, (id, shop) => {
points.add(id, {
coords: shop.coords,
label: Locale("OPEN_SHOP", shop.label),
onPressed: () => callAction("myshop:open", [id], { fallback: "SHOP_FAILED" }),
});
return () => points.remove(id);
});
// Eine Einstellung, die jeder Shop liest, hat sich geändert: alle neu bauen.
world.onChange("settings", () => shops.rebuildAll());RecordObjects baut in seinem Konstruktor. Erstelle es nach allem, was build liest, und nutze in build das Argument record: Ein Klassenfeld, das das RecordObjects hält, ist während dieses ersten Aufbaus noch nicht zugewiesen.
new AdminWorld
Die Client-Kopie der Welt-Panel-Dateien deines Skripts (die Dateien, deren Schema sync: "all" sagt, siehe Admin-Panel). Sie liest die Kopie, die FiveM beim Start gecacht hat, fragt den Server einmal nach den seitdem geänderten Einträgen und folgt dann Live-Änderungen. Sie fragt nie zyklisch ab.
Seite: Client · Wartet: nein (das ready-Promise sagt, wann die Änderungen seit dem Start angekommen sind)
new AdminWorld(files: string[])Kein Lua-Zwilling.
Parameter
Prop
Type
Mitglieder
Prop
Type
Beispiel
import { AdminWorld } from "@aicore-fivem/lib/client";
import { num, record } from "@aicore-fivem/lib/shared";
const world = new AdminWorld(["settings"]);
const settings = world.view("settings", (raw) => ({
radius: num(record(raw).radius, 2),
}));
world.onChange("settings", () => console.log("new radius", settings.value.radius));Der lokale Spieler
Der gecachte Ped, Fahrzeug, Sitz und Waffe, das player-Objekt (Position, Teleport, Animationen), Bildschirm-Überblendungen, die Peds anderer Spieler und der nächstgelegene Spieler.
Zonen
Zones - Bereiche mit Betreten- und Verlassen-Callbacks (Kugel, Zylinder, Polygon), in ihrem eigenen Intervall geprüft, nie in jedem Frame.