Documentation

Documentation Interact system

Système d'interaction 3D + moteur de dialogues PNJ, sans aucune dépendance. Posez une interaction n'importe où sur la map : sur une entity, sur une coordonnée libre, ou sur un PNJ que vous faites apparaître depuis le même appel. Pas d'ox_lib, pas de framework, rien à configurer côté serveur.

  • ESX
  • QBCore
  • ox
  • vRP
  • Standalone
01

Installation

  1. Déposez la ressource

    Récupérez le script depuis votre compte Tebex, puis copiez le dossier avenida_interact/ dans resources/[avenida]/ sur votre serveur.

  2. Activez dans server.cfg

    Ajoutez ensure avenida_interact. Rien n'a besoin de charger avant : depuis la 1.1.0 la ressource n'a aucune dépendance, l'ordre de chargement n'a donc pas d'importance.

  3. Réglez vos valeurs par défaut

    Ouvrez config/config.lua et ajustez ce qui s'applique à tous les points : label, touche, distances showIcon et canInteract, icône, couleur, couleurs nommées, distance d'apparition des PNJ, et Config.Hud pour choisir quel HUD est masqué pendant un dialogue.

  4. Retirez les démos

    Les exemples de config/entities/, config/peds/ et config/positions/ sont actifs tant que leurs fichiers sont là. Supprimez ceux dont vous ne voulez pas, le manifest globe ces dossiers, il n'y a rien d'autre à modifier.

  5. Testez le rechargement à chaud

    Démarrez le serveur, puis tapez /restart avenida_interact en jeu. Les icônes disparaissent et reviennent proprement, et les points enregistrés par vos propres ressources survivent, sans relancer votre session.

02

Configuration

Votre premier point d'interaction en 30 secondes

Créez un myscript/client.lua dans votre propre ressource et collez ceci. Un marqueur cyan apparaît à 30m, le prompt "E - Hello" se déroule à 2m. La seule chose qui doit tourner, c'est avenida_interact lui-même.

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

    Enregistre un point sur des coordonnées fixes. Accepte tous les champs visuels (hintIcon, hintColor, showIcon, canInteract, offsetZ, blips) plus choices[] pour plusieurs touches sur le même point. canInteract accepte un nombre ou une fonction, vous pouvez donc conditionner le prompt à un job, un item ou un cooldown. Renvoie un id.

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

    Fait apparaître un PNJ configuré (modèle, animDict et animName ou un scenario, blip), lui attache un point et renvoie son index. Le ped est invincible, ne fuit pas et ne tombe pas en ragdoll. Il apparaît et disparaît autour de Config.PedSpawnRange, 50m par défaut. Mettez randomComponents = false sur un vendeur nommé, sinon chaque joueur voit une tenue différente.

  • pedRemove
    exports.avenida_interact:pedRemove(idx)

    Ajouté en 1.1.0. Fait disparaître un ped enregistré avec pedRegister et retire son enregistrement, pour qu'un donneur de quête temporaire puisse partir une fois la quête finie.

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

    Attache un point sur une entity existante, passée dans cfg.entity. Gère cfg.bone pour accrocher le prompt sur un bone plutôt que sur le centre de l'entity. À utiliser pour ce qu'un autre script a fait apparaître : un véhicule, un prop posé, un objet lâché.

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

    Watcher passif : la config s'applique à toutes les entities de ce modèle, celles déjà streamées et celles qui apparaîtront. Un seul thread couvre tous les hash enregistrés et balaye toutes les 5s. C'est ce qu'il vous faut pour les ATM, les distributeurs et les portes, plutôt qu'une liste de coordonnées à maintenir.

  • entityRemove
    exports.avenida_interact:entityRemove(idx)

    Détache le point renvoyé par entityRegister. L'entity elle-même n'est pas touchée.

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

    Point créé et retiré à la volée. À utiliser quand le point n'existe qu'un temps : un objectif de mission, un sac au sol, un corps à fouiller.

  • interactRemove
    exports.avenida_interact:interactRemove(id)

    Retire un point créé par interactCreate, et son blip s'il en déclarait un.

  • unlockLastInteract
    exports.avenida_interact:unlockLastInteract()

    Un point se verrouille une fois utilisé, pour qu'une touche maintenue ne le déclenche pas deux fois. Appelez ceci à la fin de votre action pour le réarmer. Si le joueur est encore à portée, le prompt revient directement, sinon il attend qu'il rentre à nouveau dans la zone.

Prompt API

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

    Crée ou met à jour une bulle 3D autonome, sans point d'interaction derrière. Pour quand vous voulez piloter la visibilité depuis votre propre thread. Renvoie le handler à passer à Draw3DSprite.

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

    Dessine la bulle dans le monde depuis un thread de rendu, dé-étirée selon le ratio de l'écran. Avec hintOnly = true, seul le marqueur est dessiné, c'est l'état longue distance d'un point normal.

  • CloseTextUI
    exports.avenida_interact:CloseTextUI(id)

    Masque une bulle mais garde son DUI en mémoire, la réouverture est donc instantanée.

  • RemoveTextUI
    exports.avenida_interact:RemoveTextUI(id)

    Retire une bulle définitivement et libère son DUI. À utiliser quand vous savez qu'elle ne reviendra pas.

  • RemoveTextUIs
    exports.avenida_interact:RemoveTextUIs()

    Retire toutes les bulles actives. Appelé pour vous quand la ressource s'arrête.

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

    Vrai tant qu'un prompt est à l'écran. Lisez-le pour éviter que vos propres prompts s'empilent par-dessus une interaction.

Key handler

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

    La couche sur laquelle les points sont construits : elle dessine un prompt sur Coords et surveille sa touche depuis un thread manager à 50ms. data prend BindToHold (control index), DistanceHold, Coords, ChoicesUI et ChoicesCtrl pour un prompt multi-touches, onCallback(id) et canInteract(id, dist). Déclenche à la pression avec un debounce de 500ms, et votre callback tourne dans un pcall, donc une erreur de votre côté ne casse pas le prompt.

  • CloseHoldTextUI
    exports.avenida_interact:CloseHoldTextUI(id)

    Masque le prompt sans détruire l'instance, pour une sortie propre quand le joueur quitte la zone.

  • RemoveHoldTextUI
    exports.avenida_interact:RemoveHoldTextUI(id)

    Détruit une instance et libère son DUI.

  • RemoveHoldTextUIs
    exports.avenida_interact:RemoveHoldTextUIs()

    Détruit toutes les instances actives.

04

Recettes

Position fixe avec un blip sur la map

Le cas le plus simple : un point sur des coordonnées, avec son blip déclaré dans la même table. Le marqueur s'affiche à 30m, le label à 2m. À déposer dans n'importe quel fichier client de votre ressource.

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,
    },
})

Tous les ATM de la map, deux touches chacun

Le watcher enregistre votre config une fois par modèle et l'applique à chaque prop correspondant, déjà streamé ou non. Chaque choix porte son label, sa couleur et sa condition, le braquage n'apparaît donc que si votre script police l'autorise. Attention au nom du champ : un choix utilise condition, pas 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

PNJ avec dialogue à embranchements et waypoint GPS

Un donneur de quête complet : le ped apparaît avec son animation et son blip, et les réponses du dialogue exécutent ce que vous voulez, ici le natif SetNewWaypoint. changeDialog remplace le texte et les réponses, vous imbriquez donc aussi loin que la conversation le demande. randomComponents = false garde sa tenue identique pour tous les joueurs.

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

Questions

Je n'ai vraiment pas besoin d'ox_lib ?

Non. Depuis la 1.1.0, bridges/compat.lua implémente la création des DUI, les points de proximité et les helpers d'attente en natifs purs, et la ligne @ox_lib/init.lua est commentée dans le manifest. Si vous préférez passer par ox_lib, mettez Config.UseOxLib = true et décommentez cette ligne. Les deux chemins sont maintenus, celui en natifs est le défaut.

Mon marqueur ne s'affiche pas, où chercher ?

D'abord la portée : showIcon vaut 4m par défaut dans Config.Defaults, ce qui est court si vous attendiez un marqueur visible de loin, passez donc votre propre valeur. Ensuite, avec entityRegister l'entity doit exister au moment de l'appel, alors que entityRegisterByHash l'attend, ce qui est le bon choix pour les props du jeu. Enfin, mettez Config.Debug = true pour afficher les erreurs internes dans la console client.

Ma condition de choix est ignorée

Un choix lit condition, pas canInteract. canInteract est le champ du point lui-même, où il accepte un nombre ou une fonction. Dans choices[], la garde s'appelle condition = function() ... end, et un choix dont la condition renvoie faux n'est tout simplement pas dessiné.

Quelles couleurs puis-je utiliser ?

N'importe quel hex comme #22d3ee, ou un nom de Config.Colors, livré avec red, blue, green, gold, purple et white. Cette table est hors escrow, ajoutez-y la palette de votre serveur et utilisez vos propres noms partout. Un hintIcon inconnu est dessiné en texte, un emoji fait donc office d'icône ponctuelle.

Le prompt était étiré sur mon ultrawide

C'est corrigé en 1.1.0. La texture DUI est verrouillée en 1920x1080 et dé-étirée au rendu selon le vrai ratio de l'écran, et l'icône ne change plus de taille avec la distance. Les boîtes de réponse des dialogues ont aussi gagné en hauteur au-dessus de 2560x1440. Mettez à jour si vous êtes encore en 1.0.x.

Mes points restent visibles après l'arrêt de ma ressource

La propriété est suivie avec GetInvokingResource(), un point appartient donc à la ressource qui a appelé l'export. Si vous enregistrez depuis un handler partagé qui tourne dans le contexte d'une autre ressource, le nettoyage suit cette autre ressource. Encapsulez l'appel dans une fonction de la ressource propriétaire du point. Depuis la 1.1.0 les points sont aussi purgés au démarrage d'une ressource, un restart ne laisse donc plus de fantômes.

Comment le brancher sur ESX, QBCore, Qbox, ox ou vRP ?

Vous n'avez rien à brancher : la ressource ne connaît pas votre framework et ne parle jamais à votre serveur. Lisez le job ou le grade du joueur dans canInteract ou dans la condition d'un choix, et appelez vos propres events depuis onInteract. Tout ce qui tourne sur votre framework tourne derrière ces deux fonctions, sans modification.

Combien de points puis-je enregistrer ?

Mesuré à 0.00ms au repos avec 100 points. Le rendu ne se déclenche que dans la portée showIcon, et la surveillance des touches tourne à 50ms pour les instances à portée, donc des milliers de points répartis sur la map ne coûtent rien tant qu'ils ne sont pas tous empilés dans la même rue.

Panier
Spirit RP
Discord