Pipeline de spawn des crates¶
Les crates sont la monnaie physique de la logistique CTLD : un transport demande de
l'équipement sous forme de crate (ou d'un ensemble de crates), le transporte jusqu'à une
destination, le dépose ou le sling-load, puis le unpack en un véhicule, un JTAC ou une scène.
Tout le domaine réside dans src/CTLD_crate.lua, qui définit l'entité CTLDCrate et le
singleton CTLDCrateManager.
Cette page suit une crate de sa configuration jusqu'à ses contenus déployés. Pour les signatures des méthodes publiques du manager, voir la référence de l'API ; pour les événements qu'il publie, voir Events ; pour les scènes et véhicules qu'il alimente, voir Scenes et Vehicles.
L'entité CTLDCrate¶
CTLDCrate = class() modélise un static de cargo et son cycle de vie. Une crate est créée avec
CTLDCrate:new(data) (jamais via les internes du CTLDCrateManager directement) et porte son
descripteur, sa position, sa coalition, sa méthode de spawn et le handle du StaticObject DCS.
Le cycle de vie est une petite machine à états conservée dans crate.state, tirée de
CTLDCrate.STATE :
| État | Signification |
|---|---|
spawned |
Au sol, fraîchement créée, jamais déplacée |
loaded |
À l'intérieur / attachée à un transport |
falling |
En l'air, en descente (drop ou parachute) |
landed |
Au sol après un cycle de transport |
unpacked |
Contenus déployés — état terminal |
Les transitions sont pilotées par des méthodes d'entité, dont chacune positionne state et les
champs pertinents : load(transport), unload(position), drop(position),
startParachute(altitude), land(position) et unpack(). destroy() supprime le static DCS
sous-jacent.
Deux prédicats conditionnent les actions de menu. isOnGround() renvoie vrai uniquement dans
l'état spawned ou landed et vérifie dcsStatic:isExist() lorsqu'un handle de static est
présent — cela protège contre les crates détruites au combat avant qu'un S_EVENT_DEAD n'ait pu
les désenregistrer. canUnpack() combine isOnGround() avec le flag canBeUnpacked.
isLoadedByCTLD() renvoie vrai dès que la crate est loaded, couvrant à la fois les loads via
le menu CTLD et les loads natifs DCS migrés dans CTLD ; utilisez loadedByDCSNative pour
distinguer les deux.
Chaque crate enregistre son origine dans crate.spawnMethod, l'une des valeurs de
CTLDCrate.SPAWN_METHOD :
| Valeur | Origine |
|---|---|
crate_spawn |
Spawnée depuis le menu F10 Request Equipment |
vehicle_pack |
Résultat du pack d'un véhicule |
mission_maker |
Pré-placée par le mission maker (détectée via INIT-B) |
menu_ctld |
Spawnée via une action de menu CTLD |
Pré-traitement de la configuration : _processSpawnableCrates¶
CTLDCrateManager.getInstance() appelle _processSpawnableCrates() une seule fois au premier
accès. Elle lit la configuration brute ctld.gs("spawnableCrates") et la transforme en deux
structures internes sur lesquelles s'appuient le constructeur de menu et chaque lookup de
descripteur :
self._processedCrates[category] = { singleCrates = { {singleCrate, singleTypeSet?}, … }, mixedSets = { … } }self._weightIndex[weight] = descriptor— un index O(1) utilisé par toutes les méthodesfindDescriptorBy*(crates simples uniquement ; les mixed sets n'ont pas de champweight).
Avant la boucle principale, CTLDCrateAssemblyManager.injectAACrates(spawnableCrates) injecte
les entrées de crates de pièces/réparation des systèmes AA afin qu'elles transitent par les
mêmes passes. La transformation exécute ensuite trois passes par catégorie :
- Séparation. Chaque entrée avec un champ
weightdevient une crate simple (indexée dans uncatWeightIdxpropre à la catégorie et dansself._weightIndex) ; chaque entrée avec un champmixedSetdevient un mixed set. Les entrées avec ni l'un ni l'autre sont journalisées en warning et ignorées. - Génération de
singleTypeSet. Pour chaque crate simple dontcratesRequired > 1, lorsqueenableAllCratesn'est pasfalseet que leshowSetsde la crate n'est pasfalse, unsingleTypeSetauto-généré est stocké à côté d'elle :multipleest le weight répétécratesRequiredfois, etdescest la description de la crate plus un suffixe i18n-aware" - " .. ctld.tr("All crates"). PositionnershowSets = falsesur une crate supprime cette entrée même lorsqueenableAllCratesest activé. - Validation des mixed sets. Chaque weight d'un
mixedSetest vérifié par rapport à l'index de la catégorie. Tout weight non résolu invalide l'ensemble du set (exclu du menu) et met en file un warning mission-maker affiché viatrigger.action.outTextau démarrage. Les sets valides sont copiés avecmixedSetaliasé surmultiplepour la compatibilité de spawn.
Les scene crates enregistrées avant l'init sont fusionnées ensuite via _injectSceneCrate ; les
scènes enregistrées plus tard sont injectées de façon incrémentale par
CTLDSceneManager:registerSceneModel. Les collisions de weight lors de l'injection sont résolues
en avançant jusqu'au prochain slot libre dans la même plage 1001.xx et en journalisant un
WARN. Les scènes désactivées par l'audit des mods sont purgées par _purgeDisabledScenes.
Du menu au sol : spawnCrate¶
Une requête de spawn passe par spawnCrate(descriptor, position, coalitionId, spawnedBy,
spawnMethod, countryId, modelKey). Elle délègue la création du static à _spawnStatic, qui :
- résout le modèle de crate depuis
ctld.gs("spawnableCratesModels")parmodelKey, avec un fallback sur"load"; - dérive le country de la coalition lorsque
countryIdest nil ; - construit un nom unique assaini (
CTLD_<label>_<uid>ouCTLD_Crate_<uid>) ; et - appelle
ctld.utils.dynAddStatic(jamaiscoalition.addStaticObjectdirectement) à l'intérieur d'unpcall, renvoyant le nom et le handle duStaticObject.
spawnCrate enveloppe ensuite le static dans un CTLDCrate, l'enregistre, publie
OnCrateSpawned et planifie un _refreshNearbyPlayers quasi-immédiat afin que les transports
proches voient la nouvelle crate dans leur sous-menu Load Crate. spawnCratesAligned est le point
d'entrée par lot : il choisit un axe de spawn aléatoire (secteur avant pour les transports
standard, secteur arrière pour ceux capables de native-cargo), calcule des positions sans
chevauchement en évitant les bounding boxes des autres aéronefs à cargo dynamique, et appelle
spawnCrate pour chaque descripteur. La clé de modèle est choisie par _crateModelKey : "sling"
lorsque slingLoad est positionné, "dynamic" pour les transports capables de native-cargo,
sinon "load".
Transitions de cycle de vie sur le manager¶
Le manager reflète les transitions de l'entité et détient la publication des événements :
| Méthode | Transition | Publie |
|---|---|---|
loadCrate(crateName, transport) |
spawned/landed → loaded (détruit le static) |
OnCrateLoaded, OnCrateCleared |
unloadCrate(crateName, position, method) |
loaded → landed (respawn le static) |
OnCrateUnloaded, OnCrateSpawned |
unpackCrate(crateName, unpacker) |
spawned/landed → unpacked (détruit + désenregistre) |
OnCrateUnpacked, OnCrateCleared |
dropCrate(crateName, altitudeAGL) |
→ falling |
(événements drop/parachute) |
destroyCrate(crateName) |
détruit + désenregistre | OnCrateCleared |
Le load et le unload appellent tous deux ctld.utils.updateTransportWeight pour que la masse du
transport reflète son cargo. Comme le static DCS est détruit au load, unloadCrate le recrée via
_respawnStatic, qui génère un nouveau nom unique et ré-indexe self.crates sous celui-ci.
Le pipeline de unpack : _spawnUnpacked¶
Chaque issue de unpack — véhicule au sol, JTAC aérien ou static — est déployée via un unique
pipeline en trois étapes dans _spawnUnpacked(desc, pos, coa, cId, playerName) :
_spawnUnpacked(desc, pos, coa, cId, playerName)
├── spawnAs ≠ "GROUND"/"STATIC" (air) + JTAC_dropEnabled == false → skip
├── desc.isJTAC → CTLDJTACManager:_consumeJTACSlot(coa) ← quota gate (définitif)
│ └── limite atteinte → notifie le joueur + return (pas de spawn)
├── ctld.utils.buildGroupUnitDef(desc, pos, gname, gid, uid)
│ ├── spawnAs == "GROUND" → minimal {name, task, units[{x,y,heading}]}
│ └── spawnAs == "AIRPLANE" → complet {groupId, units[{alt,speed}], route[orbit+EPLRS]}
│ (orbit + EPLRS embarqués uniquement quand isJTAC = true)
├── ctld.utils.spawnFromDescriptor(desc, cId, unitDef)
│ ├── spawnAs == "STATIC" → coalition.addStaticObject
│ └── sinon → coalition.addGroup(Group.Category[spawnAs])
└── _dispatchPostSpawn(desc, gname)
└── isJTAC = true → CTLDJTACManager:startLase(gname)
Les spawns au sol publient en plus OnGroundUnitSpawned. En cas de succès, lorsqu'un playerName
est fourni, les sous-menus load et pack du spawner de véhicules sont rafraîchis.
Règles clés :
coalition.addGroupetcoalition.addStaticObjectne doivent être atteints que viactld.utils.spawnFromDescriptor— jamais appelés directement.ctld.utils.buildGroupUnitDefest le constructeur unique des définitions d'unités GROUND et AIR. Les objets STATIC utilisent un schéma séparé et vont directement àaddStaticObject.- L'activation de rôle post-spawn appartient exclusivement à
_dispatchPostSpawn. Cette méthode démarre le lasing JTAC pour les descripteursisJTACet enregistre à la fois les JTAC et les véhicules au sol ordinaires auprès deCTLDVehicleSpawnerafin que le menu Load/Unload puisse les suivre. N'ajoutez pas de logique de rôle ailleurs dans le chemin de unpack. - Le quota JTAC (
JTAC_LIMIT_RED/JTAC_LIMIT_BLUE) est consommé avant le spawn, ici comme dans le chemin Request Equipment (spawnJTACFromDescriptor). Le quota est définitif — il n'est jamais réapprovisionné lorsqu'un JTAC est tué. Les JTAC de mission-maker et les soldats JTAC d'infanterie ne le consomment pas.
Champs de descripteur pilotant le pipeline¶
| Champ | Effet sur le pipeline |
|---|---|
spawnAs (string, défaut "GROUND") |
Sélectionne la catégorie addGroup ou route vers addStaticObject |
isJTAC (boolean) |
Source de vérité pour le rôle JTAC. Ajoute la route d'orbit à une définition d'unité aérienne et déclenche startLase en post-spawn. Le nom de type d'unité (unit) n'est jamais utilisé pour la détection JTAC dans la stack OOP. |
specificParams (table, air uniquement) |
Réglage d'orbit passé à startLase / deployAirJTAC : speed, alti, orbitRadiusNoLase, orbitRadiusOnLase |
cratesRequired (number) |
Conditionne le unpack — le compte doit être atteint avant que le pipeline ne s'exécute |
showSets (boolean, défaut true) |
Lorsque false, supprime l'entrée singleTypeSet « All crates » auto-générée même si enableAllCrates est activé |
Règles de détection JTAC (ne pas inverser)¶
| Contexte | Règle |
|---|---|
| Peuplement du menu Request Equipment | getJTACDescriptors(coalition) itère _processedCrates et renvoie les entrées de crate simple avec isJTAC = true pour la coalition du joueur (ou side = nil) |
| Spawn Request Equipment | CTLDVehicleSpawner:spawnJTACFromDescriptor(desc, spawner, zone) — vérification du quota → sol : spawnVehicleForTransport + startLase ; air : deployAirJTAC |
| Activation post-unpack | _dispatchPostSpawn : if desc.isJTAC → CTLDJTACManager:startLase() |
| Détection de groupe MM pré-placé | Le nom du groupe contient "jtac" (insensible à la casse) — type d'unité non utilisé |
| Deploy de troops avec un soldat JTAC | tmpl.hasJtac == true (calculé depuis jtac > 0) → startLase après le deploy |
N'ajoutez pas de nouveaux chemins de détection JTAC. Si un nouveau type d'unité a besoin du comportement JTAC, positionnez
isJTAC = truesur son descripteur de crate — ne l'ajoutez jamais à une liste de noms de type.JTAC_unitTypeNamesa été supprimé : le catalogue de crates est la source de vérité unique tant pour le menu de crates que pour le menu Request Equipment.
Intégration au menu F10¶
buildMenuSection(playerObj, menu) construit le sous-menu Request Equipment (order = 25,
entre les commandes Troop et Vehicle) et le sous-menu Crate Commands (order = 40),
conditionnés par le capabilitiesByType[typeName].cratesEnabled du joueur. Les sous-menus
dynamiques sont reconstruits à la demande :
refreshRequestEquipmentSectionrend lessingleCratesde chaque catégorie (chacune suivie immédiatement de sonsingleTypeSetlorsqu'il est visible) puis sesmixedSets, avec un filtrage coalition/JTAC par joueur et un compteur d'ordre stable.refreshLoadCrateSectionregroupe les crates situées à moins de 50 m par type de descripteur avec un compte ; le load choisit la crate correspondante la plus proche et applique lemaxCratesOnboarddu transport.refreshUnpackSectionliste les sets assemblables (compte ≥cratesRequired) à moins de 300 m, en déléguant les sets AA àCTLDCrateAssemblyManager:tryUnpackOrRepair, les scene crates àCTLDSceneManager:playScene(ou aucrate.unpackpropre à un modèle), et les véhicules standard à_spawnUnpacked.refreshPackEquiptSectionaffiche le sous-menu unifié Pack Equipt (scènes FARP et véhicules packables) uniquement au sol, lorsqueenableFARPRepackouenablePackingVehiclesest positionné.
Un poller de position au sol (10 s) rafraîchit Request Equipment uniquement lorsque l'ensemble des logistic-zones du joueur change, évitant une reconstruction à cadence fixe qui éjecterait les joueurs des sous-menus ouverts. Les événements de spawn et de clear de crate rafraîchissent les sous-menus Load Crate et Unpack pour chaque transport à portée.