Registra tus propios NPC desde cualquier recurso, cuelga un diálogo de un ped que ya creaste, o abre un diálogo o una tienda directamente. Los puntos se registran en el cliente; los precios de una tienda, en el servidor.
Referencia rápida
| Lado | Export | Devuelve |
|---|---|---|
| Cliente | Register(id, def) | boolean |
| Cliente | Update(id, patch) | boolean |
| Cliente | Remove(id) | boolean |
| Cliente | Exists(id) | boolean |
| Cliente | GetEntity(id) | integer o nil |
| Cliente | Interact(id) | boolean |
| Cliente | ShowDialog(data) | id del botón que cerró, o nil |
| Cliente | SwitchDialog(dialogId) | boolean |
| Cliente | ShowShop(data) | boolean o nil |
| Cliente | Close() | boolean |
| Cliente | IsOpen() | boolean |
| Servidor | RegisterShop(id, items, options) | boolean |
| Servidor | RemoveShop(id) | boolean |
| Servidor | GetShop(id) | table o nil |
Ejemplo completo: un NPC con diálogo y tienda
Un NPC comprador registrado desde un recurso llamado my_resource. El cliente registra el punto; el servidor registra qué compra la tienda y a qué precio.
Registra la tienda en el servidor
local SHOP_ID = 'my_resource:scrap_buyer'
local function registerShop()
if GetResourceState('cuxial_interactions') ~= 'started' then return end
exports.cuxial_interactions:RegisterShop(SHOP_ID, {
{ name = 'scrapmetal', price = 12 },
{ name = 'copper', price = 20, label = 'Cable de cobre' },
}, {
coords = vec3(25.7, -1347.3, 29.49),
distance = 6.0,
account = 'cash',
})
end
CreateThread(registerShop)
AddEventHandler('onResourceStart', function(resource)
if resource == 'cuxial_interactions' then registerShop() end
end)
RegisterNetEvent('my_resource:server:AskForWork', function(args, info)
local src = source
-- args e info vienen del cliente: comprueba aquí al jugador, la distancia y el estado.
print(('el jugador %s pidió trabajo en el punto %s'):format(src, info.id))
end)Registra el NPC en el cliente
local POINT_ID = 'my_resource:scrap_buyer'
local function registerNpc()
if GetResourceState('cuxial_interactions') ~= 'started' then return end
exports.cuxial_interactions:Register(POINT_ID, {
coords = vec4(25.7, -1347.3, 29.49, 271.0),
model = 's_m_y_dealer_01',
scenario = 'WORLD_HUMAN_STAND_IMPATIENT',
behavior = { invincible = true, noTemporaryEvents = true, freeze = true },
label = 'Hablar con el comprador',
targetIcon = 'fas fa-comment-dots',
activeDistance = 2.5,
blip = { id = 52, scale = 0.6, colour = 5, name = 'Chatarrero' },
-- shop.id vale por defecto el id del punto, así coincide con RegisterShop.
shop = { name = 'Sam', job = 'Comprador', icon = 'store' },
dialog = {
{
id = 'inicio',
name = 'Sam',
job = 'Comprador',
icon = 'store',
text = '¿Traes algo para mí?',
buttons = {
{ id = 'vender', label = 'Enseñar lo que llevo', icon = 'hand-coins', shop = true },
{ id = 'trabajo', label = '¿Tienes trabajo?', icon = 'briefcase', nextDialog = 'trabajo' },
{ id = 'adios', label = 'Hoy no', icon = 'x', close = true },
},
},
{
id = 'trabajo',
name = 'Sam',
job = 'Comprador',
text = 'Puede. Tráeme una ganzúa y hablamos.',
buttons = {
{
id = 'aceptar',
label = 'Tengo una',
icon = 'key',
item = 'lockpick',
close = true,
serverEvent = 'my_resource:server:AskForWork',
args = { job = 'scrap' },
},
{
id = 'despedirse',
label = 'Despedirse',
icon = 'hand',
close = true,
onSelect = function(ctx)
PlayAmbientSpeech1(ctx.entity, 'GENERIC_BYE', 'SPEECH_PARAMS_FORCE')
end,
},
{ id = 'volver', label = 'Volver', icon = 'undo-2', nextDialog = 'inicio' },
},
},
},
})
end
CreateThread(registerNpc)
AddEventHandler('onClientResourceStart', function(resource)
if resource == 'cuxial_interactions' then registerNpc() end
end)Reiniciar cuxial_interactions borra todos los puntos y tiendas registrados por otros recursos. Conserva los manejadores onClientResourceStart y onResourceStart del ejemplo para que los tuyos se registren de nuevo.
Propiedad y limpieza
- Cada punto y cada tienda pertenece al recurso que lo registró.
- Registrar un id que ya es tuyo lo sustituye. Un id de otro recurso se rechaza con un aviso en consola.
- Solo el dueño puede modificar o quitar un punto o una tienda.
- Al parar un recurso se quitan sus puntos, se cierra su diálogo abierto y se borran sus tiendas.
- Los ids son comunes a todo el servidor. Ponles de prefijo el nombre de tu recurso:
'my_resource:scrap_buyer'.
Exports de cliente
Register
Registra un punto: un NPC, o un lugar sin ped, que abre un diálogo o una tienda.
exports.cuxial_interactions:Register(id, def)| Parámetro | Tipo | Descripción |
|---|---|---|
id | string | Id único del punto. No vacío. |
def | table | Definición del punto. Consulta Campos del punto. |
Devuelve: boolean. false cuando id o def no son válidos, cuando el id es de otro recurso, cuando def.enabled == false, o cuando def no tiene ninguno de coords, entity y netId.
-- Cuelga un diálogo de un ped que tu recurso ya creó.
exports.cuxial_interactions:Register('my_resource:doctor', {
entity = doctorPed,
label = 'Hablar',
dialog = {
{
id = 'inicio',
name = 'Dra. Lee',
text = '¿En qué puedo ayudarte?',
buttons = { { id = 'adios', label = 'Nada, gracias', close = true } },
},
},
})Update
Mezcla patch en la definición de un punto tuyo y lo reconstruye.
exports.cuxial_interactions:Update(id, patch)| Parámetro | Tipo | Descripción |
|---|---|---|
id | string | Id del punto. |
patch | table | Campos a sustituir. Solo se mezcla el primer nivel: un dialog o un blip en el parche sustituye la tabla entera. |
Devuelve: boolean. false cuando el punto no existe o es de otro recurso.
exports.cuxial_interactions:Update('my_resource:scrap_buyer', { label = 'Cerrado por hoy', dialog = closedDialog })Update no puede borrar un campo, porque un valor nil no viaja en el parche. Para eso llama otra vez a Register con la definición completa.
Remove
Quita un punto tuyo: su ped, su blip y su opción de target. Si su diálogo está abierto, se cierra.
exports.cuxial_interactions:Remove(id)Devuelve: boolean. false cuando el punto no existe o es de otro recurso.
exports.cuxial_interactions:Remove('my_resource:scrap_buyer')Exists
Indica si un punto está registrado, sea de quien sea.
local registered = exports.cuxial_interactions:Exists(id)Devuelve: boolean.
GetEntity
Devuelve el ped de un punto.
local ped = exports.cuxial_interactions:GetEntity(id)Devuelve: integer o nil. nil cuando el punto no existe, no tiene ped, o el jugador está lejos y el ped no está creado.
local ped = exports.cuxial_interactions:GetEntity('my_resource:scrap_buyer')
if ped then PlayAmbientSpeech1(ped, 'GENERIC_HI', 'SPEECH_PARAMS_FORCE') endInteract
Abre un punto como si el jugador lo hubiera seleccionado. Aplica las mismas condiciones (job, item, canInteract). Úsalo en los puntos con interaction = 'none', que abres desde tu propio marcador, tecla o menú.
exports.cuxial_interactions:Interact(id)Devuelve: boolean. false cuando el punto no existe, ya hay un diálogo o una tienda abiertos, no se cumplen las condiciones, o el punto no tiene dialog, shop ni onInteract.
if not exports.cuxial_interactions:Interact('my_resource:scrap_buyer') then
lib.notify({ description = 'No quiere hablar contigo.', type = 'error' })
endShowDialog
Abre un diálogo que no está ligado a un punto registrado. La llamada espera hasta que el diálogo se cierra.
local result = exports.cuxial_interactions:ShowDialog(data)| Parámetro | Tipo | Descripción |
|---|---|---|
data.dialog | table | Lista de páginas. Obligatorio. Consulta «Página de diálogo» más abajo. |
data.entity | integer | Ped al que enfoca la cámara. Opcional. |
data.shop | table | Tienda que abren los botones con shop = true. Consulta Tienda. |
data.extraData | table | Tus propios datos, que llegan en ctx.extraData. |
data.camera | boolean | false abre sin mover la cámara. |
data.id | string | Id que se informa en ctx.id y en los eventos. Opcional. |
Devuelve: el id del botón con close = true que cerró el diálogo, o su posición si no tiene id. nil cuando se cerró con Esc o con Close(), cuando los datos no son válidos, o al momento si ya hay algo abierto.
local answer = exports.cuxial_interactions:ShowDialog({
entity = guardPed,
dialog = {
{
id = 'pregunta',
name = 'Guardia',
text = 'Esta zona está cerrada. ¿Quieres pagar la tasa?',
buttons = {
{ id = 'si', label = 'Pagar', icon = 'dollar-sign', close = true },
{ id = 'no', label = 'Irme', icon = 'x', close = true },
},
},
},
})
if answer == 'si' then
TriggerServerEvent('my_resource:server:PayFee')
endShowDialog bloquea el hilo que lo llama. Llámalo dentro de CreateThread o de un manejador de evento, nunca en un bucle que corre cada frame.
SwitchDialog
Cambia la página del diálogo abierto.
exports.cuxial_interactions:SwitchDialog(dialogId)| Parámetro | Tipo | Descripción |
|---|---|---|
dialogId | string | number | id de una página del diálogo abierto. |
Devuelve: boolean. false cuando no hay ningún diálogo abierto o la página no existe o está desactivada.
onSelect = function()
local hasLicense = lib.callback.await('my_resource:server:HasLicense', false)
exports.cuxial_interactions:SwitchDialog(hasLicense and 'aprobado' or 'denegado')
end,ShowShop
Abre una tienda. Con un diálogo abierto, pasa a la tienda y añade un botón para volver. Sin nada abierto, abre la tienda directamente y espera hasta que se cierra.
exports.cuxial_interactions:ShowShop(data)| Parámetro | Tipo | Descripción |
|---|---|---|
data | table | Una tabla de tienda, o { shop = tablaDeTienda, entity = ped, camera = boolean, id = string }. Consulta Tienda. |
Devuelve: boolean cuando pasó a la tienda desde un diálogo abierto. nil cuando la abrió directamente, una vez cerrada.
exports.cuxial_interactions:ShowShop({
entity = buyerPed,
shop = { id = 'my_resource:scrap_buyer', name = 'Sam', job = 'Comprador' },
})Abierta así, la tienda necesita su propio id, y ese id debe estar registrado en el servidor con RegisterShop. Si no, se muestra como no disponible.
Close
Cierra el diálogo o la tienda que esté abierto.
exports.cuxial_interactions:Close()Devuelve: boolean. false cuando no había nada abierto.
IsOpen
Indica si hay un diálogo o una tienda abiertos.
local open = exports.cuxial_interactions:IsOpen()Devuelve: boolean.
if exports.cuxial_interactions:IsOpen() then return endEl mismo valor está en LocalPlayer.state.isDialogOpen. Es local al cliente del jugador y no se envía al servidor.
Alias de compatibilidad
Se conservan nombres antiguos en minúscula para que el código existente siga funcionando. En código nuevo usa los nombres de arriba.
| Alias | Equivalente |
|---|---|
showDialog(data) | ShowDialog(data). Acepta además data.ped como entidad. |
switchDialog(id) | SwitchDialog(id) |
showShop(shop) | Pasa a la tienda desde un diálogo abierto. |
showShopDirect({ ped, shop }) | Abre la tienda directamente. |
Exports de servidor
RegisterShop
Registra qué compra una tienda y a qué precio. El servidor solo acepta ventas a tiendas registradas, y siempre paga el precio registrado aquí.
exports.cuxial_interactions:RegisterShop(id, items, options)| Parámetro | Tipo | Descripción |
|---|---|---|
id | string | Id de la tienda. El mismo que shop.id en el cliente; en un punto registrado con Register, el id del punto salvo que pongas otro. |
items | table | Lista de { name, price, label, image }. name es el nombre del objeto y price un número de 0 en adelante; los dos son obligatorios. label toma por defecto el nombre del inventario. image es una URL. Un objeto repetido se toma una vez. |
options.coords | vector3 | vector4 | table | Posición de la tienda, o una lista de posiciones. |
options.netId | integer | Id de red de un ped en red; la distancia se mide hasta él. |
options.anywhere | boolean | true acepta ventas desde cualquier distancia. |
options.distance | number | Metros máximos hasta coords o hasta el ped. Por defecto, shop.maxDistance del config. |
options.account | string | Cuenta en la que se paga la venta. Por defecto, shop.account del config. |
Es obligatorio uno de coords, netId o anywhere = true.
Devuelve: boolean. false cuando el id no es válido o es de otro recurso, cuando la lista está vacía o algún objeto no tiene nombre o un precio válido, o cuando no se indica ubicación.
exports.cuxial_interactions:RegisterShop('my_resource:scrap_buyer', {
{ name = 'scrapmetal', price = 12 },
}, { coords = vec3(25.7, -1347.3, 29.49) })Lo que comprueba el servidor en cada venta, en este orden:
- La tienda está registrada y compra ese objeto.
- La cantidad es un número entero entre 1 y
shop.maxQuantity. - El jugador esperó
shop.cooldowndesde su última venta. - El jugador está dentro de la distancia de la tienda.
- El jugador lleva las unidades.
Después quita los objetos y paga precio × cantidad, redondeado a un número entero. Si el pago falla, se devuelven los objetos.
Con la tienda registrada, el jugador ve la lista y los precios del servidor. Los items de la tabla de tienda del cliente solo se muestran cuando la tienda no está registrada, y entonces no se puede vender nada.
RemoveShop
Quita una tienda tuya.
exports.cuxial_interactions:RemoveShop(id)Devuelve: boolean. false cuando la tienda no existe o es de otro recurso.
GetShop
Devuelve una tienda registrada.
local shop = exports.cuxial_interactions:GetShop(id)Devuelve: { id, account, items } o nil. Cada objeto trae name, label, price e image.
local shop = exports.cuxial_interactions:GetShop('my_resource:scrap_buyer')
if shop then
for _, item in ipairs(shop.items) do
print(item.name, item.price)
end
endReferencia de datos
Campos del punto
La tabla que se pasa a Register, y el valor de cada NPC en un archivo de data/.
Dónde. Es obligatorio uno de los tres.
| Campo | Tipo | Descripción |
|---|---|---|
coords | vector3 | vector4 | Posición del punto. Con model o usePlayerSkin se crea un ped ahí; sin ellos es un lugar sin ped. La w de un vector4 es la orientación. |
entity | integer | Un ped u objeto que ya existe en este cliente. El punto no lo crea ni lo borra. |
netId | integer | Lo mismo, por id de red. El punto se engancha cuando la entidad entra en rango. |
Ped. Solo en los puntos con coords que crean su propio ped.
| Campo | Tipo | Descripción |
|---|---|---|
model | string | number | Modelo del ped. Si no existe, se usa npc.fallbackModel. |
usePlayerSkin | boolean | true da al ped el aspecto de un personaje activo al azar. Recurre a model cuando no hay ningún aspecto disponible. |
heading | number | Orientación, cuando coords es un vector3. |
scenario | string | Escenario que hace el ped, como 'WORLD_HUMAN_CLIPBOARD'. |
animation | table | { dict, clip, speed, blendOut, duration, flag }. dict y clip son obligatorios. Por defecto: 8.0, -8.0, -1, 1. |
clothing | table | Lista de { componentIndex, variation, texture }. No se aplica cuando el ped recibió el aspecto de un jugador. |
behavior | table | { invincible, noTemporaryEvents, freeze }, cada uno un boolean. |
spawnDistance | number | Metros a los que se crea el ped. Por defecto, npc.spawnDistance. |
Interacción.
| Campo | Tipo | Descripción |
|---|---|---|
label | string | Texto de la opción de target. Por defecto, «Hablar». |
targetIcon | string | Clase Font Awesome de la opción de target. Por defecto, target.icon. |
activeDistance | number | Metros desde los que el jugador puede interactuar. Por defecto, target.distance. |
interaction | string | 'target', 'sprite' o 'none'. Por defecto, interaction del config. |
camera | boolean | false abre sin mover la cámara. |
blip | table | { id, scale, colour, name }. id es el sprite del blip. Por defecto: escala 0.6, color 0. Necesita coords. El blip se crea al registrar el punto, esté a la distancia que esté el jugador. |
Condiciones. Deben cumplirse todas para que aparezca la opción.
| Campo | Tipo | Descripción |
|---|---|---|
job | string | table | 'police', { 'police', 'ambulance' } (cualquiera), o { police = 2 } (grado mínimo). |
item | string | table | 'lockpick', { 'lockpick', 'phone' } (todos), o { lockpick = 3 } (unidades mínimas). |
canInteract | function | function(ctx). Debe devolver true. |
enabled | boolean | false deja el punto sin registrar. |
Contenido.
| Campo | Tipo | Descripción |
|---|---|---|
dialog | table | Lista de páginas. Se muestra la primera página activa. |
shop | table | Tienda del punto. Sin dialog, el punto abre la tienda directamente; con dialog, la abre un botón con shop = true. |
onInteract | function | function(ctx). Se ejecuta en lugar de abrir el diálogo o la tienda. |
extraData | table | Tus propios datos, que llegan en ctx.extraData. |
Página de diálogo
| Campo | Tipo | Descripción |
|---|---|---|
id | string | number | Único dentro del diálogo. Lo usan nextDialog y SwitchDialog. |
name | string | Quién habla. |
job | string | Cargo que aparece en la cabecera. Solo texto, no es una condición. |
text | string | Lo que dice el NPC. |
textSpeed | number | Milisegundos por carácter. Por defecto, dialog.textSpeed. |
icon | string | Icono de la cabecera. Consulta Iconos. |
enabled | boolean | false omite la página. |
buttons | table | Lista de botones. |
Botón de diálogo
| Campo | Tipo | Descripción |
|---|---|---|
id | string | number | Id del botón. Es lo que devuelve ShowDialog cuando el botón cierra. |
label | string | Texto del botón. |
icon | string | Icono del botón. Consulta Iconos. |
enabled | boolean | false oculta el botón. |
job, item, canInteract | Condiciones, como en el punto. Un botón que no las cumple no se envía a la interfaz ni se puede ejecutar. | |
close | boolean | Cierra el diálogo. |
onSelect | function | function(ctx). |
event | string | Evento de cliente: TriggerEvent(event, args, info). |
serverEvent | string | Evento de servidor: TriggerServerEvent(serverEvent, args, info). |
args | any | Valor que se envía como primer argumento de event y serverEvent. |
shop | boolean | Abre la tienda del punto. |
nextDialog | string | number | id de la página que se muestra a continuación. |
Un botón ejecuta sus acciones en este orden: close, onSelect, event, serverEvent, y después shop o nextDialog. Con close = true se omite el último paso. shop tiene prioridad sobre nextDialog.
Tienda
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id registrado con RegisterShop. En un punto vale por defecto el id del punto. |
name | string | Nombre del comprador. Por defecto, «Vendedor». |
job | string | Cargo que aparece en la cabecera. Por defecto, «Comerciante». |
icon | string | Icono de la cabecera. Consulta Iconos. |
items | table | Lista de { name, label, price, image }. Obligatoria en los archivos de data/, donde es también la lista que se registra en el servidor. Desde otro recurso es opcional: se usa la lista del servidor. |
La tabla ctx
La reciben onSelect, canInteract y onInteract.
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id del punto. nil en un diálogo abierto con ShowDialog sin data.id. |
entity | integer | Ped del punto, si lo hay. ctx.ped contiene el mismo valor. |
dialogId | string | number | id de la página actual. Solo en botones. |
buttonId | string | number | id del botón. Solo en botones. |
index | integer | Posición del botón en la página. Solo en botones. |
extraData | table | El extraData del punto o de ShowDialog. |
La tabla info
Segundo argumento de event y serverEvent.
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id del punto. |
dialogId | string | number | id de la página. |
buttonId | string | number | id del botón. |
entity | integer | Handle del ped en el cliente que lo envió. |
netId | integer | Id de red del ped. Solo cuando el ped está en red; los peds que crea un punto son locales y no lo tienen. |
Cualquier cliente puede lanzar un evento de servidor en cualquier momento. En su manejador, comprueba al jugador (source), la distancia al NPC y lo que la acción requiera. No te fíes nunca de args ni de info.
Funciones enviadas a través de exports
onSelect, canInteract y onInteract son funciones de tu recurso a las que llama Cuxial Interactions. Ten en cuenta:
- El
canInteractde un punto se evalúa mientras el jugador le apunta, reutilizando el resultado durantedialog.conditionIntervalmilisegundos, y una vez más al seleccionar. Que sea ligero. - Un error dentro de cualquiera de ellas no rompe el diálogo. Deja un aviso en la consola del cliente.
- No guardes
ctxdespués de que la llamada termine. - Cuando baste con un evento, usa
eventoserverEvent: no dependen de referencias a funciones.
Iconos
targetIcon recibe una clase Font Awesome, porque lo dibuja tu recurso de target.
El icon de páginas, botones y tiendas recibe uno de estos nombres:
arrow-left, arrow-right, bed, book, book-open, briefcase, car, check, circle-alert, circle-check, circle-play, circle-question-mark, circle-x, clipboard, clipboard-plus, dollar-sign, door-open, fish, folder-open, gamepad-2, hand, hand-coins, hand-heart, heart, hospital, house, id-card, info, key, log-out, message-circle, message-circle-more, messages-square, package, phone, pill, shield, shopping-cart, skull, stethoscope, store, thumbs-up, undo-2, user, user-pen, venetian-mask, wrench, x.
Cualquier otro nombre muestra un bocadillo de diálogo. En la cabecera de una tienda muestra una tienda.
Eventos
Cuxial Interactions no tiene eventos propios para otros recursos. Usa los campos event y serverEvent de un botón para recibir los tuyos.
