Système de véhicules¶
Le système de véhicules transporte des unités terrestres entières par voie aérienne : un transport récupère un véhicule pilotable dans une zone logistique, le convoie, et le repose sur le terrain — sans jamais le décomposer en crates. C'est le pendant du pipeline de crates (voir crates), et il sous-tend également le spawn des JTAC terrestres (voir Cycle de vie Troop + JTAC).
Source : src/CTLD_vehicle.lua
Entité : CTLDVehicle
Singleton : CTLDVehicleSpawner
CTLDVehicleSpawner suit l'idiome de manager standard (X = class() + getInstance() — voir
Architecture) : une unique _instance, allouée paresseusement au premier
appel de CTLDVehicleSpawner.getInstance(). CTLDVehicle est une simple entité créée avec
:new(data).
CTLDVehicle = class()
CTLDVehicleSpawner = class()
CTLDVehicleSpawner._instance = nil
function CTLDVehicleSpawner.getInstance()
if not CTLDVehicleSpawner._instance then
local o = setmetatable({}, CTLDVehicleSpawner)
o:init()
CTLDVehicleSpawner._instance = o
end
return CTLDVehicleSpawner._instance
end
À l'init(), le singleton enregistre sa section du menu F10 (order = 30), s'abonne au pont DCS
S_EVENT_DEAD, s'abonne à plusieurs événements du bus, et démarre trois timers périodiques
(détection de chargement natif, rafraîchissement de l'atterrissage pour packing, indice de vol
stationnaire).
Machine à états¶
CTLDVehicle.STATE déclare quatre états :
| État | Signification |
|---|---|
WAITING |
Au sol, suivi, prêt à être chargé |
LOADED |
Transporté par un transport (unité DCS détruite pour menu_ctld, ou physiquement liée pour dcs_native) |
FALLING |
Libéré en vol par le système cargo natif DCS : l'unité reste vivante et tombe sous son parachute DCS ; retour à WAITING une fois posé |
DELIVERED |
Déclaré mais actuellement non assigné — réservé à un futur état de livraison dédié |
Les transitions réellement effectuées par le code actuel sont :
spawn / register loadVehicle
(none) ───────────────► WAITING ──────────────► LOADED
▲ │
│ unloadVehicle │
└────────────────────────┘
│ parachuteVehicle
└─── (drop, then respawn) ─► WAITING
unloadVehicle comme parachuteVehicle ramènent le véhicule à WAITING (et non DELIVERED)
afin qu'il puisse être rechargé une fois de retour au sol. setState(newState) n'effectue aucune
validation — la correction incombe aux appelants.
Méthodes de chargement et déchargement¶
Le chargement et le déchargement portent chacun une chaîne method :
| Sens | Méthode | Déclencheur |
|---|---|---|
| Load | menu_ctld |
Le joueur choisit un véhicule dans le sous-menu F10 Load / Extract Vehicles ; l'unité DCS est détruite au chargement |
| Load | dcs_native |
L'entrée compagnon CRG:<nom d'unité> du véhicule apparaît dans la liste de cargo à bord d'un appareil joueur (unit:getCargosOnBoard()) ; DCS garde l'unité physiquement liée |
| Unload | menu_ctld |
Le joueur choisit un véhicule dans Unload Vehicles ; l'unité est respawnée près du transport |
| Unload | dcs_native |
L'entrée quitte la liste, transport au sol ; DCS a déjà posé l'unité, CTLD récupère la référence vivante (jamais respawnée) |
| Unload | parachute |
Largage en vol : Parachute Vehicle virtuel de CTLD (l'unité est respawnée à sa position d'atterrissage), ou véhicule en carry natif dont l'entrée quitte la liste en vol (l'unité n'est pas respawnée ; état FALLING jusqu'à l'atterrissage) |
La règle derrière les deux modes de carry est qu'un objet cargo est déchargé de la façon dont il a été
chargé : les entrées F10 de déchargement et de parachutage (findVirtualCarryVehicles) ne listent que les
véhicules en carry virtuel, et un véhicule en carry natif est libéré par DCS.
D'où vient un véhicule WAITING¶
Un CTLDVehicle chargeable en état WAITING est enregistré par l'un de ces chemins :
| Source | Mécanisme |
|---|---|
| Véhicule pré-placé par le MM | INIT-D scanMMVehicles() scanne les groupes Group.Category.GROUND des deux coalitions et appelle _registerMMVehicleUnit(unit) pour chaque unité vivante dont le type possède un descripteur de crate CTLD (findDescriptorByUnitType) |
| Activation retardée | S_EVENT_BIRTH → onBirth diffère d'une frame → _onBirthDeferred → _registerMMVehicleUnit (ou rattache une référence d'unité vivante à un véhicule existant après un déchargement) |
| Spawn programmatique | spawnVehicleForTransport(vehicleType, spawner, logisticZone) spawne une unité neuve près d'un transport et l'enregistre WAITING |
| Unpack de crate | spawnVehicleAt(spawnData, position) / registerJTACVehicle(...) enregistrent une unité produite par le flux d'unpack de crate/JTAC |
_registerMMVehicleUnit est idempotent : il ignore une unité déjà présente dans la table de
recherche inverse _unitToVehicle.
spawnVehicleForTransport¶
Spawne une unité pilotable dans le secteur avant du transport via ctld.utils.dynAdd, crée le
CTLDVehicle en WAITING, et publie OnVehicleSpawnedForTransport. Les noms de groupe et d'unité
sont attribués sous la forme CTLD_VEH_<type>_<id> (les espaces et slashes étant remplacés par
_), et conservés dans spawnData afin que l'unité réapparaisse sous le même nom après tout
déchargement ultérieur. Le chemin JTAC terrestre (spawnJTACFromDescriptor) appelle cette méthode,
puis CTLDJTACManager:startLase(...).
La branche Feature-Q « Request Equipment spawns a whole vehicle » existe dans
CTLDCrateManager:refreshRequestEquipmentSectionmais est actuellement inerte : le constructeur de menu fixespawnAsVehicle = falsepour chaque entrée, de sorte que Request Equipment (order 25) ne spawne jamais que des crates. Le chargement de véhicules entiers est piloté par le menu Vehicle Commands dédié, à l'encontre de véhicules déjàWAITING.
Pipeline de chargement — loadVehicle(vehicle, transport, player, method)¶
- Rejeter sauf si le véhicule est
WAITING. - Garde-fou de capacité
menu_ctld: lirecaps.maxWholeVehiclesOnboard(défaut1) depuis l'entréecapabilitiesByTypedu transport, compterfindLoadedVehicles(transport), et refuser avec un message au joueur si la soute est pleine. (La capacitédcs_nativeest laissée à DCS.) - Si le véhicule est un JTAC enregistré, suspendre le lasing (
setJTACInTransit). menu_ctld: détruire l'unité DCS, publierOnGroundUnitRemoved(reason = "loaded"), et vider la recherche inverse.dcs_native: garder l'unité vivante mais la retirer de la recherche inverse afin qu'unS_EVENT_DEADobsolète ne se déclenche pas à tort.- Enregistrer
loadMethod,loadTransportName,loadTime; passer à l'étatLOADED. menu_ctlduniquement : recalculer le poids de cargo interne du transport (_updateVehicleCargo) et envoyer un message de confirmation.- Publier
OnVehicleLoaded.
La distance de ramassage maximale n'est pas appliquée dans loadVehicle ; elle l'est en amont
dans findLoadableVehicles (≤ maximumDistancePackableUnitsSearch, défaut 200 m).
Pipeline de déchargement — unloadVehicle(vehicle, transport, player, method, rearSector)¶
- Rejeter sauf si le véhicule est
LOADED. - Calculer une position de spawn avec
_computeSpawnPosition(transport, rearSector). - Un véhicule en carry virtuel (
loadMethod ~= "dcs_native") est respawné près du transport avecctld.utils.dynAdd, en réutilisantspawnData.groupName/spawnData.unitName, quel que soit lemethodpublié. Un véhicule en carry natif (loadMethod == "dcs_native") n'est jamais respawné : l'unité vivante que DCS a gardée est récupérée viaGroup.getByName(spawnData.groupName). Le mécanisme découle de la façon dont le véhicule a été chargé, pas du motif publié. - Repasser à l'état
WAITING(FALLINGpour un véhicule en carry natif libéré avecmethod = "parachute") ; ré-enregistrer la recherche inverse. - Carry virtuel : recalculer le poids de cargo (le poids d'un carry natif est laissé à DCS). Reprendre le lasing JTAC le cas échéant (un véhicule en chute le reprend à l'atterrissage).
menu_ctlduniquement : envoyer un message de confirmation.- Publier
OnVehicleUnloaded; pour un véhicule libéré en vol, démarrer le suivi d'atterrissage (_scheduleNativeLandingPoll).
Le brouillon du guide développeur décrivait le déchargement comme un respawn via
CTLDObjectRegistry.spawnObject; le code actuel respawne viactld.utils.dynAddet n'utilise pas l'object registry ici.
Positionnement au spawn¶
_computeSpawnPosition(transport, rearSector) (wrapper public computeSafeDropPos) place l'unité
dans un secteur de ±45° par rapport au cap du transport — le secteur avant par défaut, ou le
secteur arrière (hdg + π) quand rearSector vaut vrai, ce qui dégage le drop de la
trajectoire de décollage d'un hélicoptère IA. Le décalage radial provient de _secureOffset, la
diagonale de la bounding box sqrt(halfLen² + halfWid²) × 2 + 10 m, avec un repli à 60 m lorsque
getDesc().box est indisponible.
Chargement et libération de véhicule entier natif DCS — _checkNativeLoading (tick 1 s)¶
Un véhicule en carry natif est lu dans la liste que DCS déclare à bord de l'appareil, pas par géométrie (ADR 0022). À chaque tick :
- Collecter les véhicules
WAITINGavec unités vivantes, et les véhiculesLOADEDdontloadMethod == "dcs_native". S'il n'y en a aucun, le tick retourne avant de lire une liste. - Les candidats sont les unités joueur (
CTLDPlayerManager._players; un appareil piloté par l'IA n'est jamais scanné), de toute catégorie, dont l'entréecapabilitiesByTypefixeuseNativeDcsCargoSystemetcanTransportWholeVehicle. Leur liste est lue parctld.utils.getOnBoardCargo(unit). Si elle est illisible, un seul avertissement est journalisé pour le type et le type n'est plus surveillé (_unreadableCargoTypes) ; il n'y a aucun repli géométrique. - Entrée : un véhicule entier apparaît comme une entrée compagnon nommée
CRG:<nom d'unité>. Si elle correspond à un véhiculeWAITING, appelerloadVehicle(veh, transport, nil, "dcs_native"). Aucun test de position, de sol, de vitesse ou de coalition : DCS décide de ce qu'il accepte. Une entrée qui n'est pas un véhicule en attente (cargo créé par la tablette de loadmaster, caisse d'éditeur inconnue) est ignorée avec une seule trace debug (_ignoredCargoNames). - Libération : un véhicule en carry natif dont l'entrée a quitté la liste de son transport est libéré par
unloadVehicle:method = "dcs_native"transport au sol (WAITINGde nouveau aussitôt), oumethod = "parachute"sictld.utils.inAir(transport)(étatFALLING)._scheduleNativeLandingPollsuit ensuite l'unité (vivante pendant toute la chute) jusqu'à ce quectld.utils.hasLandedla dise au sol (critère des caisses parachutées), puis reprend son JTAC. Une unité disparue est abandonnée comme tout véhicule perdu (_dropLostVehicle: JTAC désenregistré,OnVehicleDead). - Transport disparu (changement de slot, despawn : pas d'événement de mort) : un véhicule en carry natif
dont le transport enregistré n'existe plus subit le même
_dropLostVehiclequ'un transport détruit, une fois. Un transport dont la liste n'a pas été lue à ce tick ne libère rien. Un véhicule déjàFALLINGn'est pas affecté.
Un véhicule libéré ne peut pas être rechargé par CTLD : il n'est rechargé qu'en réapparaissant dans la liste.
Filtrage des chargeables — findLoadableVehicles / _isTypeLoadable¶
findLoadableVehicles(transport) retourne les véhicules WAITING qu'un transport donné peut
charger, en appliquant trois filtres par-dessus la porte de capacité :
- L'entrée
capabilitiesByTypedu transport doit fixercanTransportWholeVehicle, sinon{}. - Coalition (GAP-Q1) :
veh.spawnData.coalitionIddoit être égal à la coalition du transport. - Type (GAP-Q2) :
_isTypeLoadableexige queveh.vehicleTypefigure dans la listeloadableVehiclesRED(coalition 1) ouloadableVehiclesBLUE(coalition 2) du transport. - Distance : ≤
maximumDistancePackableUnitsSearch.
Les références d'unité des véhicules sont résolues paresseusement dans la boucle (Group.getByName
peut avoir une frame de retard sur coalition.addGroup).
Capacités de transport¶
Le comportement des véhicules entiers est piloté par l'entrée capabilitiesByType du transport
(ctld.gs("capabilitiesByType")), plus le flag par joueur canCarryVehicles :
| Champ | Effet |
|---|---|
canTransportWholeVehicle |
Active le chargement/déchargement de véhicule entier pour ce type |
maxWholeVehiclesOnboard |
Capacité simultanée en véhicules entiers (0 = aucune) |
loadableVehiclesRED / loadableVehiclesBLUE |
Noms de type DCS que cet appareil peut charger, par coalition |
canParachuteDrop |
Active l'entrée Parachute Vehicle |
useNativeDcsCargoSystem |
Marque le type comme porteur à cargo natif |
Packing d'un véhicule en crates — packVehicle¶
findPackableVehicles(transport) liste les véhicules WAITING gérés par CTLD situés à moins de
maximumDistancePackableUnitsSearch (uniquement les véhicules suivis — jamais des props de scène
arbitraires). packVehicle(transportUnitName, packableUnitName, playerObj) :
- Résoudre le descripteur pour le type d'unité (
findDescriptorByUnitType) ; refuser s'il n'y en a aucun. - Désenregistrer silencieusement tout JTAC sur l'unité, puis la
destroy()et publierOnGroundUnitRemoved(reason = "packed"). - Spawner
descriptor.cratesRequiredcrates viaCTLDCrateManager:spawnCratesAligned(...)avecCTLDCrate.SPAWN_METHOD.VEHICLE_PACK, dans le secteur avant (hélicoptère) ou le secteur arrière (appareil à cargo natif). - Publier
OnVehiclePacked, puis reconstruire le menu du joueur une frame plus tard (pour éviter la frame de retard decoalition.getGroups()aprèsdestroy()).
Le rafraîchissement du menu de pack déclenché à l'atterrissage (_checkPackingLanding, tick 3 s)
est conditionné à ctld.gs("enablePackingVehicles").
Largage en parachute — parachuteVehicle¶
Requiert caps.canParachuteDrop. Vérifie l'AGL par rapport à parachuteMinAltitudeVehicles
(défaut 30 m) et refuse si trop bas. Résout le véhicule (id explicite, sinon le premier véhicule
chargé sur ce transport), calcule le point d'atterrissage avec ctld.utils.calcDropPosition en
utilisant parachuteDescentRateVehicles (défaut 8 m/s), repasse le véhicule à WAITING, publie
OnVehicleParachuting, et après le délai de descente le respawne à la position d'atterrissage et
publie OnVehicleParachuteLanded (en reprenant le lasing JTAC le cas échéant).
Poids de cargo¶
getLoadedVehicleWeight(transportUnitName) somme ctld.gs("groundVehicleWeights") pour les
véhicules LOADED en menu_ctld uniquement (défaut 2500 kg par type inconnu) ; le poids
dcs_native est laissé à DCS. _updateVehicleCargo délègue à ctld.utils.updateTransportWeight,
qui agrège troops, crates et véhicules en un unique appel setUnitInternalCargo.
Menu F10 — buildMenuSection¶
Ajouté uniquement lorsque l'unité du joueur possède canCarryVehicles. Il crée Vehicle Commands
(order = 30) avec trois branches dynamiques :
- Load / Extract Vehicles — reconstruit par
refreshLoadSection; désactivé en vol ; liste le résultat defindLoadableVehicles. Sélectionner une entrée appelleloadVehicle(..., "menu_ctld"). - Unload Vehicles — reconstruit par
refreshUnloadSection; masqué quand rien n'est chargé, affiche « Land to unload vehicles » en vol, sinon liste les véhicules chargés. - Parachute Vehicle — présent uniquement quand
caps.canParachuteDrop; activé uniquement en vol avec un véhicule chargé.
Les rafraîchissements sont pilotés par le détecteur d'atterrissage (_checkPackingLanding), les
événements de bus de spawn/retrait d'unité terrestre (OnGroundUnitSpawned / OnGroundUnitRemoved),
et les événements de load/unload eux-mêmes. Un timer d'indice de vol stationnaire
(_checkVehicleHoverHint, tick 5 s, cooldown de 30 s par joueur) incite un porteur en vol
stationnaire à se poser au-dessus d'un véhicule WAITING voisin.
Gestion des événements DCS¶
onDead(S_EVENT_DEAD) — si l'unité morte est un véhicule suivi, la retirer et publierOnVehicleDead+OnGroundUnitRemoved. Sinon, s'il s'agit d'un transport qui portait des véhiculesLOADED, chaque véhicule transporté est considéré perdu : son JTAC est désenregistré etOnVehicleDeadest publié pour lui.onBirth(S_EVENT_BIRTH) — voir la source d'activation retardée ci-dessus.
Événements publiés¶
| Événement | Quand |
|---|---|
OnVehicleSpawnedForTransport |
spawnVehicleForTransport a créé un véhicule WAITING |
OnVehicleLoaded |
Véhicule chargé dans un transport |
OnVehicleUnloaded |
Véhicule déchargé / largué d'un transport |
OnVehicleDead |
Véhicule suivi détruit, ou perdu avec son transport |
OnVehiclePacked |
Véhicule packé en crates |
OnVehicleParachuting |
Largage en parachute démarré |
OnVehicleParachuteLanded |
Véhicule parachuté arrivé au sol |
OnGroundUnitSpawned / OnGroundUnitRemoved |
Une unité terrestre suivie est apparue / a disparu (pilote le rafraîchissement du menu) |
Voir Events pour le catalogue complet du bus et la forme des payloads.