API
SL-Interact se usa desde tus scripts con exports de cliente. Cada interacción es del recurso que la dio de alta y se borra sola cuando ese recurso se para.
Los ejemplos salen del recurso de ejemplo que trae SL-Interact (sl_interact_demo, junto a sl_interact en la descarga). Su código está abierto: ponlo en tu carpeta resources, arráncalo y escribe /slidemo para que aparezcan todos los ejemplos delante de ti.
Para empezar
Sección titulada «Para empezar»local SI = exports.sl_interact
local id = SI:addCoords(vec3(147.6, -1035.8, 29.3), { label = 'Cajero', icon = 'cash', options = { { key = 'E', label = 'Usar', onSelect = function(ctx) AbrirCajero() end }, { key = 'G', label = 'Ver saldo', serverEvent = 'banco:server:saldo' }, },})
-- Más tarde: cambiarla o quitarlaSI:update(id, { status = { ok = false, label = 'Fuera de servicio' } })SI:remove(id)Dar de alta
Sección titulada «Dar de alta»Cada función devuelve un id (un número) para update y remove, o nil si la interacción venía mal. El motivo sale en la consola F8 con el nombre de tu recurso.
| Export | Dónde va la interacción |
|---|---|
addCoords(coords, def) |
Un punto fijo del mundo (vector3) |
addLocalEntity(entity, def) |
Un objeto, NPC o vehículo creado por este cliente (handle) |
addEntity(netId, def) |
Una entidad en red. Sale cuando existe cerca, aunque se cree después |
addModel(models, def) |
Todos los objetos, NPC o vehículos de uno o varios modelos (nombre o hash) |
addGlobalVehicle(def) |
Todos los vehículos |
addGlobalPed(def) |
Todos los NPC |
addGlobalObject(def) |
Todos los objetos |
addGlobalPlayer(def) |
Todos los demás jugadores |
addMany(list) |
Muchas a la vez (ver muchas a la vez) |
Sin bones, una interacción en una entidad es la entidad entera: el jugador puede apuntar a cualquier parte, y la distancia se mide hasta su cuerpo. Con bones, es solo ese sitio. Las dos caben en el mismo coche: el maletero al apuntar atrás, el menú del coche en cualquier otra parte.
-- Todos los NPC de este modeloSI:addModel('a_m_y_business_01', { label = 'Empresario', icon = 'talk', reveal = 'look', options = { ... } })
-- El maletero de todos los coches, solo al apuntar atrásSI:addGlobalVehicle({ label = 'Maletero', icon = 'box', bones = 'boot', options = { { key = 'E', label = 'Abrir / cerrar', onSelect = function(ctx) Maletero(ctx.entity) end } },})Campos de una interacción
Sección titulada «Campos de una interacción»| Campo | Tipo | Descripción |
|---|---|---|
label |
texto | Nombre en la etiqueta |
icon |
texto | Icono del punto: uno de serie o uno registrado con registerIcon |
options |
lista | Lo que puede hacer el jugador (ver opciones) |
distance |
número | Metros hasta los que sale el punto. De serie: Config.Points.distance (2.0) |
offset |
vector3 |
Dónde va el punto respecto a la entidad. De serie: el centro de su modelo |
bones |
texto o lista | Huesos de la entidad, un punto por hueso ('boot', 'bonnet', 'door_dside_f'…) |
reveal |
texto | Cuándo sale el punto: 'always' (al acercarse), 'look' (solo al mirarlo) o 'key' (solo manteniendo la tecla de mostrar, Alt) |
easy |
booleano | Se enfoca con solo mirar hacia él, sin apuntar al punto |
aimBoost |
número | Hace más fácil enfocarlo: 2 cuenta como el doble de cerca de la mira |
highlight |
texto o false |
'outline', 'box', 'both' o false. El contorno del juego solo se dibuja en objetos |
grow |
booleano | El objeto crece un poco mientras se mira |
moving |
booleano | La entidad se mueve: su punto se recalcula en cada fotograma. NPC y vehículos ya lo hacen |
inVehicle |
booleano | Sale también desde dentro de un vehículo (de serie, desde un vehículo no sale nada) |
badge |
texto | Texto corto junto al nombre: un precio, una cantidad… |
status |
tabla | Chapa junto al nombre: { ok = false, label = 'Fuera de servicio' }. Verde o roja |
disabled |
true o texto |
La interacción entera está desactivada. Con un texto, sus teclas se ponen rojas con ese motivo |
data |
lo que sea | Lo que quieras recibir en ctx.data |
Huesos de los vehículos
Sección titulada «Huesos de los vehículos»| Hueso | Dónde va el punto |
|---|---|
door_dside_f, door_pside_f, door_dside_r, door_pside_r |
La maneta de esa puerta. Se mueve con la puerta al abrirla |
boot |
El borde del maletero. Sube con la tapa al abrirlo |
bonnet |
El borde del capó. Sube con él al abrirlo |
| Cualquier otro hueso | El propio hueso (petroltank, wheel_lf…) |
Opciones
Sección titulada «Opciones»Cada opción es una tecla de la etiqueta, o una línea del menú.
| Campo | Tipo | Descripción |
|---|---|---|
label |
texto | Texto de la opción (obligatorio) |
key |
texto | Una de Config.Keys ('E', 'G', 'H'). Sin ella, la opción coge la primera tecla libre |
onSelect |
función | Se ejecuta al pulsar la tecla: function(ctx) ... end |
event |
texto | Evento de cliente que se lanza con ctx |
serverEvent |
texto | Evento de servidor que se lanza con ctx (sin el handle de la entidad, con netId si está en red) |
canInteract |
función | function(ctx) return true end, o return false, 'motivo' (ver condiciones) |
groups |
texto o tabla | Trabajos o bandas que pueden usarla: 'police', { 'police', 'ambulance' } o { police = 2 } (grado mínimo) |
items |
texto o tabla | Items que tiene que llevar: 'lockpick', { 'lockpick', 'gloves' } o { water = 2 } (cantidad) |
disabled |
true o texto |
No se puede usar. Con un texto, la tecla se pone roja con ese motivo al pulsarla |
showDisabled |
booleano | Si falla una condición, la tecla sale apagada en vez de esconderse |
hold |
número | Milisegundos que hay que mantener la tecla. La tecla se llena mientras tanto |
confirm |
true o texto |
La primera pulsación pregunta ('¿Venderlo? Pulsa otra vez'), la segunda lo hace |
options |
lista | Un submenú |
quantity |
true o tabla |
Elegir antes una cantidad con la rueda: { min = 1, max = 10, step = 1, default = 1 }. Llega en ctx.quantity |
price |
número | Precio junto a la opción. Con quantity, el total sale mientras se elige |
description |
texto | Texto de ayuda debajo de la opción, en los menús |
badge |
texto | Texto corto junto a la opción |
image |
texto o lista | Imagen de la opción: un archivo de tu recurso, nui://, https:// o data:image/... |
itemImage |
texto o lista | Imagen de un item del inventario del servidor, por su nombre |
Una opción necesita onSelect, event, serverEvent u options; si no, no hace nada.
Los archivos de tu recurso (iconos e imágenes) tienen que estar en files { } de tu fxmanifest.lua.
onSelect, canInteract y event reciben una tabla ctx:
| Campo | Descripción |
|---|---|
id |
Id de la interacción |
key |
Tecla u opción del menú que se usó |
entity |
Handle de la entidad (solo en entidades) |
netId |
Id de red, si la entidad está en red |
coords |
Posición de la entidad o del punto |
distance |
Distancia al jugador |
model |
Modelo de la entidad |
bone |
Hueso del punto, si tiene |
quantity |
Cantidad elegida, en opciones con quantity |
data |
El data que se dio al dar de alta |
Un evento de servidor recibe la misma tabla sin entity ni distance:
RegisterNetEvent('tienda:server:comprar', function(ctx) local src = source -- ctx.netId, ctx.coords, ctx.quantity, ctx.data...end)Cuando una interacción tiene más opciones que teclas, o alguna opción tiene options (submenú) o quantity, la etiqueta pasa a ser un menú. La rueda del ratón elige, la primera tecla (E) usa la elegida, y la segunda (G) o Retroceso vuelven atrás. Las teclas puestas con key no cuentan dentro de un menú.
SI:addModel('prop_vend_soda_01', { label = 'Máquina', icon = 'product', options = { { label = 'Bebidas', description = 'Bebidas frías.', options = { { label = 'Agua', price = 2, itemImage = 'water', quantity = { min = 1, max = 5 }, serverEvent = 'maquina:server:comprar', }, { label = 'Cola', price = 3, itemImage = 'kurkakola', serverEvent = 'maquina:server:comprar' }, }, }, { label = 'Darle una patada', hold = 1000, onSelect = function() return false, 'No cae nada' end }, { label = 'Romper el cristal', confirm = '¿Romperlo? Pulsa otra vez', onSelect = function() RomperCristal() end }, },})Condiciones
Sección titulada «Condiciones»Las condiciones más rápidas son las que SL-Interact comprueba solo, sin llamar a tu script: distance, groups, items, disabled e inVehicle. Úsalas siempre que puedas.
canInteract es para todo lo demás. Solo se llama cuando el jugador enfoca la interacción, cada 500 ms mientras la sigue enfocando y al pulsar la tecla. Nunca por las que están cerca sin mirarlas.
canInteract devuelve |
Qué ve el jugador |
|---|---|
true |
La tecla sale normal |
false |
La tecla no sale y pulsarla no hace nada |
false, 'motivo' |
La tecla no sale. Si se pulsa, se pone roja un momento con el motivo |
false, 'motivo' con showDisabled = true |
La tecla sale apagada desde el principio y se pone roja con el motivo al pulsarla |
Para lo que solo sabe tu script o el servidor (dinero, stock, permisos):
onSelectpuede devolverfalse, 'motivo': la tecla se pone roja con el motivo.- Cliente:
exports.sl_interact:deny(key, motivo). - Servidor:
exports.sl_interact:deny(source, key, motivo). Si el jugador ya no mira nada, sale como un aviso normal.
-- Servidor: decir que no después de mirar el dineroRegisterNetEvent('tienda:server:comprar', function(ctx) local src = source if not QuitarDinero(src, 50) then exports.sl_interact:deny(src, ctx.key, 'No tienes suficiente dinero') return endend)Cambiar y quitar
Sección titulada «Cambiar y quitar»| Export | Descripción |
|---|---|
update(id, campos) |
Cambia cualquier campo de una interacción: nombre, opciones, insignia, estado, disabled… |
updateMany({ [id] = campos }) |
Varias a la vez. Devuelve cuántas cambió |
remove(id) o remove({ ids }) |
Quita una o varias |
removeMany(ids) |
Quita una lista de ids |
removeLocalEntity(entity) |
Quita tus interacciones de esa entidad |
removeEntity(netId) |
Quita tus interacciones de esa entidad en red |
removeModel(models) |
Quita tus interacciones de esos modelos |
-- El texto de una puerta cambia con su estadoSI:update(puertaId, { options = { { key = 'E', label = abierta and 'Cerrar' or 'Abrir', onSelect = CambiarPuerta },} })Muchas a la vez
Sección titulada «Muchas a la vez»Dar de alta cientos de interacciones una a una cruza de tu recurso a SL-Interact cientos de veces. addMany lo hace en una sola llamada. Cada entrada dice dónde va con uno de estos campos: coords, entity, netId, model / models, o global ('vehicle', 'ped', 'object' o 'player').
local ids = SI:addMany({ { coords = vec3(25.7, -1347.3, 29.5), label = 'Estante 1', options = { ... } }, { coords = vec3(26.4, -1347.3, 29.5), label = 'Estante 2', options = { ... } }, { model = 'prop_atm_01', label = 'Cajero', options = { ... } },})-- ids vuelve en el mismo orden (false en las que venían mal)
SI:updateMany({ [ids[1]] = { badge = 'x3' }, [ids[2]] = { disabled = 'Vacío' } })SI:removeMany(ids)Iconos de serie: product, door, checkout, pay, sale, pc, box, lock, unlock, build, cash, person, car, hand, gear, talk.
Registra los tuyos con registerIcon(nombre, imagen) antes de usarlos:
-- Un archivo de tu recurso (en files { } de tu fxmanifest)SI:registerIcon('estrella', 'star.svg')-- O: 'nui://recurso/archivo.png', 'https://...', 'data:image/png;base64,...'Si SL-Interact se reinicia
Sección titulada «Si SL-Interact se reinicia»Cuando SL-Interact se para, olvida todas las interacciones que le dieron. Con el servidor en marcha, vuelve a dar las tuyas cuando arranque:
AddEventHandler('onClientResourceStart', function(name) if name == 'sl_interact' then DarMisInteracciones() endend)Si se reinicia el servidor entero, o tu recurso después de SL-Interact, no hace falta nada más.