Aller au contenu

Interactions AR

medusa-interactions centralise la détection, la mise en évidence, le popup AR, la touche d'action et les notifications. Une seule cible — la plus proche et autorisée — peut être active à la fois.

La mise en évidence choisit son implémentation selon le type d'entité :

  • véhicules et objets utilisent SetEntityDrawOutline pour une silhouette cyan exacte ;
  • les peds utilisent MarkerTypeVerticalCylinder (DrawMarker, type 1) sous leurs pieds, sans appeler le native d'outline incompatible.

L'outline est géré par transition lors de l'acquisition, du changement de handle, de la sortie de portée et de l'arrêt de ressource. Le marker doit, selon le fonctionnement de FiveM, être dessiné chaque frame ; cette boucle reste endormie à 250 ms lorsqu'aucun ped n'est ciblé et revalide son handle d'entité après Wait(0) pour éviter les courses avec le scan. Tant que le même handle reste ciblé, le scan met à jour l'objet existant au lieu de remplacer sa référence ; aucune frame de marker n'est donc perdue au rythme du scan de proximité.

Les valeurs globales vivent dans medusa-interactions/config.json. La portée de prévisualisation vaut 20 mètres par défaut. highlight.enabled masque tous les highlights ; highlight = false sur une inscription masque seulement celui de cette interaction. Chaque inscription peut remplacer highlightDistance et highlightColor. highlight.ped configure l'offset vertical, la largeur, la profondeur et la hauteur du cylindre.

Cibles prises en charge

Export Usage
registerEntity(options) handle local : ped, joueur, véhicule ou prop
registerNetworkEntity(options) entité résolue à partir de networkId
registerPed(options) ped local créé et nettoyé par le module
registerPoint(options) position sans entité ni contour
unregister(id) retire l'interaction et supprime le ped géré
setEnabled(id, bool) active ou suspend une interaction
update(id, changes) modifie popup, portée, position ou état
notify(message, status, title?) affiche un toast AR partagé

registerEntity accepte aussi directement un ped joueur retourné par GetPlayerPed, un véhicule ou un objet. La nature de l'entité n'est pas imposée.

Pour une zone matérialisée au sol, horizontalDistance = true mesure la portée sur les axes X/Y et verticalDistance (3 mètres par défaut) conserve une barrière entre étages ou instances verticales. Le garage utilise ce mode afin que le conducteur assis dans un véhicule haut reste dans le même cercle que le point au sol ; les autres interactions gardent leur distance 3D.

Exemple

local id = exports['medusa-interactions']:registerEntity({
    id = 'vehicle-inspection',
    entity = vehicle,
    distance = 2.2,
    highlightDistance = 8.0,
    control = 38,
    controlLabel = 'E',
    popup = {
        eyebrow = 'MEDUSA // DIAGNOSTIC',
        signal = 'VÉHICULE VERROUILLÉ',
        overline = 'UNITÉ MOBILE',
        title = 'INSPECTION DU VÉHICULE',
        description = 'Consulter les informations mécaniques',
        actionLabel = 'LANCER LE SCAN',
        actionHint = 'ANALYSE LOCALE',
        telemetry = {
            { label = 'PLAQUE', value = 'MEDUSA' },
            { label = 'ÉTAT', value = 'STABLE' },
        },
    },
    payload = { inspectionMode = 'mechanical' },
    clientEvent = 'my-resource:inspect',
})

AddEventHandler('my-resource:inspect', function(context)
    inspectVehicle(context.entity, context.payload.inspectionMode)
end)

popup peut être une table ou, pour une inscription effectuée dans le même runtime, une fonction recalculant les données affichées. Une action peut être un callback local action, un clientEvent ou un serverEvent. En pratique, les ressources consommatrices utilisent les événements à cause de la frontière de sérialisation des exports. context contient id, entity, distance et le payload opaque fourni lors de l'inscription.

Verrou d'action asynchrone

lockOnAction = true demande au gestionnaire de verrouiller toutes les interactions avant d'appeler l'action. Le verrou masque immédiatement le popup et le highlight, puis suspend scan, sélection et entrées à 250 ms. Un callback Lua reçoit context.releaseInteraction() et doit l'appeler une fois son interface fermée. context.lockToken identifie l'acquisition. Pour un flux piloté par événement, exports['medusa-interactions']:releaseInteraction(id) libère le verrou actif du même identifiant.

Les options d'inscription passent par un export FiveM et doivent donc être sérialisables. Une fonction Lua fournie par une autre ressource dans action, popup ou canInteract n'arrive pas comme fonction dans medusa-interactions. Pour un consommateur externe, utiliser clientEvent, un payload composé de valeurs sérialisables, puis appeler l'export releaseInteraction(id, reason) depuis le gestionnaire d'événement. Avant TriggerEvent, le gestionnaire retire lui-même le callback local du contexte afin de ne pas tenter de le sérialiser.

La libération est idempotente et vérifie identifiant et jeton lorsque le callback fourni est utilisé. Une exception synchrone dans action, une inscription supprimée ou désactivée libère automatiquement le verrou. Sans lockOnAction, le comportement historique reste inchangé.

Diagnostic du cycle

Le drapeau debug de medusa-interactions/config.json active des traces F8 horodatées, sans sortie par frame. Une ouverture normale doit produire dans l'ordre :

  1. control_released puis action_allowed ;
  2. lock_acquired avec l'identifiant et le jeton ;
  3. client_event_dispatched avec locked=true pour un consommateur externe ;
  4. plus tard, lock_released avec la raison fournie par le consommateur.

lock_release_rejected signale un identifiant ou jeton obsolète. popup_target_changed après une libération confirme le moment exact auquel le scanner a repris. Le drapeau est temporairement activé pour le diagnostic courant et doit être remis à false une fois l'incident résolu.

Les champs de popup sont eyebrow, signal, overline, title, description, actionLabel, actionHint, distanceLabel et telemetry. L'interface n'injecte aucun texte métier : la configuration defaultPopup complète uniquement les champs omis par une inscription. Les entrées de telemetry utilisent { label, value }.

Performance

  • le scan spatial est adaptatif : 1 seconde au repos, 150 ms dans la portée de highlight ;
  • les distances sont comparées au carré et sqrt n'est calculé que dans la portée utile ;
  • aucun scan d'entités globales ou raycast permanent n'est effectué ;
  • le polling de la touche chaque frame ne s'active que lorsqu'une interaction est actionnable ;
  • le NUI ne reçoit un message que si cible, distance arrondie ou contenu du popup change ;
  • un verrou d'action suspend entièrement les scans au lieu de laisser le popup se recréer derrière une interface active ;
  • un seul effet de sélection est actif ; le rendu par frame est limité au marker du ped ciblé ;
  • l'outline et les peds gérés sont nettoyés à l'arrêt.

La boucle d'entrée capture son candidat avant Wait(0), puis vérifie que la cible partagée est toujours identique après la reprise. Cette règle évite qu'un scan concurrent mettant active à nil interrompe l'action ou provoque une erreur Lua. Le scan conserve aussi le même objet active tant que l'identifiant et le handle ne changent pas, ce qui évite de perdre ponctuellement l'appui sur E entre deux rafraîchissements.

Les ressources consommatrices doivent désinscrire leurs interactions à leur arrêt. Pour de très grandes populations dynamiques, elles doivent inscrire seulement les entités pertinentes pour le client plutôt que toutes les entités du serveur.

Fournisseur de cible Alt

Le registre registerTargetProvider permet à une ressource de fournir une cible raycast sans dupliquer la boucle d'interaction E. Le binding +medusatarget utilise Alt gauche par défaut et reste remappable. Tant que la touche est tenue, une couche NUI plein écran strictement transparente porte le vrai curseur et le clic local. GetNuiCursorPosition est normalisé avec la résolution active, puis GetWorldCoordFromScreenCoord fournit l'origine et la normale exactes du pointeur. Un seul StartShapeTestLosProbe asynchrone, avec le masque explicite 511, est conservé et son résultat n'est lu qu'au statut final 2. Le candidat le plus prioritaire situé sous ce pointeur, dans sa distance autorisée et en ligne de vue reçoit l'outline. Un fournisseur peut remplacer l'entité raycastée par son entité métier, par exemple convertir un Ped occupant en véhicule. Caméra, roue native du personnage, combat et clics monde sont neutralisés dans les groupes d'entrée 0 à 2 pendant ce mode seulement.

Les fonctions resolve et action peuvent traverser l'export depuis une autre ressource. Cfx les désérialise alors sous forme de tables appelables portant __cfx_functionReference, et non sous le type Lua brut function. Un adaptateur privé unique accepte soit une fonction locale, soit cette référence Cfx avec son métamécanisme __call; il refuse les tables ordinaires et les valeurs absentes. L'inscription, les deux résolutions (acquisition puis clic) et l'action utilisent ce même adaptateur dans une fermeture protégée. Une exception rejoint ainsi les erreurs structurées existantes au lieu d'être ignorée, sans élargir le contrat de données du fournisseur.

cancelTargetMode est la sortie unique utilisée sur relâchement, Échap, éloignement, disparition, ouverture d'une NUI et arrêt de ressource. Elle retire focus, curseur, outline, candidat et état de touche. Lors d'un clic accepté, la couche cible se désactive sans libérer le focus avant l'action : le fournisseur acquitte une seule action et déclare explicitement s'il reprend le focus. La fermeture est idempotente : un relâchement Alt postérieur à l'ouverture du panneau cible ne peut pas voler le focus de cette nouvelle NUI.

Chaque refus conserve un motif structuré et un code de corrélation ALT-…. La NUI consomme la réponse du callback et affiche l'échec en français, y compris en cas de panne CEF. Les diagnostics F8 tracent uniquement les transitions du mode, du raycast, du candidat, de la sélection et du transfert de focus ; ils ne journalisent jamais chaque frame. medusa-vehicles enregistre un fournisseur à 3 m, contrôle le résultat de l'export et le réinscrit après onClientResourceStart de la dépendance. La version 1.7.4 ajoute une trace bornée de l'inscription/désinscription ; l'activation debug doit ensuite annoncer providers=1. Il reste propriétaire de toutes les validations métier ; le fournisseur générique ne décide jamais d'un verrou, d'une porte ou d'un inventaire.

Stations et équipement

medusa-energy enregistre uniquement les points configurés et conserve la validation métier côté serveur. Le cycle E, le câble, la trappe, les distances et le cleanup sont détaillés dans Énergie des véhicules.