Système de recon¶
Le système de recon permet aux joueurs de révéler les unités ennemies qu'une unité alliée peut réellement voir. Sa portée est délibérément restreinte : RECON n'affiche que les moyens ennemis détectés par ligne de visée (LOS) depuis une unité alliée. Il ne doit jamais servir à montrer des moyens amis (FOB, zones, beacons) — ceux-ci relèvent des menus de leurs propres managers.
Le code source se trouve dans src/CTLD_recon.lua, qui définit deux collaborateurs :
CTLDReconRenderer— une table statique (sans instance) qui dessine les icônes sur la carte F10.CTLDReconManager— le singleton du domaine (CTLDReconManager = class(), obtenu viaCTLDReconManager.getInstance()) qui détient l'état par joueur, le pipeline de scan et le sous-menu F10 RECON.
Voir Architecture pour l'idiome manager/singleton et Événements pour les événements listés ci-dessous.
Pipeline scan → mark¶
Une session de recon pour un joueur exécute la boucle suivante :
- Le joueur active un ou plusieurs layers depuis le sous-menu RECON (tous les layers démarrent désactivés).
- Le joueur lance RECON.
scan()exécute une vérification LOS viactld.utils.getUnitsLOS()et dessine une icône Draw-API par cible détectée, mise en forme selon le layer de la cible. - Le rafraîchissement automatique est toujours activé au démarrage. Toutes les
reconRefreshIntervalsecondes, un tick de rafraîchissement re-scanne et réconcilie les cibles nouvelles / déplacées / perdues. - Arrêter RECON supprime toutes les marks et annule le timer.
scan(playerUnit, player) — démarrage / re-scan¶
scan() est l'action F10 « RECON [Start] » et il est aussi appelé en interne lors du toggle d'un
layer pendant qu'un scan est actif (un re-scan avec l'ensemble de layers mis à jour). Il :
- Se conditionne à
ctld.gs("reconF10Menu")— la même clé que la section de menu. Si RECON est visible, le scan fonctionne sans configuration supplémentaire. - Impose une altitude AGL minimale de
ctld.gs("reconMinAltitude")(valeur de repli50m), calculée à partir deland.getHeight()sous l'unité. - Annule tout scan précédent (supprime son timer et ses marks) avant de reconstruire.
- Appelle
_scanLOS()avec la liste complète des layers du joueur (pas seulement ceux activés), afin que_matchLayer()puisse appliquer correctement la priorité des layers (voir ci-dessous), puis crée les icônes pour les cibles retournées et les compte par layer. - Enregistre la session dans
self._activeScans[player]. - Synchronise les marks FARP/FOB via
_syncFarpMarks(). - Publie
OnReconScan, puis appelleenableAutoRefresh(..., true)et reconstruit la branche RECON.
RECON démarre même quand aucun layer n'est activé : le joueur peut activer des layers depuis le menu ensuite, sans redémarrer. Lors d'un démarrage à neuf sans layer, un message d'information est affiché ; lors d'un re-scan par toggle de layer, il est supprimé.
_scanLOS(playerUnit, enabledLayers, searchRadius) — cœur LOS¶
_scanLOS() construit la liste des noms d'unités ennemies (_getEnemyUnitNames() couvre GROUND,
AIRPLANE, HELICOPTER, SHIP pour la coalition adverse), puis appelle
ctld.utils.getUnitsLOS() avec un altoffset de 180 (identique au legacy). Pour chaque unité
visible, il résout le meilleur layer via _matchLayer() et, si ce layer est activé, produit un
enregistrement de cible portant unit, unitName, unitType, coalition, playerCoalition,
position, distance et layer.
Rafraîchissement automatique : _doRefresh(playerName, unitName, _t)¶
Le callback du timer réexécute _scanLOS(), indexe les cibles précédentes par unitName, et
classifie les cibles courantes :
- new — aucune entrée précédente : alloue une mark, dessine l'icône, tague
status = "new". - moved — même unité déplacée de plus de
5m : supprime l'ancienne icône, alloue un nouvel identifiant de mark, redessine, taguestatus = "moved". Les identifiants de mark DCS ne sont jamais réutilisés, donc une cible déplacée reçoit toujours un nouvel identifiant. - existing — déplacement ≤
5m : reporte lemarkIdprécédent inchangé. - lost — restant dans l'index précédent : supprime l'icône ; la raison est
"dead"si l'unité n'existe plus, sinon"out_of_los".
Le tick met à jour scan.targets, re-synchronise les marks FARP/FOB, se re-planifie lui-même, et
publie OnReconScanRefresh avec le détail complet new/moved/lost.
Arrêt et toggle¶
stopScan()(F10 « RECON [Stop] ») annule le timer, supprime toutes les marks de cibles et les marks FARP/FOB, efface_activeScans[player], et publieOnReconHideTargets. Les états activé/désactivé des layers sont préservés pour le prochain démarrage.enableAutoRefresh()/disableAutoRefresh()activent/désactivent le timer de rafraîchissement et publientOnReconAutoRefreshEnabled/OnReconAutoRefreshDisabled. Désactiver fige les marks courantes sur place.toggleLayer()bascule le flagenabledd'un layer, l'indique au joueur, et — si un scan est actif — re-scanne immédiatement pour que le nouvel ensemble de layers prenne effet. Il publieOnReconLayerToggled.
Layers¶
Les layers sont définis une seule fois dans CTLDReconManager._defaultLayers et copiés par
joueur par _getPlayerLayers() (initialisé paresseusement), de sorte que chaque joueur porte un
état activé/désactivé indépendant. Chaque objet layer possède un layerId, un name d'affichage,
un flag enabled, une color, un filterAttrib (un nom d'attribut d'unité DCS) et une clé
iconRenderer.
layerId |
Nom | filterAttrib |
iconRenderer |
|---|---|---|---|
infantry |
Infantry | Infantry |
infantry |
air_defense |
Air Defense (AA) | Air Defence |
aa |
ground_vehicles |
Ground Vehicles | Vehicles |
vehicle |
helicopters |
Helicopters | Helicopters |
helicopter |
aircraft |
Aircraft | Planes |
aircraft |
ships |
Ships | Ships |
ship |
farp_fob |
FARP / FOB | nil |
farp |
L'ordre des layers est significatif. _matchLayer() parcourt la liste ordonnée complète et
retourne le premier layer dont l'unité possède le filterAttrib. Les attributs plus spécifiques
doivent précéder les plus généraux : Air Defence ⊂ Vehicles, donc air_defense vient avant
ground_vehicles ; Helicopters ⊂ Planes, donc helicopters vient avant aircraft. Point
crucial, _matchLayer() s'exécute contre la liste complète même lors d'un scan, puis ne retourne le
layer trouvé que s'il est actuellement activé — cela empêche une unité de « retomber » vers un layer
de priorité inférieure lorsque son layer de meilleure correspondance est désactivé (un hélicoptère
ne doit pas apparaître comme Aircraft quand le layer Helicopters est désactivé ; un ZU-23 ne doit pas
apparaître comme Vehicle quand AA est désactivé).
Le layer farp_fob a filterAttrib = nil à dessein : _matchLayer() ignore les layers sans
filterAttrib, car la détection FARP/FOB utilise un pipeline dédié plutôt qu'une correspondance par
attribut d'unité.
Cycle de vie du layer FARP/FOB¶
Les marks FARP/FOB ne suivent pas le cycle de vie des enregistrements de cible. Elles sont gérées par
_syncFarpMarks() et suivies dans self._farpMarks[player] (indexé par object id → mark id). La
détection puise dans deux sources, chacune conditionnée par reconSearchRadius (valeur de repli
5000 m) et une vérification LOS land.isVisible() à +180 m :
- Source A —
coalition.getAirbases()pour le camp ennemi, filtré aux objets dontgetDesc().attributes.Helipadest défini. - Source B — les FOB ennemis issus de
CTLDFOBManager(_fobscorrespondant à la coalition ennemie et vivants).
Les nouvelles détections dessinent une icône, enregistrent un CTLDStaticWatcher sous la clé
"recon_farp_<player>_<id>", et publient ReconFarpDetected. Quand l'objet surveillé meurt, le
watcher supprime l'icône, efface l'entrée, et publie ReconFarpLost. Les marks sont
persistantes : un objet déjà marqué est ignoré lors des syncs suivants (pas de scintillement),
et les marks survivent jusqu'à la mort de l'objet ou l'effacement du layer/scan. _clearFarpMarks()
supprime toutes les marks FARP/FOB d'un joueur et annule ses watchers.
Rendu des icônes¶
CTLDReconRenderer.createIcon(target, markId) dispatche sur target.layer.iconRenderer vers la
fonction de dessin correspondante (drawInfantryIcon, drawVehicleIcon, drawAAIcon,
drawAircraftIcon, drawHelicopterIcon, drawShipIcon, drawFarpIcon), avec un repli sur un simple
cercle pour une clé inconnue. Chaque cible consomme jusqu'à trois éléments Draw-API aux identifiants
markId*10+1 .. markId*10+3 ; removeIcon(markId) supprime les trois.
La forme de l'icône encode le type de layer, et la couleur de l'icône encode la coalition de
l'unité détectée (RED → rouge, BLUE → bleu, NEUTRAL → gris), de sorte que les deux dimensions
n'entrent jamais en collision. La taille de l'icône est mise à l'échelle avec
ctld.gs("reconIconScale") (valeur de repli 1.0). Les marks ne sont dessinées que pour la
coalition du joueur, et tous les identifiants de mark proviennent du compteur monotone applicatif
global ctld.utils.getNextMarkId() via _nextMark().
Menu F10¶
CTLDReconManager enregistre une section de menu lors de l'init() via
CTLDPlayerManager.getInstance():registerMenuSection({ key = "recon", ..., configKey =
"reconF10Menu", order = 70 }), conditionnée à reconF10Menu. buildMenuSection() ajoute le
sous-menu RECON sous CTLD et délègue à _addReconCommands(), qui met en place :
- un unique toggle RECON [Start] / RECON [Stop] reflétant l'état du scan actif ;
- une commande par layer, dont le libellé indique la prochaine action —
[activate]quand le layer est désactivé,[deactivate]quand il est activé. Un suffixe(X)est ajouté tant que RECON est au repos (aucun scan actif).
Après tout changement d'état (démarrage, arrêt, toggle de layer), _rebuildReconBranch() efface la
branche RECON et ré-ajoute les commandes avec des libellés à jour, puis rafraîchit le menu.
API de requête¶
getActiveScan(player)— l'état du scan courant (playerUnit,coalition,targets,layers,autoRefresh,refreshTimer) ounil.getPlayerLayers(player)— le tableau de layers du joueur (initialisé paresseusement).
Événements émis¶
| Événement | Quand |
|---|---|
OnReconScan |
Un scan démarre (ou re-scanne) |
OnReconScanRefresh |
Un tick de rafraîchissement automatique se termine |
OnReconHideTargets |
RECON est arrêté |
OnReconAutoRefreshEnabled / OnReconAutoRefreshDisabled |
Rafraîchissement automatique basculé |
OnReconLayerToggled |
Un layer est activé/désactivé |
ReconFarpDetected / ReconFarpLost |
Une mark FARP/FOB est ajoutée / son objet meurt |
Clés de configuration¶
| Clé | Valeur de repli | Rôle |
|---|---|---|
reconF10Menu |
— | Condition d'accès au sous-menu RECON et à scan() |
reconSearchRadius |
5000 |
Rayon de scan LOS (m) |
reconMinAltitude |
50 |
AGL minimale pour scanner (m) |
reconRefreshInterval |
10 |
Période de rafraîchissement automatique (s) |
reconIconScale |
1.0 |
Multiplicateur de taille d'icône |
Toute la configuration est en lecture seule via ctld.gs("...") — jamais config:getSetting().