Documentación

Documentación de Interact system

Sistema de interacción 3D + motor de diálogos para NPC, sin ninguna dependencia. Coloca una interacción en cualquier punto del mapa: en una entity, en unas coordenadas sueltas, o en un NPC que haces aparecer desde la misma llamada. Sin ox_lib, sin framework, nada que configurar en el servidor.

  • ESX
  • QBCore
  • ox
  • vRP
  • Standalone
01

Instalación

  1. Coloca el recurso

    Descarga el script desde tu cuenta Tebex y copia la carpeta avenida_interact/ en resources/[avenida]/ de tu servidor.

  2. Actívalo en server.cfg

    Añade ensure avenida_interact. No hace falta que cargue nada antes: desde la 1.1.0 el recurso no tiene dependencias, así que el orden de carga da igual.

  3. Fija tus valores por defecto

    Abre config/config.lua y ajusta lo que se aplica a todos los puntos: etiqueta, tecla, distancias showIcon y canInteract, icono, color, colores con nombre, distancia de aparición de los peds, y Config.Hud para elegir qué HUD se oculta durante un diálogo.

  4. Quita las demos

    Los ejemplos de config/entities/, config/peds/ y config/positions/ siguen activos mientras sus archivos estén ahí. Borra los que no quieras, el manifest recorre esas carpetas, no hay nada más que tocar.

  5. Prueba la recarga en caliente

    Arranca el servidor y escribe /restart avenida_interact en el juego. Los iconos desaparecen y vuelven limpios, y los puntos registrados por tus propios recursos sobreviven, sin reiniciar tu sesión.

02

Configuración

Tu primer punto de interacción en 30 segundos

Crea un myscript/client.lua en tu propio recurso y pega esto. Un marcador cian aparece a 30m, el prompt "E - Hello" se despliega a 2m. Lo único que tiene que estar activo es avenida_interact.

lua
-- myscript/client.lua
exports.avenida_interact:positionRegister({
    coords      = vec3(-265.0, -963.6, 31.2),
    showIcon    = 30.0,           -- marker fades in at 30m
    canInteract = 2.0,            -- label unrolls at 2m
    hintIcon    = 'interact',     -- see the icon list below
    hintColor   = '#22d3ee',      -- hex, or an alias from Config.Colors
    message     = 'Hello',
    bind        = 'E',
    onInteract  = function()
        TriggerEvent('chat:addMessage', { args = { 'Hello, world!' } })
    end,
})
03

Exports

Interact Points

  • positionRegister
    exports.avenida_interact:positionRegister(cfg) --> id

    Registra un punto en unas coordenadas fijas. Acepta todos los campos visuales (hintIcon, hintColor, showIcon, canInteract, offsetZ, blips) más choices[] para varias teclas en el mismo punto. canInteract acepta un número o una función, así que puedes condicionar el prompt a un trabajo, un item o un cooldown. Devuelve un id.

  • pedRegister
    exports.avenida_interact:pedRegister(cfg) --> id

    Hace aparecer un NPC configurado (modelo, animDict y animName o un scenario, blip), le engancha un punto y devuelve su índice. El ped es invencible, no huye y no cae en ragdoll. Aparece y desaparece a la distancia que marca Config.PedSpawnRange, 50m por defecto. Pon randomComponents = false en un vendedor con nombre, porque si no cada jugador le verá ropa distinta.

  • pedRemove
    exports.avenida_interact:pedRemove(idx)

    Añadido en la 1.1.0. Hace desaparecer un ped registrado con pedRegister y borra su registro, para que un NPC de misiones temporal pueda irse cuando la misión termina.

  • entityRegister
    exports.avenida_interact:entityRegister(cfg) --> id

    Engancha un punto a una entity existente, pasada en cfg.entity. Admite cfg.bone para colgar el prompt de un bone en vez del centro de la entity. Úsalo para lo que otro script haya hecho aparecer: un vehículo, un prop colocado, un objeto tirado.

  • entityRegisterByHash
    exports.avenida_interact:entityRegisterByHash(hash, cfg)

    Watcher pasivo: la config se aplica a todas las entities de ese modelo, las ya cargadas y las que aparezcan después. Un único thread cubre todos los hashes registrados y hace una pasada cada 5s. Es lo que quieres para cajeros, máquinas expendedoras y puertas, en vez de mantener una lista de coordenadas.

  • entityRemove
    exports.avenida_interact:entityRemove(idx)

    Desengancha el punto devuelto por entityRegister. La entity en sí no se toca.

  • interactCreate
    exports.avenida_interact:interactCreate(cfg) --> id

    Punto en tiempo de ejecución, que creas y quitas cuando hace falta. Úsalo cuando el punto solo existe un rato: un objetivo de misión, una bolsa en el suelo, un cuerpo que registrar.

  • interactRemove
    exports.avenida_interact:interactRemove(id)

    Quita un punto creado con interactCreate, y su blip si declaraba uno.

  • unlockLastInteract
    exports.avenida_interact:unlockLastInteract()

    Un punto se bloquea al usarse, para que una tecla mantenida no lo dispare dos veces. Llama a esto al final de tu acción para rearmarlo. Si el jugador sigue dentro del alcance, el prompt vuelve enseguida. Si no, espera a que se acerque otra vez.

Prompt API

  • HandleTextUI
    exports.avenida_interact:HandleTextUI(id, data) --> duiHandler

    Crea o actualiza una burbuja 3D autónoma, sin punto de interacción detrás. Para cuando quieres controlar la visibilidad desde tu propio thread. Devuelve el handler que pasas a Draw3DSprite.

  • Draw3DSprite
    exports.avenida_interact:Draw3DSprite({ duiHandler, coords, maxDistance, hintOnly? })

    Dibuja la burbuja en el mundo desde un thread de render, corregida según la proporción de la pantalla. Con hintOnly = true solo se dibuja el marcador, que es el estado a larga distancia de un punto normal.

  • CloseTextUI
    exports.avenida_interact:CloseTextUI(id)

    Oculta una burbuja pero mantiene su DUI en memoria, así que reabrirla es instantáneo.

  • RemoveTextUI
    exports.avenida_interact:RemoveTextUI(id)

    Quita una burbuja del todo y libera su DUI. Úsalo cuando sepas que no va a volver.

  • RemoveTextUIs
    exports.avenida_interact:RemoveTextUIs()

    Quita todas las burbujas activas. Se llama automáticamente cuando el recurso se para.

  • IsDuiVisible
    exports.avenida_interact:IsDuiVisible() --> boolean

    Verdadero mientras hay un prompt en pantalla. Léelo para que tus propios prompts no se apilen encima de una interacción.

Key handler

  • HandleHoldTextUI
    exports.avenida_interact:HandleHoldTextUI(id, data) --> duiHandler

    La capa sobre la que se construyen los puntos: dibuja un prompt en Coords y vigila su tecla desde un thread manager a 50ms. data toma BindToHold (índice de control), DistanceHold, Coords, ChoicesUI y ChoicesCtrl para un prompt de varias teclas, onCallback(id) y canInteract(id, dist). Dispara al pulsar con un debounce de 500ms, y tu callback se ejecuta dentro de un pcall, así que un error tuyo no rompe el prompt.

  • CloseHoldTextUI
    exports.avenida_interact:CloseHoldTextUI(id)

    Oculta el prompt sin destruir la instancia, para una salida limpia cuando el jugador deja la zona.

  • RemoveHoldTextUI
    exports.avenida_interact:RemoveHoldTextUI(id)

    Destruye una instancia y libera su DUI.

  • RemoveHoldTextUIs
    exports.avenida_interact:RemoveHoldTextUIs()

    Destruye todas las instancias activas.

04

Recetas

Posición fija con blip en el mapa

El caso más simple: un punto en unas coordenadas, con su blip declarado en la misma tabla. El marcador se ve a 30m, la etiqueta a 2m. Pégalo en cualquier archivo de cliente de tu recurso.

lua
exports.avenida_interact:positionRegister({
    coords      = vec3(149.5, -1040.4, 29.4),
    showIcon    = 30.0,
    canInteract = 2.0,
    hintIcon    = 'bank',
    hintColor   = 'blue',
    message     = 'Use the ATM',
    bind        = 'E',
    onInteract  = function()
        TriggerEvent('myserver-banking:openATM')
    end,
    blips = {
        sprite  = 277,
        color   = 2,
        scale   = 0.8,
        text    = 'ATM',
        display = 4,
    },
})

Todos los cajeros del mapa, dos teclas cada uno

El watcher registra tu config una vez por modelo y la aplica a cada prop que coincida, esté ya cargado o aparezca después. Cada opción lleva su etiqueta, su color y su condition, así que el atraco solo se muestra si tu script de policía lo permite. Ojo con el nombre del campo: una opción usa condition, no canInteract.

lua
local ATM_PROPS = {
    'prop_atm_01', 'prop_atm_02', 'prop_atm_03', 'prop_fleeca_atm',
}

for _, prop in ipairs(ATM_PROPS) do
    exports.avenida_interact:entityRegisterByHash(prop, {
        canInteract = 1.2,
        offsetZ     = 1.0,
        hintIcon    = 'bank',
        hintColor   = 'blue',
        choices = {
            {
                bind       = 'E',
                message    = 'Use the ATM',
                onInteract = function()
                    TriggerEvent('myserver-banking:atmUse')
                end,
            },
            {
                bind      = 'G',
                message   = 'Rob the ATM',
                hintColor = 'red',
                condition = function()
                    return exports['myserver-police']:hasEnoughCops(2)
                end,
                onInteract = function(entity)
                    TriggerServerEvent('myserver-robbery:startATM', NetworkGetNetworkIdFromEntity(entity))
                end,
            },
        },
    })
end

NPC con diálogo ramificado y waypoint GPS

Un NPC de misiones completo: el ped aparece con su animación y su blip, y las respuestas del diálogo ejecutan lo que quieras, aquí el native SetNewWaypoint. changeDialog reemplaza el texto y las respuestas, así que anidas tan profundo como pida la conversación. randomComponents = false mantiene su ropa igual para todos los jugadores.

lua
exports.avenida_interact:pedRegister({
    coords           = vec4(-1393.81, -1064.09, 3.17, 266.09),
    model            = 'a_f_y_vinewood_04',
    animDict         = 'anim@amb@waving@male',
    animName         = 'ground_wave',
    randomComponents = false,
    message          = 'Talk with Maria',
    showIcon         = 10.0,
    canInteract      = 3.0,
    hintIcon         = 'talk',
    hintColor        = 'gold',
    blips = { sprite = 119, color = 50, text = 'Lumberjack', scale = 0.8 },
    dialogue = {
        name     = 'Maria',
        startMsg = 'Hello, are you ready to start your shift?',
        elements = {
            {
                label  = 'Yes, where do I start?',
                action = function(changeDialog, close)
                    SetNewWaypoint(-584.01, 5491.59)
                    changeDialog('The forest is on your GPS now.', {
                        { label = 'Got it!', action = function(_, close) close() end },
                    })
                end,
            },
            {
                label  = 'I\'ll come back later',
                action = function(_, close) close() end,
            },
        },
    },
})
05

Preguntas

¿De verdad no necesito ox_lib?

No lo necesitas. Desde la 1.1.0 bridges/compat.lua implementa la creación de DUI, los puntos de proximidad y los helpers de espera solo con natives, y la línea @ox_lib/init.lua está comentada en el manifest. Si prefieres pasar por ox_lib, pon Config.UseOxLib = true y descomenta esa línea. Los dos caminos siguen soportados, y el nativo es el que viene por defecto.

Mi marcador no aparece, ¿dónde miro?

Primero el alcance: showIcon vale 4m por defecto en Config.Defaults, que se queda corto si esperabas un marcador visible de lejos, así que pasa tu propio valor. Segundo, con entityRegister la entity tiene que existir en el momento de la llamada, mientras que entityRegisterByHash la espera, que es lo correcto para los props del juego. Tercero, pon Config.Debug = true para ver los errores internos en la consola del cliente.

La condición de mi opción no se aplica

Una opción lee condition, no canInteract. canInteract es el campo del punto en sí, donde acepta un número o una función. Dentro de choices[] el filtro se llama condition = function() ... end, y una opción cuya condición devuelve false simplemente no se dibuja.

¿Qué colores puedo usar?

Cualquier hex como #22d3ee, o un nombre de Config.Colors, que viene con red, blue, green, gold, purple y white. Esa tabla está fuera del escrow, añade la paleta de tu servidor y usa tus propios nombres en todas partes. Un hintIcon desconocido se dibuja como texto, así que un emoji te sirve de icono puntual.

El prompt salía estirado en mi ultrawide

Está corregido en la 1.1.0. La textura DUI ahora está fijada a 1920x1080 y se corrige en el dibujado según la proporción real de la pantalla, y el icono ya no cambia de tamaño con la distancia. Las cajas de respuesta de los diálogos también ganaron altura por encima de 2560x1440. Actualiza el recurso si sigues en la 1.0.x.

Mis puntos siguen visibles después de parar mi recurso

La propiedad de un punto se rastrea con GetInvokingResource(), así que pertenece al recurso que llamó al export. Si registras desde un handler compartido que se ejecuta en el contexto de otro recurso, la limpieza sigue a ese otro recurso. Envuelve la llamada en una función del recurso dueño del punto. Desde la 1.1.0 los puntos también se purgan al arrancar un recurso, así que un restart ya no deja fantasmas.

¿Cómo lo conecto a ESX, QBCore, Qbox, ox o vRP?

No tienes que conectarlo a nada: el recurso no conoce tu framework y nunca habla con tu servidor. Lee el trabajo o el grado del jugador dentro de canInteract o de la condition de una opción, y llama a tus propios events desde onInteract. Todo lo que funciona con tu framework se queda detrás de esas dos funciones, sin cambios.

¿Cuántos puntos puedo registrar?

Medido a 0.00ms en reposo con 100 puntos. El dibujado solo ocurre dentro del alcance showIcon, y la vigilancia de teclas se ejecuta cada 50ms para las instancias que están dentro del alcance, así que miles de puntos repartidos por el mapa no cuestan nada mientras no estén todos apilados en la misma calle.

Carrito
Spirit RP
Discord