Точки взаимодействия
Points - каждое место, которым пользуется игрок (прилавки, магазины, двери, NPC, банкоматы, багажники транспорта), в виде подсказки или опции target, с педами, блипами и точками, построенными из записей панели.
Точка это любое место, которым пользуется игрок: прилавок магазина, дверь, NPC, место возврата, каждый банкомат, багажник каждой машины. Вы описываете её один раз (где, какой текст, что происходит), а lib рисует её так, как выбрал владелец сервера.
Prompt или target
Владелец выбирает стиль взаимодействия в админ-панели ai-core (Interaction). В обоих случаях выполняется один и тот же onPressed, поэтому вашему скрипту неважно, какой стиль включён.
| Стиль | Что видит игрок |
|---|---|
| Prompt | Окно «нажмите E» справа на экране, пока игрок стоит внутри радиуса точки. Клавиша это одна переназначаемая клавиша в ai-lib (по умолчанию E), поэтому ничто не опрашивает клавиши каждый кадр. |
| Target | Опция в глазе target на месте, NPC, объекте или транспорте. Для этого должен быть запущен один из target-скриптов, для которых в lib есть адаптер. Если запущено несколько, побеждает первый в таком порядке: ai-target, tgiann-radialmenu-target, tgiann-target, sleepless_interact, ox_target, qb-target, interact. |
Если владелец выбрал target, но ни один target-скрипт не запущен, каждая точка становится подсказкой (prompt). Точка может переопределить выбор владельца: promptOnly оставляет подсказку (действия из транспорта: топливные колонки, автомойки), preferTarget использует опцию target всегда, когда запущен target-скрипт (сиденья). Когда target-скрипт запускается или останавливается или владелец переключает стиль, каждая точка переходит сама.
Одновременно выполняется только одно действие точки вашего ресурса. Когда onPressed возвращает promise, остальные точки вашего ресурса игнорируют нажатия, пока он не завершится, поэтому собственный флаг busy вам никогда не нужен. Кроме того, в момент нажатия игрок должен проходить canShow.
new Points
Создаёт группу точек. Создайте одну группу на функцию в начале файла и добавляйте в неё точки.
Сторона: Клиент · Ждёт: нет
new Points()local points = aiLib.Points.new()Возвращает
Группа Points с методами ниже.
Пример
import { Points } from "@aicore-fivem/lib/client";
const points = new Points();points.add
Добавляет точку в месте или заменяет точку с тем же id. Точка может иметь собственного NPC (ped) и блип на карте (blip).
Сторона: Клиент · Ждёт: нет
points.add(id: string, options: PointOptions): voidpoints:add(id, options)Параметры
Prop
Type
PointOptions
Prop
Type
PointPed
Поле ped принимает опции LocalPed, кроме onSpawn и onDespawn (их использует точка):
Prop
Type
PointBlip
Поле blip принимает опции блипа; coords по умолчанию равен точке:
Prop
Type
PromptAnchor
Что возвращает функция world (PromptAnchor), одно из:
Prop
Type
Возвращает
void.
Пример
import { callAction, createLocale, hasJob, onMembershipChange, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
// Прилавок магазина с продавцом и блипом на карте.
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" }),
});
// Хранилище, которое может открыть только полиция; текст следует состоянию.
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 читает работу: проверяем снова, когда меняются работы, дежурство или банды.
onMembershipChange(() => points.refresh());canShow решает только то, что видит игрок. Сервер должен снова проверить (работу, расстояние, запасы) в своём обработчике. Читер может вызвать ваше серверное событие без всякой точки.
points.addModel
Добавляет точку на каждый объект мира с определёнными моделями (банкоматы, торговые автоматы, мусорные контейнеры, стулья) или заменяет точку с тем же id. В режиме target это опция target на этих моделях; в режиме prompt подсказка показывается рядом с ближайшим объектом. Поиск ближайшего объекта выполняется один раз в ai-lib для всех ресурсов, два раза в секунду, и приостанавливается, пока игрок стоит на месте.
Сторона: Клиент · Ждёт: нет
points.addModel(id: string, options: ModelPointOptions): voidpoints:addModel(id, options)Параметры
Prop
Type
ModelPointOptions
Prop
Type
Возвращает
void.
Пример
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" }),
});Два состояния одного объекта (привязать и отвязать лодку) это две точки моделей с противоположным canShow, а не одна функция подписи. Точка с более чем 6 моделями находит свои объекты через индекс пула объектов, поэтому сотни моделей стоят столько же, сколько несколько.
points.addVehicle
Добавляет точку на каждый сетевой транспорт (багажник, капот, толкать, ремонт) или заменяет точку с тем же id. В режиме target это глобальная опция транспорта target-скрипта; в режиме prompt подсказка показывается у ближайшего сетевого транспорта в пределах radius.
Сторона: Клиент · Ждёт: нет
points.addVehicle(id: string, options: VehiclePointOptions): voidpoints:addVehicle(id, options)Параметры
Prop
Type
Возвращает
void.
Пример
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
Удаляет одну точку вместе с её подсказкой, опцией target, NPC и блипом. Работает для точек, созданных через add, addModel и addVehicle. Неизвестный id ничего не делает.
Сторона: Клиент · Ждёт: нет
points.remove(id: string): voidpoints:remove(id)Параметры
Prop
Type
Возвращает
void.
Пример
points.remove("shop");points.clear
Удаляет все точки группы.
Сторона: Клиент · Ждёт: нет
points.clear(): voidpoints:clear()Возвращает
void.
Пример
onServerEvent("myscript:jobEnded", () => points.clear());Никогда не перестраивайте точки панели через clear() + добавление всего при каждом изменении панели: при тысячах записей одна правка перестраивала бы их все на каждом клиенте. Используйте RecordObjects: правка перестраивает только свою запись.
points.refresh
Заново читает canShow и функции подписей. Вызывайте, когда изменилось то, что они читают: работа, дежурство, транспорт, состояние, которое прислал сервер. Подсказки обновляются сразу; опции target читают canShow при открытии глаза, а изменённая подпись ставится заново.
Сторона: Клиент · Ждёт: нет
points.refresh(id?: string): voidpoints:refresh() -- каждая точка группыПараметры
Prop
Type
Возвращает
void.
Пример
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());
// Изменилась одна касса: обновляем только её точку.
robbed.onChange((netId) => points.refresh(`register:${netId}`));Внутри голого AddStateBagChangeHandler новое значение ещё не читается: вызывайте там refresh в setTimeout(..., 0). Классы состояния lib (EntityState.onChange) уже дают вам новое значение.
points.isInside
Стоит ли игрок сейчас внутри радиуса точки (точки, созданные через add).
Сторона: Клиент · Ждёт: нет
points.isInside(id: string): booleanLua-аналога нет.
Параметры
Prop
Type
Возвращает
boolean: true, пока игрок внутри; false снаружи, а также для неизвестного id или точки модели.
Пример
if (points.isInside("garage")) notify(Locale("PARK_HERE"), "info");points.restyle
Переводит все точки группы на текущий стиль взаимодействия. Lib вызывает это сама, когда владелец переключает стиль или target-скрипт запускается или останавливается; вам это никогда не нужно.
Сторона: Клиент · Ждёт: нет
points.restyle(): voidLua-аналога нет (Lua тоже меняет стиль сам).
Возвращает
void.
Точки из записей панели
Большинство точек берутся из файла записей админ-панели вашего скрипта (магазины, гаражи, стойки аренды). Зеркалируйте файл на клиенте через AdminWorld, затем стройте точки каждой записи через RecordObjects: изменённая запись перестраивает только свои точки, удалённая их убирает.
new RecordObjects
Поддерживает объекты мира из файла записей (точки, блипы, педы, зоны) в соответствии с файлом, по одной записи за раз. build(key, record) создаёт объекты одной записи и возвращает способ их удалить. Он строит все записи в своём конструкторе.
Сторона: Клиент и сервер (из @aicore-fivem/lib/shared) · Ждёт: нет
new RecordObjects<T>(
source: RecordSource,
file: string,
records: RecordReader<T> | ReadonlyMap<string, T>,
build: (key: string, record: T) => (() => void) | void,
)Lua-аналога нет.
Параметры
Prop
Type
Члены
Prop
Type
Пример
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);
});
// Изменилась настройка, которую читает каждый магазин: перестраиваем все.
world.onChange("settings", () => shops.rebuildAll());RecordObjects строит в своём конструкторе. Создавайте его после всего, что читает build, а внутри build используйте аргумент record: поле класса, которое хранит RecordObjects, во время этой первой постройки ещё не присвоено.
new AdminWorld
Клиентская копия мировых файлов панели вашего скрипта (файлов, в схеме которых указано sync: "all", см. Админ-панель). Она читает копию, которую FiveM закэшировал при старте, один раз спрашивает сервер о записях, изменённых с тех пор, а затем следует живым изменениям. Никогда не опрашивает.
Сторона: Клиент · Ждёт: нет (promise ready сообщает, когда пришли изменения с момента старта)
new AdminWorld(files: string[])Lua-аналога нет.
Параметры
Prop
Type
Члены
Prop
Type
Пример
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));Локальный игрок
Кэшированные пед, транспорт, место и оружие, объект player (позиция, телепорт, анимации), затемнения экрана, педы других игроков и ближайший игрок.
Зоны
Zones - области с колбэками входа и выхода (сфера, цилиндр, полигон), которые проверяются со своим интервалом и никогда не каждый кадр.