mirror of
https://github.com/ciribob/DCS-CTLD.git
synced 2026-08-11 19:57:28 +00:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9302203f09 | |||
| e59bc50e6c | |||
| c12c0b242d | |||
| 659268d3f5 | |||
| c9270a0fd2 | |||
| def40f440c | |||
| 417c57f97c | |||
| dec5bf02b4 | |||
| 68693300dc | |||
| f7f603c486 | |||
| 290950a16b | |||
| 041a9ab6af | |||
| 8d73c966db | |||
| af3e4fab17 | |||
| 97e7c72144 | |||
| 65336fdab8 | |||
| 61db3e9f93 | |||
| a1da0ea1c4 | |||
| 782940265d | |||
| b5cd8929f3 | |||
| 1826d28a86 | |||
| 359bf1418d | |||
| e3424c6af5 | |||
| 1ac3b3f8a9 | |||
| e13e630830 | |||
| 7bfed7ef55 | |||
| 8bfaba399f | |||
| 4dada3d24d | |||
| 74b40fe3cb | |||
| aa8bc78b27 | |||
| 7286a6a010 | |||
| c8bb41648c | |||
| 08635622b0 | |||
| 42e8549649 | |||
| 5c48a9d756 | |||
| f3983c6a90 | |||
| 1c66408127 | |||
| 3ac2b3b22c | |||
| c576268d87 | |||
| 35f52d543a | |||
| c608d00d15 | |||
| d2789f554a | |||
| fcef6a66d1 | |||
| dd71451194 | |||
| 807e81e389 | |||
| 33d8810ebb | |||
| 87e8a31f31 | |||
| e4a0fff931 | |||
| ca5bf76c78 | |||
| f8659c5de6 | |||
| 9a0cc8eeee | |||
| a706890cb6 | |||
| 815684dd77 | |||
| 0ccb2ba7bb | |||
| 1e471df264 | |||
| cc8b916870 | |||
| cd62d0c60a | |||
| a35e023752 | |||
| f71e7f3213 | |||
| a468faefec | |||
| 99f2c7aec0 | |||
| 7db8e2d772 | |||
| 026099bd12 | |||
| 82a330e61a | |||
| b3470250e4 | |||
| bbde4ae62b | |||
| 540a316754 | |||
| 574217d931 | |||
| 1fce52088d | |||
| 4d704a489e | |||
| da80f9ea06 | |||
| 93260908c4 | |||
| 3c261e8dc2 | |||
| 7b7eb62bf0 | |||
| 951ee37903 | |||
| 3a7ab50834 | |||
| 9a69d4b52c | |||
| 0345d16150 | |||
| d071fba20f | |||
| 1a32ce6b57 | |||
| 85275ad84b | |||
| 819ba2de53 | |||
| 8f34ad3902 | |||
| 16f366d039 | |||
| 2524320096 | |||
| e6b8c9ba14 | |||
| a5b47503be | |||
| aaa25dcd41 | |||
| e834dd1395 | |||
| 774e499759 | |||
| 0a1f4de471 | |||
| f9cd8b8b51 | |||
| 3eef8d9a5d | |||
| e1b9e1d7d8 | |||
| ca976f4387 | |||
| 91f74b73ed | |||
| f8df243043 | |||
| 29b19d5262 | |||
| 48aca9d488 | |||
| 12741d0532 | |||
| 6df583b9f1 | |||
| ca99c58f98 | |||
| 879272a2e8 | |||
| 267156e04e | |||
| 6fa12ca46f | |||
| eb22f5dc67 | |||
| 601805abb0 | |||
| e0e4279f20 | |||
| 9023c571ba | |||
| 72aab26e43 | |||
| aef146a6d8 | |||
| d8db662dc5 | |||
| e4f13f98b1 | |||
| c1aba95b6f | |||
| d50bcfccd9 | |||
| f0f095e274 | |||
| 69f04dfd57 | |||
| 546f3feaae | |||
| 23e83bf53a | |||
| ee07423588 | |||
| 96d801c342 | |||
| b1818d9fb3 | |||
| 2bc6e76516 | |||
| 61a1fd42ee | |||
| 954a6b0fad | |||
| 159915a6ac | |||
| d029e2479b | |||
| 251467fee4 | |||
| f3a9b44388 | |||
| f0adb72c49 | |||
| ecc47bb247 | |||
| 0c74729837 | |||
| ef1d3d57e5 | |||
| b69f20eb6b | |||
| d80a70c0ee | |||
| 80fe5d59b0 | |||
| 3eaa08b74e | |||
| f83365ec63 | |||
| 0b3b1ea1f8 | |||
| 888d949234 | |||
| 75c9d0bf68 | |||
| 7517eaf188 | |||
| 1181fcfff7 | |||
| 9def2f1a3a | |||
| 9c25240c43 | |||
| 8a17ff52aa | |||
| d125cdbe80 | |||
| 8dcaa3e0bb | |||
| baf1231141 | |||
| d1d9b55769 | |||
| ff06dcecf7 | |||
| dcdf6438a1 | |||
| 4d0062dee3 | |||
| c618f4b282 | |||
| b390d5dabf | |||
| f4b81fd101 | |||
| 78aa8a010e | |||
| 6996769cec | |||
| 0d9c01283c | |||
| fa1553a83e | |||
| 4db3c71728 | |||
| 0180cf33d2 | |||
| c51a3d80ca | |||
| 9eb4fc66b5 | |||
| 8f2a335609 | |||
| c5bae34ed1 | |||
| 6fc9f146ae | |||
| b6232d3c15 | |||
| 111fd73b94 | |||
| 61d9e206b2 | |||
| 08ebd7e82d | |||
| f152f58a97 | |||
| 38e86bead3 | |||
| b0d8f8b5c7 | |||
| a58efad985 | |||
| 0793d3bda5 | |||
| c397bad107 | |||
| 791ee4e63e | |||
| 5aa37c52a4 | |||
| fb26645996 | |||
| 3a3bcaf568 | |||
| fb804b4a22 | |||
| 665bdddbdd | |||
| 6beaedb79a | |||
| 9c2f982d03 |
@@ -0,0 +1,265 @@
|
||||
# DCS-CTLD — As-Is Analysis
|
||||
|
||||
> This document captures the current state of the CTLD codebase prior to
|
||||
> the v2 modernization effort. It serves as a reference and justification
|
||||
> for the changes outlined in `MODERNIZATION-PLAN.md`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
| Metric | Value |
|
||||
| ------ | ----- |
|
||||
| Primary script | `CTLD.lua` |
|
||||
| Lines of code | ~8 700 |
|
||||
| i18n script | `CTLD-i18n.lua` |
|
||||
| Bundled dependency | `mist.lua` (MIST 4.5 build 128-DYNSLOTS-02) |
|
||||
| Lua version | 5.1 (DCS sandbox) |
|
||||
| Public functions (`ctld.*`) | 100+ |
|
||||
| Local / private functions | 0 (everything is public on the `ctld` table) |
|
||||
| OOP / metatables usage | None — fully procedural |
|
||||
| Unit tests | None |
|
||||
|
||||
---
|
||||
|
||||
## 2. File structure
|
||||
|
||||
```text
|
||||
.
|
||||
├── CTLD.lua -- Main script (monolith)
|
||||
├── CTLD-i18n.lua -- Translation tables (FR, ES, KO)
|
||||
├── mist.lua -- MIST library (stock, not customized)
|
||||
├── beacon.ogg -- Radio beacon sound
|
||||
├── beaconsilent.ogg -- Silent beacon sound (FC3)
|
||||
├── README.md -- User / mission-maker guide
|
||||
├── demo-mission.miz
|
||||
├── test-mission.miz
|
||||
├── test-dev-dynamic.miz
|
||||
├── test-dev-static.miz
|
||||
├── test_witchcraft_#131.miz
|
||||
└── test_witchcraft_#137 et #149.miz
|
||||
```
|
||||
|
||||
No `src/`, no `test/`, no `docs/`, no CI configuration.
|
||||
|
||||
---
|
||||
|
||||
## 3. CTLD.lua section breakdown
|
||||
|
||||
| Section | Lines | Content |
|
||||
| ------- | ----- | ------- |
|
||||
| Header + contributors | 1–35 | Credits, links |
|
||||
| i18n table setup | 36–360 | English reference keys (all `= ""`), `i18n_translate()` |
|
||||
| USER CONFIGURATION | 403–600 | 50+ tunable parameters |
|
||||
| Crate configuration | 601–1 200 | `ctld.spawnableCrates`, AA system templates |
|
||||
| Public API functions | 1 200–1 950 | Functions usable from DO SCRIPT triggers |
|
||||
| Core gameplay logic | 1 950–4 600 | Troops, vehicles, crates, FOBs, beacons |
|
||||
| JTAC subsystem | 4 600–7 000 | Targeting, lasing, IR points, F10 menus |
|
||||
| AI processing + menus | 7 000–8 200 | AI auto-load/unload, F10 menu building |
|
||||
| `ctld.initialize()` | 8 190–8 400 | State tables init, zone parsing |
|
||||
| Event handler + tools | 8 400–8 705 | DCS event handler, `ctld.tools` utilities |
|
||||
|
||||
---
|
||||
|
||||
## 4. State management
|
||||
|
||||
34 global tables inside the `ctld` namespace track runtime state.
|
||||
Most are duplicated per coalition.
|
||||
|
||||
### 4.1 — Coalition-duplicated tables (RED / BLUE pairs)
|
||||
|
||||
| Table pair | Content |
|
||||
| ---------- | ------- |
|
||||
| `ctld.spawnedCratesRED` / `BLUE` | Spawned crate statics |
|
||||
| `ctld.droppedTroopsRED` / `BLUE` | Deployed infantry groups |
|
||||
| `ctld.droppedVehiclesRED` / `BLUE` | Deployed vehicles |
|
||||
| `ctld.droppedFOBCratesRED` / `BLUE` | FOB crates on ground |
|
||||
|
||||
This duplication creates **50+ `if coalition == 1 then … RED … else … BLUE`** branches.
|
||||
|
||||
### 4.2 — Shared tables
|
||||
|
||||
| Table | Content |
|
||||
| ----- | ------- |
|
||||
| `ctld.inTransitTroops` | Cargo currently onboard (keyed by unit name) |
|
||||
| `ctld.inTransitSlingLoadCrates` | Simulated sling-load crates |
|
||||
| `ctld.builtFOBS` | Completed FOB positions |
|
||||
| `ctld.completeAASystems` | Assembled AA system groups |
|
||||
| `ctld.deployedRadioBeacons` | Active beacons |
|
||||
| `ctld.fobBeacons` | FOB beacon cache (refreshed every 60 s) |
|
||||
| `ctld.extractZones` | Extract zone definitions |
|
||||
| `ctld.hoverStatus` | Hover-over-crate tracking |
|
||||
| `ctld.crateLookupTable` | Crate weight → type lookup |
|
||||
| `ctld.callbacks` | Registered callback functions |
|
||||
| `ctld.jtacUnits` | JTAC unit references |
|
||||
| `ctld.jtacCurrentTargets` | JTAC active targets |
|
||||
| `ctld.jtacSelectedTarget` | Player-selected JTAC targets |
|
||||
| `ctld.jtacGeneratedLaserCodes` | Allocated laser codes |
|
||||
| `ctld.usedUHFFrequencies` / `VHF` / `FM` | Radio frequency pools |
|
||||
|
||||
---
|
||||
|
||||
## 5. Code duplication patterns
|
||||
|
||||
### 5.1 — RED/BLUE branching (most pervasive)
|
||||
|
||||
```lua
|
||||
-- This pattern appears 50+ times
|
||||
if _heli:getCoalition() == 1 then
|
||||
_list = ctld.droppedTroopsRED
|
||||
else
|
||||
_list = ctld.droppedTroopsBLUE
|
||||
end
|
||||
```
|
||||
|
||||
### 5.2 — Troop / Vehicle symmetry
|
||||
|
||||
Many functions have near-identical troop and vehicle variants differentiated
|
||||
only by a boolean parameter (`true` = troops, `false` = vehicles).
|
||||
|
||||
### 5.3 — Sling load vs simulated load
|
||||
|
||||
Two code paths for crate pickup depending on `ctld.slingLoad`:
|
||||
|
||||
- Real sling load (DCS native, crashy)
|
||||
- Simulated hover load (custom implementation)
|
||||
|
||||
### 5.4 — Zone handling
|
||||
|
||||
Pickup zones, dropoff zones, and waypoint zones share similar parsing
|
||||
(smoke color, coalition check, active flag) but are implemented separately.
|
||||
|
||||
---
|
||||
|
||||
## 6. MIST dependency
|
||||
|
||||
91 calls to `mist.*` functions across the codebase.
|
||||
|
||||
| Category | Functions | Call count |
|
||||
| -------- | --------- | ---------- |
|
||||
| Utility | `deepCopy`, `round`, `makeVec2/3` | ~28 |
|
||||
| Distance / vectors | `get2DDist`, `vec.mag`, `vec.dp`, `vec.sub` | ~15 |
|
||||
| Heading / bearing | `getHeading` | ~8 |
|
||||
| Coordinate format | `tostringLL`, `tostringMGRS` | ~4 |
|
||||
| Spawning | `dynAdd`, `dynAddStatic` | ~8 |
|
||||
| Route / waypoints | `buildWP`, `getGroupRoute` | ~4 |
|
||||
| LOS / search | `getUnitsLOS`, `getAvgPos`, `makeUnitTable` | ~4 |
|
||||
| Database | `mist.DBs.unitsByName` | iterated |
|
||||
| Scheduling | `mist.scheduleFunction` | ~1 |
|
||||
| Other | misc | ~19 |
|
||||
|
||||
No functions provide a fallback if MIST is absent — CTLD hard-crashes on
|
||||
`assert(mist ~= nil)` during initialization.
|
||||
|
||||
---
|
||||
|
||||
## 7. Scheduling and timers
|
||||
|
||||
20+ `timer.scheduleFunction` calls drive recurring behavior.
|
||||
All timers reschedule themselves recursively; there are no continuous
|
||||
polling loops.
|
||||
|
||||
| Timer | Interval | Purpose |
|
||||
| ----- | -------- | ------- |
|
||||
| `checkTransportStatus` | 3 s | Monitor transport helicopters |
|
||||
| `checkHoverStatus` | 1 s | Track hover-over-crate countdown |
|
||||
| `refreshRadioBeacons` | 60 s | Update beacon battery status |
|
||||
| `refreshSmoke` | 300 s | Refresh smoke markers |
|
||||
| `checkAIStatus` | 2 s | AI troop behavior check |
|
||||
| `timerJTACAutoLase` | variable | JTAC targeting loop |
|
||||
| `timerLaseUnit` | variable | Single-unit lasing |
|
||||
| `autoUpdateRepackMenu` | 1 s | Refresh vehicle repacking F10 menu |
|
||||
| `reconRefreshTargetsInLosOnF10Map` | variable | RECON map markers |
|
||||
|
||||
---
|
||||
|
||||
## 8. Error handling
|
||||
|
||||
- **pcall usage**: 12 instances, mainly around crate spawning and hover math.
|
||||
- **Nil checks**: Extensive `if X == nil then return end` before DCS API calls.
|
||||
- **No structured error propagation**: errors are logged via `env.error()` /
|
||||
`ctld.logError()` and silently swallowed.
|
||||
- **No stack traces** or exception chaining.
|
||||
|
||||
---
|
||||
|
||||
## 9. Internationalization (i18n)
|
||||
|
||||
| Language | Code | `translation_version` | Completeness |
|
||||
| -------- | ---- | --------------------- | ------------ |
|
||||
| English | `en` | 1.6 | Reference (keys only, values = `""`) |
|
||||
| French | `fr` | 1.6 | Partial — many crate/equipment names empty |
|
||||
| Spanish | `es` | 1.6 | Similar to French |
|
||||
| Korean | `ko` | **1.1** | Severely outdated, many missing keys |
|
||||
|
||||
Issues:
|
||||
|
||||
- No tooling to detect missing keys or version drift.
|
||||
- Empty string (`""`) is a valid Lua value, so missing translations silently
|
||||
fall back to the English key (which is also `""` for reference entries).
|
||||
- All translations live in a single `CTLD-i18n.lua` file — no per-language
|
||||
separation.
|
||||
|
||||
---
|
||||
|
||||
## 10. Documentation
|
||||
|
||||
| Audience | Coverage | Location |
|
||||
| -------- | -------- | -------- |
|
||||
| Mission maker | Partial | `README.md` (setup, config, API examples) |
|
||||
| Player | Minimal | `README.md` (gameplay mechanics mentioned briefly) |
|
||||
| Developer | None | No architecture doc, no contributing guide |
|
||||
|
||||
README strengths:
|
||||
|
||||
- Good coverage of pickup/dropoff zone configuration.
|
||||
- Code examples for DO SCRIPT functions.
|
||||
- Dynamic loading workflow documented.
|
||||
|
||||
README gaps:
|
||||
|
||||
- JTAC advanced features poorly explained.
|
||||
- No API reference table (signatures, parameters, return values).
|
||||
- No architecture or data flow diagrams.
|
||||
- Some examples use different defaults than the current code.
|
||||
|
||||
---
|
||||
|
||||
## 11. Testing
|
||||
|
||||
- **No automated tests** of any kind.
|
||||
- **Manual smoke testing** via `.miz` missions in the repository:
|
||||
- `test-mission.miz` — full feature demonstration
|
||||
- `test-dev-dynamic.miz` — dynamic script loading for fast iteration
|
||||
- `test-dev-static.miz` — static loading variant
|
||||
- `test_witchcraft_#131.miz`, `test_witchcraft_#137 et #149.miz` — bug repro missions
|
||||
- No test framework, no mocks, no CI.
|
||||
|
||||
---
|
||||
|
||||
## 12. Dead code and technical debt
|
||||
|
||||
| Item | Location | Detail |
|
||||
| ---- | -------- | ------ |
|
||||
| Commented-out blocks | 18+ blocks across file | Old implementations, debug markers, disabled alternatives |
|
||||
| Unused function | `ctld.tools.getRelativeBearing` | Defined but never called |
|
||||
| Unused function | `ctld.tools.isValueInIpairTable` | Called once, could be inlined |
|
||||
| Commented event handler | `ctld.initialize()` | Old `ctld.eventHandler` pattern, replaced but not removed |
|
||||
| Disabled vehicle types | Crate config tables | Many entries commented out (`-- BUK`, `-- Strela`, etc.) |
|
||||
| Debug markers | Various | `--ctld.logTrace("FG_ XXXX...")` left in code |
|
||||
| Alternate coordinate | `ctld.listFOBS()` | MGRS conversion commented out |
|
||||
|
||||
---
|
||||
|
||||
## 13. Summary of pain points
|
||||
|
||||
1. **Monolithic file**: 8 700 lines, impossible to navigate or review efficiently.
|
||||
2. **No encapsulation**: 100+ public functions, 0 local functions, all state is global.
|
||||
3. **RED/BLUE duplication**: 50+ coalition branches + paired tables.
|
||||
4. **MIST hard dependency**: 91 calls, no fallback, no abstraction layer.
|
||||
5. **No tests**: zero automated tests, zero mocks, zero CI.
|
||||
6. **No OOP**: purely procedural, no classes, no metatables.
|
||||
7. **Dead code**: 18+ commented blocks, unused functions.
|
||||
8. **i18n drift**: Korean 3 versions behind, many empty translations.
|
||||
9. **No developer documentation**: new contributors must read 8 700 lines to understand the architecture.
|
||||
10. **No build system**: the source file IS the deliverable.
|
||||
@@ -0,0 +1,364 @@
|
||||
# DCS-CTLD Modernization Plan
|
||||
|
||||
> Status: **Draft**
|
||||
> Branch: `next` (git flow, `main` stays stable)
|
||||
> Target: CTLD v2.0
|
||||
|
||||
---
|
||||
|
||||
## Vision
|
||||
|
||||
Rewrite CTLD as a modern, modular, and testable Lua project while
|
||||
temporarily preserving backward compatibility with existing missions.
|
||||
The deliverable remains a single `.lua` file produced by an automated build.
|
||||
|
||||
---
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
Key decisions made during the planning phase, for reference.
|
||||
|
||||
| # | Topic | Decision |
|
||||
| - | ----- | -------- |
|
||||
| 1 | Module split | Separate source files in `src/`, concatenated into a single `dist/CTLD.lua` by a build script (CI) |
|
||||
| 2 | OOP level | Full OOP Lua 5.1 with metatables and classes — ready to break backward compat when needed |
|
||||
| 3 | MIST dependency | Middleware layer (`src/mist_compat/`): CTLD never calls `mist.*` directly. Long-term goal: eliminate MIST entirely |
|
||||
| 4 | API breaking changes | Short/medium term: legacy wrappers with deprecation warnings. Long term (v3): legacy API removed |
|
||||
| 5 | Lua environment | Lua 5.1 DCS sandbox, desanitized server assumed (`io`, `os`, `lfs` accessible) |
|
||||
| 6 | Testing | Both: busted + DCS/MIST mocks in CI, and in-game test missions for integration |
|
||||
| 7 | Documentation | 3 audiences (player, mission maker, developer). Markdown in `docs/`, auto-published to GitHub Pages via MkDocs |
|
||||
| 8 | i18n | Included in plan: audit missing keys, CI lint for version drift, cleanup empty translations |
|
||||
| 9 | Priorities | 1-Cleanup, 2-OOP, 3-MIST middleware, 4-Legacy compat, 5-Tests, 6-CI, 7-i18n, 8-Docs |
|
||||
| 10 | Branching | `next` branch for v2, git flow. `main` stays stable. Feature branches: `feature/<phase>-<description>` |
|
||||
|
||||
---
|
||||
|
||||
## Guiding principles
|
||||
|
||||
- **OOP Lua 5.1**: metatables, classes, state encapsulation.
|
||||
- **Separate modules** in development, single file in delivery.
|
||||
- **Zero direct MIST dependency**: all MIST usage goes through a middleware layer.
|
||||
- **Automated testing**: pure logic tested outside DCS, integration tested in-game.
|
||||
- **Auto-generated documentation**: from the repo to GitHub Pages.
|
||||
- **Transitional legacy compatibility**: deprecated wrappers with warnings, removed in v3.
|
||||
|
||||
---
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 1 — Dead code cleanup
|
||||
|
||||
**Goal**: Start from a clean, noise-free baseline.
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 1.1 | Remove the 18+ identified commented-out code blocks |
|
||||
| 1.2 | Remove unused functions (`ctld.tools.getRelativeBearing`, etc.) |
|
||||
| 1.3 | Remove state variables that are never read |
|
||||
| 1.4 | Clean up obsolete `--TODO`/`--FIXME` markers |
|
||||
| 1.5 | Validate with existing test missions that nothing breaks |
|
||||
|
||||
**Deliverable**: A lighter CTLD.lua, functionally identical.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 — Module split + OOP
|
||||
|
||||
**Goal**: Transform the procedural monolith into a modular, object-oriented architecture.
|
||||
|
||||
#### 2.1 — Lua 5.1 class system
|
||||
|
||||
Define a micro class framework based on metatables:
|
||||
|
||||
```lua
|
||||
-- src/lib/class.lua
|
||||
local function class(base)
|
||||
local cls = {}
|
||||
cls.__index = cls
|
||||
if base then setmetatable(cls, { __index = base }) end
|
||||
function cls:new(...)
|
||||
local instance = setmetatable({}, cls)
|
||||
if instance.init then instance:init(...) end
|
||||
return instance
|
||||
end
|
||||
return cls
|
||||
end
|
||||
```
|
||||
|
||||
#### 2.2 — Module layout
|
||||
|
||||
Target structure for the `src/` directory:
|
||||
|
||||
```text
|
||||
src/
|
||||
├── lib/
|
||||
│ ├── class.lua -- OOP micro-framework
|
||||
│ ├── utils.lua -- Utilities (deepCopy, formatText, logging)
|
||||
│ └── vec.lua -- Vector math (2D/3D distance, bearing)
|
||||
├── core/
|
||||
│ ├── config.lua -- User configuration (USER CONFIGURATION)
|
||||
│ ├── state.lua -- State manager (replaces 34 global tables)
|
||||
│ ├── coalition.lua -- Coalition class (eliminates RED/BLUE duplication)
|
||||
│ ├── i18n.lua -- i18n system
|
||||
│ └── events.lua -- Event handler + callback system
|
||||
├── transport/
|
||||
│ ├── transport.lua -- Transport class (heli/plane)
|
||||
│ ├── troops.lua -- Troop management (load/unload/extract)
|
||||
│ └── vehicles.lua -- Vehicle management
|
||||
├── logistics/
|
||||
│ ├── crate.lua -- Crate class
|
||||
│ ├── crate_manager.lua -- Spawning, hover, sling load, unpack
|
||||
│ ├── fob.lua -- FOB construction and management
|
||||
│ └── beacon.lua -- Radio beacons
|
||||
├── jtac/
|
||||
│ ├── jtac_controller.lua -- JTACController class
|
||||
│ ├── targeting.lua -- Target acquisition, LOS, priorities
|
||||
│ ├── lasing.lua -- Laser/IR point, spot corrections
|
||||
│ └── menu.lua -- F10 JTAC menus
|
||||
├── ui/
|
||||
│ ├── f10_menu.lua -- F10 menu construction
|
||||
│ ├── messages.lua -- Player messages (displayMessageToGroup)
|
||||
│ └── smoke.lua -- Smoke markers
|
||||
├── recon/
|
||||
│ └── recon.lua -- Target recognition on F10 map
|
||||
├── ai/
|
||||
│ └── ai_transport.lua -- AI auto-load/unload logic
|
||||
├── compat/
|
||||
│ └── legacy_api.lua -- Deprecated ctld.* wrappers
|
||||
├── mist_compat/
|
||||
│ └── mist_middleware.lua -- MIST middleware (see Phase 3)
|
||||
└── ctld.lua -- Entry point, initialization, orchestration
|
||||
```
|
||||
|
||||
#### 2.3 — Core classes
|
||||
|
||||
| Class | Responsibility | Replaces |
|
||||
| ----- | -------------- | -------- |
|
||||
| `Coalition` | Encapsulates side (1/2) and per-coalition state | `ctld.spawnedCratesRED/BLUE`, `ctld.droppedTroopsRED/BLUE`, etc. (34 tables → 2 instances) |
|
||||
| `Transport` | State of a transport helicopter/plane | `ctld.inTransitTroops[name]`, `ctld.hoverStatus[name]` |
|
||||
| `Crate` | Represents a logistics crate | Entries from `ctld.spawnableCrates`, `ctld.crateLookupTable` |
|
||||
| `CrateManager` | Spawn, pickup, drop, unpack crates | `ctld.spawnCrate`, `ctld.unpackCrates`, `ctld.checkHoverStatus` |
|
||||
| `FOB` | A built Forward Operating Base | `ctld.builtFOBS[name]` |
|
||||
| `JTACController` | An active JTAC with its lasing state | `ctld.jtacUnits`, `ctld.jtacCurrentTargets`, `ctld.jtacSelectedTarget` |
|
||||
| `Beacon` | A deployed radio beacon | `ctld.deployedRadioBeacons[name]` |
|
||||
| `StateManager` | Central registry of all active objects | Replaces 34 global tables |
|
||||
| `Config` | Typed user configuration | Current USER CONFIGURATION section |
|
||||
|
||||
#### 2.4 — Eliminating RED/BLUE duplication
|
||||
|
||||
Before (50+ branches):
|
||||
|
||||
```lua
|
||||
if _heli:getCoalition() == 1 then
|
||||
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
|
||||
else
|
||||
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
|
||||
end
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```lua
|
||||
local side = stateManager:getCoalition(heli:getCoalition())
|
||||
local extract = side:findNearestDroppedTroop(heli)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3 — MIST middleware
|
||||
|
||||
**Goal**: Isolate MIST behind a facade. CTLD never references `mist.*` directly.
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 3.1 | Inventory the 91 MIST calls and categorize them (trivial / medium / complex) |
|
||||
| 3.2 | Create `src/mist_compat/mist_middleware.lua` exposing a clean API |
|
||||
| 3.3 | Rewrite trivial functions: `deepCopy`, `get2DDist`, `round`, `makeVec2/3` |
|
||||
| 3.4 | Wrap medium functions: `getHeading`, `vec.*`, `tostringLL/MGRS` |
|
||||
| 3.5 | Delegate complex functions to MIST: `dynAdd`, `dynAddStatic`, `getUnitsLOS` |
|
||||
| 3.6 | Replace all `mist.*` calls in modules with middleware calls |
|
||||
| 3.7 | (Long term) Rewrite complex functions to eliminate MIST entirely |
|
||||
|
||||
**Deliverable**: CTLD works with or without MIST (graceful degradation when MIST is absent, eventually).
|
||||
|
||||
---
|
||||
|
||||
### Phase 4 — Legacy API compatibility layer
|
||||
|
||||
**Goal**: Existing missions continue to work without modification.
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 4.1 | List all public `ctld.*` functions documented in the README |
|
||||
| 4.2 | Create `src/compat/legacy_api.lua` with wrappers that call the new API |
|
||||
| 4.3 | Each wrapper logs a deprecated warning with the new API name |
|
||||
| 4.4 | Document old → new migration in `docs/migration-v2.md` |
|
||||
| 4.5 | Announce the policy: legacy maintained in v2, removed in v3 |
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
-- legacy_api.lua
|
||||
function ctld.spawnCrateAtZone(_side, _weight, _zone)
|
||||
ctld.logWarning("DEPRECATED: ctld.spawnCrateAtZone() -> use CrateManager:spawnAtZone()")
|
||||
local side = (_side == "red") and 1 or 2
|
||||
return ctld.crateManager:spawnAtZone(side, _weight, _zone)
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 5 — Unit tests
|
||||
|
||||
**Goal**: Coverage of pure logic outside DCS + in-game smoke tests.
|
||||
|
||||
#### 5.1 — Framework and mocks
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 5.1.1 | Set up busted (Lua 5.1) + luarocks in the repo |
|
||||
| 5.1.2 | Create `test/mocks/dcs_env.lua`: stubs for `trigger`, `Unit`, `Group`, `coalition`, `land`, `timer`, `missionCommands`, `coord`, `world`, `env` |
|
||||
| 5.1.3 | Create `test/mocks/mist_env.lua`: stubs for MIST functions used |
|
||||
| 5.1.4 | Configure busted to load mocks before tests |
|
||||
|
||||
#### 5.2 — Unit tests (outside DCS)
|
||||
|
||||
| Module | Priority tests |
|
||||
| ------ | -------------- |
|
||||
| `lib/class.lua` | Inheritance, instantiation, override |
|
||||
| `lib/vec.lua` | 2D/3D distance, bearing, clock direction |
|
||||
| `lib/utils.lua` | deepCopy, formatText |
|
||||
| `core/coalition.lua` | Per-coalition state management, lookup |
|
||||
| `core/i18n.lua` | Translation, fallback, parameters |
|
||||
| `core/config.lua` | Default values, override |
|
||||
| `logistics/crate.lua` | Weight uniqueness, lookup |
|
||||
| `compat/legacy_api.lua` | Wrappers call the correct new API |
|
||||
|
||||
#### 5.3 — In-game tests
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 5.3.1 | Create an automated test mission (DO SCRIPT running a test suite) |
|
||||
| 5.3.2 | Write results to a file via `io.open` (desanitized server) |
|
||||
| 5.3.3 | Validate: troop load/unload, crate spawn/unpack, JTAC lasing, FOB build, beacons |
|
||||
|
||||
---
|
||||
|
||||
### Phase 6 — CI infrastructure
|
||||
|
||||
**Goal**: Automate build, tests, and release.
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 6.1 | **Build script**: Lua (or Python/Bash) script that concatenates `src/**/*.lua` → `dist/CTLD.lua` in the correct order |
|
||||
| 6.2 | **GitHub Actions — Tests**: install Lua 5.1 + luarocks + busted, run `busted test/` |
|
||||
| 6.3 | **GitHub Actions — Build**: generate `dist/CTLD.lua` and `dist/CTLD-i18n.lua` |
|
||||
| 6.4 | **GitHub Actions — Release**: on tag, publish the `.lua` artifact as a GitHub release |
|
||||
| 6.5 | **GitHub Actions — Docs**: MkDocs build + deploy to GitHub Pages |
|
||||
| 6.6 | **i18n lint**: CI script comparing keys across languages and checking `translation_version` |
|
||||
|
||||
---
|
||||
|
||||
### Phase 7 — i18n cleanup + tooling
|
||||
|
||||
**Goal**: Reliable i18n with automatic drift detection.
|
||||
|
||||
| Task | Detail |
|
||||
| ---- | ------ |
|
||||
| 7.1 | Full audit: list EN vs FR vs ES vs KO keys, identify missing ones |
|
||||
| 7.2 | Fill empty FR keys (crate/equipment names) |
|
||||
| 7.3 | Update KO (1.1 → 1.6+) or mark it as `incomplete` |
|
||||
| 7.4 | CI lint script for i18n (see 6.6) |
|
||||
| 7.5 | Document the process of adding a language in `docs/contributing/i18n.md` |
|
||||
| 7.6 | Consider extracting i18n into per-language files (`i18n/fr.lua`, `i18n/ko.lua`) |
|
||||
|
||||
---
|
||||
|
||||
### Phase 8 — Documentation
|
||||
|
||||
**Goal**: 3 guides for 3 audiences, auto-published.
|
||||
|
||||
#### 8.1 — `docs/` structure
|
||||
|
||||
```text
|
||||
docs/
|
||||
├── index.md -- Landing page
|
||||
├── user-guide/
|
||||
│ ├── getting-started.md -- For the player: F10 menus, in-game workflow
|
||||
│ ├── troops.md -- Troop loading/unloading
|
||||
│ ├── crates.md -- Crate system
|
||||
│ ├── jtac.md -- Using JTACs
|
||||
│ ├── fob.md -- FOB construction
|
||||
│ └── beacons.md -- Radio beacons
|
||||
├── mission-maker/
|
||||
│ ├── setup.md -- Mission installation
|
||||
│ ├── configuration.md -- All configurable options
|
||||
│ ├── api-reference.md -- Public API (DO SCRIPT)
|
||||
│ ├── zones.md -- Pickup/dropoff/waypoint zones
|
||||
│ ├── jtac-setup.md -- Advanced JTAC configuration
|
||||
│ └── examples.md -- Mission examples
|
||||
├── developer/
|
||||
│ ├── architecture.md -- Module, class, and flow diagrams
|
||||
│ ├── contributing.md -- How to contribute
|
||||
│ ├── building.md -- Build script, CI
|
||||
│ ├── testing.md -- Running tests
|
||||
│ ├── i18n.md -- Adding/maintaining a translation
|
||||
│ └── migration-v2.md -- v1 → v2 migration guide
|
||||
└── mkdocs.yml -- MkDocs configuration
|
||||
```
|
||||
|
||||
#### 8.2 — Publishing
|
||||
|
||||
- MkDocs Material theme
|
||||
- GitHub Actions: build + deploy to `gh-pages` on every push to `next`
|
||||
- Slimmed-down README.md: links to the full documentation site
|
||||
|
||||
---
|
||||
|
||||
## Indicative timeline
|
||||
|
||||
| Phase | Dependencies | Complexity |
|
||||
| ----- | ------------ | ---------- |
|
||||
| 1 — Dead code cleanup | None | Low |
|
||||
| 2 — Modules + OOP | Phase 1 | **High** |
|
||||
| 3 — MIST middleware | Phase 2 | Medium |
|
||||
| 4 — Legacy compat | Phase 2 | Medium |
|
||||
| 5 — Tests | Phases 2-3 | Medium |
|
||||
| 6 — CI | Phases 2, 5 | Medium |
|
||||
| 7 — i18n | Phase 2 | Low |
|
||||
| 8 — Documentation | Phases 2-4 | Medium |
|
||||
|
||||
> Phases 3 and 4 can be worked on in parallel.
|
||||
> Phase 6 can start as early as Phase 2 (build script) and grow incrementally.
|
||||
|
||||
---
|
||||
|
||||
## Branching strategy
|
||||
|
||||
```text
|
||||
main (stable, v1.x)
|
||||
└── next (v2 development)
|
||||
├── feature/phase1-cleanup
|
||||
├── feature/phase2-oop-core
|
||||
├── feature/phase2-oop-jtac
|
||||
├── feature/phase3-mist-middleware
|
||||
├── feature/phase4-legacy-compat
|
||||
├── feature/phase5-tests
|
||||
└── ...
|
||||
```
|
||||
|
||||
- `main`: stable v1.x releases, urgent hotfixes only.
|
||||
- `next`: v2 integration, git flow (feature → next → release).
|
||||
- Tags: `v2.0-alpha.1`, `v2.0-beta.1`, `v2.0-rc.1`, `v2.0`.
|
||||
|
||||
---
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
| ---- | ------ | ---------- |
|
||||
| Gameplay regressions after OOP refactoring | High | Unit tests + systematic test missions |
|
||||
| DCS mock divergence from real API | Medium | In-game tests as complement, versioned mocks |
|
||||
| Incomplete MIST middleware | Medium | Progressive approach, MIST remains as fallback |
|
||||
| Mission maker resistance to API changes | Medium | Legacy layer + communication + migration guide |
|
||||
| Build script complexity (module ordering) | Low | Explicit dependencies, build tested in CI |
|
||||
@@ -0,0 +1,75 @@
|
||||
# Copilot Instructions for DCS-CTLD
|
||||
|
||||
## Working mode / Mode de travail
|
||||
|
||||
- **Communication language**: French (français) for all conversations and commit messages.
|
||||
- **Documentation language**: English for all technical documentation (code comments, docs/, README). French summaries in docs are acceptable but English is the primary language.
|
||||
- **Development methodology**: TDD (Test-Driven Development) — write tests before implementation when possible.
|
||||
- **Programming paradigm**: OOP Lua 5.1 with metatables and classes. No procedural spaghetti.
|
||||
- **Branching strategy**: Git flow on the `next` branch. Feature branches named `feature/<phase>-<description>`. Never commit directly to `main` or `next`.
|
||||
- **Commit style**: Conventional Commits (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`).
|
||||
- **Build target**: Individual source modules in `src/` are concatenated into a single `dist/CTLD.lua` deliverable by a build script. Never edit the built file directly.
|
||||
- **MIST dependency**: CTLD code must never call `mist.*` directly. All MIST usage goes through the middleware layer (`src/mist_compat/`).
|
||||
- **Legacy API**: When refactoring a public `ctld.*` function, always create a deprecated wrapper in `src/compat/legacy_api.lua` that logs a warning and delegates to the new API.
|
||||
- **Refer to the modernization plan**: See `.github/MODERNIZATION-PLAN.md` for the full roadmap and architectural decisions.
|
||||
|
||||
## Project context
|
||||
- This repository contains Lua mission scripts for DCS World.
|
||||
- The primary script is CTLD.lua.
|
||||
- Internationalization tables are split between CTLD.lua (English reference keys) and CTLD-i18n.lua (translated values).
|
||||
- Runtime is DCS mission scripting environment (Lua 5.1, metatables available, no LuaJIT, no goto).
|
||||
- Assumes desanitized server (io, os, lfs accessible).
|
||||
- MIST is being progressively replaced by an internal middleware layer.
|
||||
|
||||
## Architecture (v2 target)
|
||||
|
||||
- Source modules live in `src/` organized by domain: `lib/`, `core/`, `transport/`, `logistics/`, `jtac/`, `ui/`, `recon/`, `ai/`, `compat/`, `mist_compat/`.
|
||||
- OOP via a micro class system using metatables (`src/lib/class.lua`).
|
||||
- State is managed by a `StateManager` singleton and per-coalition `Coalition` instances — no more `ctld.xxxRED`/`ctld.xxxBLUE` table pairs.
|
||||
- Tests live in `test/` and use busted with DCS/MIST mocks.
|
||||
|
||||
## Compatibility (v1 legacy)
|
||||
- Preserve existing public ctld.* APIs used by Mission Editor DO SCRIPT triggers.
|
||||
- Do not rename exported ctld functions or change their parameter semantics unless explicitly requested.
|
||||
- Keep behavior compatible with existing .miz missions in this repository.
|
||||
- Keep script load order assumptions intact: MIST first, CTLD-i18n.lua before CTLD.lua.
|
||||
|
||||
## CTLD.lua editing rules
|
||||
- Keep user-tunable options in the USER CONFIGURATION section with clear comments and safe defaults.
|
||||
- Keep coalition conventions consistent:
|
||||
- 1 = red
|
||||
- 2 = blue
|
||||
- 0 = both (where applicable)
|
||||
- Keep pickup/dropoff/waypoint zone tuple formats compatible with current parser logic.
|
||||
- Keep crate weight values unique in ctld.spawnableCrates.
|
||||
- Preserve callback behavior and action names used by ctld.processCallback.
|
||||
- Use defensive nil checks before calling methods on DCS objects (Unit/Group/zone lookups).
|
||||
- Prefer existing logging patterns (ctld.logInfo, env.info, env.error) over ad-hoc prints.
|
||||
- Avoid adding heavy polling loops; prefer bounded searches and scheduled checks.
|
||||
|
||||
## I18N rules
|
||||
- For every new player-visible string, add an English reference key and use ctld.i18n_translate at call sites.
|
||||
- Do not hardcode new menu or gameplay text without i18n_translate.
|
||||
- When adding keys, keep translation_version alignment in mind and add placeholders in CTLD-i18n.lua tables.
|
||||
- Do not remove or rename existing i18n keys unless all language tables are updated.
|
||||
|
||||
## Testing expectations
|
||||
- Prefer the dynamic loading workflow documented in README for fast iteration.
|
||||
- After gameplay logic changes, validate at least:
|
||||
- troop load/unload flows
|
||||
- crate spawn/load/unpack flows
|
||||
- JTAC-related flows (if touched)
|
||||
- Use repository missions for smoke tests when relevant:
|
||||
- test-dev-dynamic.miz
|
||||
- test-dev-static.miz
|
||||
- test-mission.miz
|
||||
- If user-facing behavior or configuration changes, update README examples in the same change.
|
||||
|
||||
## Code style conventions
|
||||
|
||||
- Follow existing file style and indentation.
|
||||
- OOP Lua 5.1: use the project class system (`src/lib/class.lua`) for new classes.
|
||||
- For internal helpers, follow existing naming patterns (local variables often prefixed with _).
|
||||
- Keep mission-designer guidance comments concise and practical.
|
||||
- No direct `mist.*` calls — use the middleware.
|
||||
- Every new public function should have at least one unit test.
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Lua.diagnostics.globals": [
|
||||
"ctld"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
--[[
|
||||
--[[ ! IMPORTANT : You must must use the version of MIST supplied in the CTLD pack to correctly manage dynamic spwans
|
||||
|
||||
Combat Troop and Logistics Drop
|
||||
|
||||
Allows Huey, Mi-8 and C130 to transport troops internally and Helicopters to transport Logistic / Vehicle units to the field via sling-loads
|
||||
@@ -40,7 +41,7 @@ end
|
||||
ctld.Id = "CTLD - "
|
||||
|
||||
--- Version.
|
||||
ctld.Version = "1.5.0"
|
||||
ctld.Version = "1.6.0-alpha"
|
||||
|
||||
-- To add debugging messages to dcs.log, change the following log levels to `true`; `Debug` is less detailed than `Trace`
|
||||
ctld.Debug = false
|
||||
@@ -491,8 +492,8 @@ ctld.JTAC_LIMIT_RED = 10 -- max number of JTAC Crates for the RED Si
|
||||
ctld.JTAC_LIMIT_BLUE = 10 -- max number of JTAC Crates for the BLUE Side
|
||||
ctld.JTAC_dropEnabled = true -- allow JTAC Crate spawn from F10 menu
|
||||
ctld.JTAC_maxDistance = 10000 -- How far a JTAC can "see" in meters (with Line of Sight)
|
||||
ctld.JTAC_smokeOn_RED = true -- enables marking of target with smoke for RED forces
|
||||
ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE forces
|
||||
ctld.JTAC_smokeOn_RED = false -- enables marking of target with smoke for RED forces
|
||||
ctld.JTAC_smokeOn_BLUE = false -- enables marking of target with smoke for BLUE forces
|
||||
ctld.JTAC_smokeColour_RED = 4 -- RED side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
||||
ctld.JTAC_smokeColour_BLUE = 1 -- BLUE side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
||||
ctld.JTAC_smokeMarginOfError = 50 -- error that the JTAC is allowed to make when popping a smoke (in meters)
|
||||
@@ -817,7 +818,9 @@ ctld.vehicleTransportEnabled = {
|
||||
-- We will also use this to make modifications to the menu and other checks and messages
|
||||
ctld.dynamicCargoUnits = {
|
||||
"CH-47Fbl1",
|
||||
--"UH-1H",
|
||||
"UH-1H",
|
||||
"Mi-8MT",
|
||||
"Mi-24P",
|
||||
}
|
||||
|
||||
-- ************** Maximum Units SETUP for UNITS ******************
|
||||
@@ -888,6 +891,7 @@ ctld.internalCargoLimits = {
|
||||
-- Remove the -- below to turn on options
|
||||
["Mi-8MT"] = 2,
|
||||
["CH-47Fbl1"] = 8,
|
||||
["UH-1H"] = 3, -- to remove after debug
|
||||
}
|
||||
|
||||
|
||||
@@ -1977,7 +1981,7 @@ function ctld.getUnitsInRepackRadius(_PlayerTransportUnitName, _radius)
|
||||
end
|
||||
|
||||
local unitsNamesList = ctld.getNearbyUnits(unit:getPoint(), _radius, unit:getCoalition())
|
||||
|
||||
|
||||
local repackableUnits = {}
|
||||
for i = 1, #unitsNamesList do
|
||||
local unitObject = Unit.getByName(unitsNamesList[i])
|
||||
@@ -1998,11 +2002,13 @@ function ctld.getNearbyUnits(_point, _radius, _coalition)
|
||||
local unitsByDistance = {}
|
||||
local cpt = 1
|
||||
local _units = {}
|
||||
local _unitList = mist.DBs.unitsByName
|
||||
for _unitName, _unit in pairs(mist.DBs.unitsByName) do
|
||||
for _unitName, _ in pairs(mist.DBs.unitsByName) do
|
||||
local u = Unit.getByName(_unitName)
|
||||
if u and u:isExist() and (_coalition == 4 or u:getCoalition() == _coalition) then
|
||||
--local _dist = ctld.getDistance(u:getPoint(), _point)
|
||||
local e = (u and u:isExist()) or false
|
||||
-- pcall is needed because getCoalition() fails if the unit is an object without coalition (like a smoke effect)
|
||||
local c = nil
|
||||
pcall(function() c = (u and e and u:getCoalition()) or nil end)
|
||||
if u and e and (_coalition == 4 or c == _coalition) then
|
||||
local _dist = mist.utils.get2DDist(u:getPoint(), _point)
|
||||
if _dist <= _radius then
|
||||
unitsByDistance[cpt] = {id =cpt, dist = _dist, unit = _unitName, typeName = u:getTypeName()}
|
||||
@@ -2010,7 +2016,7 @@ function ctld.getNearbyUnits(_point, _radius, _coalition)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--table.sort(unitsByDistance, function(a,b) return a.dist < b.dist end) -- sort the table by distance (the nearest first)
|
||||
table.sort(unitsByDistance, function(a,b) return a.typeName < b.typeName end) -- sort the table by typeNAme
|
||||
for i, v in ipairs(unitsByDistance) do
|
||||
@@ -2092,7 +2098,7 @@ function ctld.repackVehicle(_params, t) -- scan rrs table 'repackRequestsStack'
|
||||
local _name = string.format("%s_%i", v.desc, _unitId)
|
||||
local secureDistance = ctld.getSecureDistanceFromUnit(playerUnitName) or 10
|
||||
local relativePoint = ctld.getRelativePoint(playerPoint, secureDistance + (i * offset), randomHeading) -- 7 meters from the transport unit
|
||||
|
||||
|
||||
if ctld.unitDynamicCargoCapable(PlayerTransportUnit) == false then
|
||||
ctld.spawnCrateStatic(refCountry, _unitId, relativePoint, _name, crateWeight, playerCoa, playerHeading, nil)
|
||||
else
|
||||
@@ -2105,8 +2111,8 @@ function ctld.repackVehicle(_params, t) -- scan rrs table 'repackRequestsStack'
|
||||
ctld.repackRequestsStack[ii] = nil -- remove the processed request from the stacking table
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if ctld.enableRepackingVehicles == true then
|
||||
return t + 3 -- reschedule the function in 3 seconds
|
||||
else
|
||||
@@ -2615,10 +2621,19 @@ function ctld.spawnCrate(_arguments, bypassCrateWaitTime)
|
||||
|
||||
-- add to move table
|
||||
ctld.crateMove[_name] = _name
|
||||
|
||||
local refPoint = _heli:getPoint()
|
||||
local refLat, refLon = coord.LOtoLL(refPoint)
|
||||
local unitPos = _heli:getPosition()
|
||||
local refHeading = math.deg(math.atan2(unitPos.x.z, unitPos.x.x))
|
||||
|
||||
local destLat, destLon, destAlt = coord.LOtoLL(_point)
|
||||
|
||||
local relativePos, forma = ctld.tools.getRelativeBearing(refLat, refLon, refHeading, destLat, destLon, 'clock')
|
||||
|
||||
ctld.displayMessageToGroup(_heli,
|
||||
ctld.i18n_translate("A %1 crate weighing %2 kg has been brought out and is at your %3 o'clock ",
|
||||
_crateType.desc, _crateType.weight, _position), 20)
|
||||
_crateType.desc, _crateType.weight, relativePos), 20)
|
||||
else
|
||||
env.info("Couldn't find crate item to spawn")
|
||||
end
|
||||
@@ -3169,7 +3184,7 @@ function ctld.updateTroopsInGame(params, t) -- return count of troops in game b
|
||||
for index, unitObj in pairs(v:getUnits()) do -- for each unit in group
|
||||
if unitObj:getDesc().attributes.Infantry then
|
||||
ctld.InfantryInGameCount[coalitionId] = ctld.InfantryInGameCount[coalitionId] + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3227,7 +3242,7 @@ function ctld.loadTroopsFromZone(_args)
|
||||
ctld.logTrace("FG_ ctld.InfantryInGameCount[heloCoa] = %s", ctld.p(ctld.InfantryInGameCount[heloCoa]))
|
||||
ctld.logTrace("FG_ _groupTemplate.total = %s", ctld.p(_groupTemplate.total))
|
||||
ctld.logTrace("FG_ ctld.nbLimitSpawnedTroops[%s].total = %s", ctld.p(heloCoa), ctld.p(ctld.nbLimitSpawnedTroops[heloCoa]))
|
||||
|
||||
|
||||
local limitReached = true
|
||||
if (ctld.nbLimitSpawnedTroops[1]~=0 or ctld.nbLimitSpawnedTroops[2]~=0) and (ctld.InfantryInGameCount[heloCoa] + _groupTemplate.total > ctld.nbLimitSpawnedTroops[heloCoa]) then -- load troops only if Coa limit not reached
|
||||
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Count Infantries limit in the mission reached, you can't load more troops"), 10)
|
||||
@@ -3960,7 +3975,7 @@ function ctld.unpackCrates(_arguments)
|
||||
local _crates = ctld.getCratesAndDistance(_heli)
|
||||
local _crate = ctld.getClosestCrate(_heli, _crates)
|
||||
ctld.logTrace("FG_ ctld.unpackCrates._crate = %s", ctld.p(_crate))
|
||||
|
||||
|
||||
if ctld.inLogisticsZone(_heli) == true or ctld.farEnoughFromLogisticZone(_heli) == false then
|
||||
ctld.displayMessageToGroup(_heli,
|
||||
ctld.i18n_translate("You can't unpack that here! Take it to where it's needed!"), 20)
|
||||
@@ -4015,7 +4030,7 @@ function ctld.unpackCrates(_arguments)
|
||||
ctld.logTrace("_crate = %s", ctld.p(_crate))
|
||||
local _spawnedGroups = ctld.spawnCrateGroup(_heli, { _point }, { _crate.details.unit }, { _crateHdg })
|
||||
ctld.logTrace("_spawnedGroups = %s", ctld.p(_spawnedGroups))
|
||||
|
||||
|
||||
if _heli:getCoalition() == 1 then
|
||||
ctld.spawnedCratesRED[_crateName] = nil
|
||||
else
|
||||
@@ -4221,8 +4236,7 @@ function ctld.dropSlingCrate(_args)
|
||||
_position = "6"
|
||||
end
|
||||
ctld.displayMessageToGroup(_heli,
|
||||
ctld.i18n_translate("%1 crate has been safely unhooked and is at your %2 o'clock", _crate.desc,
|
||||
_position), 10)
|
||||
ctld.i18n_translate("%1 crate has been safely unhooked and is at your %2 o'clock", _crate.desc, _position), 10)
|
||||
elseif _heightDiff > 7.5 and _heightDiff <= 40.0 then
|
||||
ctld.displayMessageToGroup(_heli,
|
||||
ctld.i18n_translate("%1 crate has been safely dropped below you", _crate.desc), 10)
|
||||
@@ -5026,7 +5040,7 @@ function ctld.unpackMultiCrate(_heli, _nearestCrate, _nearbyCrates)
|
||||
if ctld.unitDynamicCargoCapable(_heli) == true then
|
||||
_point = ctld.getPointInRearSector(_heli, ctld.getSecureDistanceFromUnit(_heli:getName()))
|
||||
end
|
||||
|
||||
|
||||
local _crateHdg = mist.getHeading(_nearestCrate.crateUnit, true)
|
||||
|
||||
-- destroy crates
|
||||
@@ -5744,7 +5758,7 @@ function ctld.unitDynamicCargoCapable(_unit)
|
||||
--ctld.logDebug("ctld.unitDynamicCargoCapable(_type=[%s])", ctld.p(_type))
|
||||
for _, _name in ipairs(ctld.dynamicCargoUnits) do
|
||||
local _nameLower = string.lower(_name)
|
||||
if string.find(_type, _nameLower, 1, true) then --string.match does not work with patterns containing '-' as it is a magic character
|
||||
if string.find(_type, _nameLower, 1, true) then --string.match does not work with patterns containing '-' as it is a magic character
|
||||
result = true
|
||||
break
|
||||
end
|
||||
@@ -5864,208 +5878,203 @@ end
|
||||
-- Adds menuitem to a human unit
|
||||
function ctld.addTransportF10MenuOptions(_unitName)
|
||||
ctld.logDebug("ctld.addTransportF10MenuOptions(_unitName=[%s])", ctld.p(_unitName))
|
||||
local status, error = pcall(function()
|
||||
local _unit = ctld.getTransportUnit(_unitName)
|
||||
ctld.logTrace("_unit = %s", ctld.p(_unit))
|
||||
local _unit = ctld.getTransportUnit(_unitName)
|
||||
ctld.logTrace("_unit = %s", ctld.p(_unit))
|
||||
|
||||
if _unit then
|
||||
local _unitTypename = _unit:getTypeName()
|
||||
local _groupId = ctld.getGroupId(_unit)
|
||||
if _groupId then
|
||||
-- ctld.logTrace("_groupId = %s", ctld.p(_groupId))
|
||||
-- ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo[tostring(_groupId)]))
|
||||
if ctld.addedTo[tostring(_groupId)] == nil then
|
||||
ctld.logTrace("adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
||||
local _rootPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("CTLD"))
|
||||
local _unitActions = ctld.getUnitActions(_unitTypename)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"), _rootPath, ctld.checkTroopStatus, { _unitName })
|
||||
if _unitActions.troops then
|
||||
local _troopCommandsPath = missionCommands.addSubMenuForGroup(_groupId,ctld.i18n_translate("Troop Transport"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload / Extract Troops"),
|
||||
_troopCommandsPath, ctld.unloadExtractTroops, { _unitName })
|
||||
if _unit then
|
||||
local _unitTypename = _unit:getTypeName()
|
||||
local _groupId = ctld.getGroupId(_unit)
|
||||
if _groupId then
|
||||
-- ctld.logTrace("_groupId = %s", ctld.p(_groupId))
|
||||
-- ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo[tostring(_groupId)]))
|
||||
if ctld.addedTo[tostring(_groupId)] == nil then
|
||||
ctld.logTrace("adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
||||
local _rootPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("CTLD"))
|
||||
local _unitActions = ctld.getUnitActions(_unitTypename)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"), _rootPath, ctld.checkTroopStatus, { _unitName })
|
||||
if _unitActions.troops then
|
||||
local _troopCommandsPath = missionCommands.addSubMenuForGroup(_groupId,ctld.i18n_translate("Troop Transport"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload / Extract Troops"),
|
||||
_troopCommandsPath, ctld.unloadExtractTroops, { _unitName })
|
||||
|
||||
-- local _loadPath = missionCommands.addSubMenuForGroup(_groupId, "Load From Zone", _troopCommandsPath)
|
||||
local _transportLimit = ctld.getTransportLimit(_unitTypename)
|
||||
local itemNb = 0
|
||||
local menuEntries = {}
|
||||
local menuPath = _troopCommandsPath
|
||||
for _, _loadGroup in pairs(ctld.loadableGroups) do
|
||||
if not _loadGroup.side or _loadGroup.side == _unit:getCoalition() then
|
||||
-- check size & unit
|
||||
if _transportLimit >= _loadGroup.total then
|
||||
table.insert(menuEntries,
|
||||
{ text = ctld.i18n_translate("Load ") .. _loadGroup.name, group = _loadGroup })
|
||||
end
|
||||
-- local _loadPath = missionCommands.addSubMenuForGroup(_groupId, "Load From Zone", _troopCommandsPath)
|
||||
local _transportLimit = ctld.getTransportLimit(_unitTypename)
|
||||
local itemNb = 0
|
||||
local menuEntries = {}
|
||||
local menuPath = _troopCommandsPath
|
||||
for _, _loadGroup in pairs(ctld.loadableGroups) do
|
||||
if not _loadGroup.side or _loadGroup.side == _unit:getCoalition() then
|
||||
-- check size & unit
|
||||
if _transportLimit >= _loadGroup.total then
|
||||
table.insert(menuEntries,
|
||||
{ text = ctld.i18n_translate("Load ") .. _loadGroup.name, group = _loadGroup })
|
||||
end
|
||||
end
|
||||
for _i, _menu in ipairs(menuEntries) do
|
||||
-- add the menu item
|
||||
itemNb = itemNb + 1
|
||||
if itemNb == 9 and _i < #menuEntries then -- page limit reached (first item is "unload")
|
||||
menuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Next page"),
|
||||
menuPath)
|
||||
itemNb = 1
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, _menu.text, menuPath, ctld.loadTroopsFromZone,
|
||||
{ _unitName, true, _menu.group, false })
|
||||
end
|
||||
if ctld.unitCanCarryVehicles(_unit) then
|
||||
local _vehicleCommandsPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Vehicle / FOB Transport"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload Vehicles"),
|
||||
_vehicleCommandsPath, ctld.unloadTroops, { _unitName, false })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load / Extract Vehicles"),
|
||||
_vehicleCommandsPath, ctld.loadTroopsFromZone, { _unitName, false, "", true })
|
||||
|
||||
if ctld.enabledFOBBuilding and ctld.staticBugWorkaround == false then
|
||||
missionCommands.addCommandForGroup(_groupId,
|
||||
ctld.i18n_translate("Load / Unload FOB Crate"), _vehicleCommandsPath,
|
||||
ctld.loadUnloadFOBCrate, { _unitName, false })
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"),
|
||||
_vehicleCommandsPath, ctld.checkTroopStatus, { _unitName })
|
||||
end
|
||||
end
|
||||
for _i, _menu in ipairs(menuEntries) do
|
||||
-- add the menu item
|
||||
itemNb = itemNb + 1
|
||||
if itemNb == 9 and _i < #menuEntries then -- page limit reached (first item is "unload")
|
||||
menuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Next page"),
|
||||
menuPath)
|
||||
itemNb = 1
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, _menu.text, menuPath, ctld.loadTroopsFromZone,
|
||||
{ _unitName, true, _menu.group, false })
|
||||
end
|
||||
if ctld.unitCanCarryVehicles(_unit) then
|
||||
local _vehicleCommandsPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Vehicle / FOB Transport"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload Vehicles"),
|
||||
_vehicleCommandsPath, ctld.unloadTroops, { _unitName, false })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load / Extract Vehicles"),
|
||||
_vehicleCommandsPath, ctld.loadTroopsFromZone, { _unitName, false, "", true })
|
||||
|
||||
if ctld.enableCrates and _unitActions.crates then
|
||||
if ctld.unitCanCarryVehicles(_unit) == false then
|
||||
-- sort the crate categories alphabetically
|
||||
local crateCategories = {}
|
||||
for category, _ in pairs(ctld.spawnableCrates) do
|
||||
table.insert(crateCategories, category)
|
||||
if ctld.enabledFOBBuilding and ctld.staticBugWorkaround == false then
|
||||
missionCommands.addCommandForGroup(_groupId,
|
||||
ctld.i18n_translate("Load / Unload FOB Crate"), _vehicleCommandsPath,
|
||||
ctld.loadUnloadFOBCrate, { _unitName, false })
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"),
|
||||
_vehicleCommandsPath, ctld.checkTroopStatus, { _unitName })
|
||||
end
|
||||
end
|
||||
|
||||
if ctld.enableCrates and _unitActions.crates then
|
||||
if ctld.unitCanCarryVehicles(_unit) == false then
|
||||
-- sort the crate categories alphabetically
|
||||
local crateCategories = {}
|
||||
for category, _ in pairs(ctld.spawnableCrates) do
|
||||
table.insert(crateCategories, category)
|
||||
end
|
||||
table.sort(crateCategories)
|
||||
--ctld.logTrace("crateCategories = [%s]", ctld.p(crateCategories))
|
||||
|
||||
-- add menu for spawning crates
|
||||
local itemNbMain = 0
|
||||
local _cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Crates: Vehicle / FOB / Drone"), _rootPath)
|
||||
|
||||
for _i, _category in ipairs(crateCategories) do
|
||||
local _subMenuName = _category
|
||||
local _crates = ctld.spawnableCrates[_subMenuName]
|
||||
|
||||
-- add the submenu item
|
||||
itemNbMain = itemNbMain + 1
|
||||
if itemNbMain == 10 and _i < #crateCategories then -- page limit reached
|
||||
_cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Next page"), _cratesMenuPath)
|
||||
itemNbMain = 1
|
||||
end
|
||||
table.sort(crateCategories)
|
||||
--ctld.logTrace("crateCategories = [%s]", ctld.p(crateCategories))
|
||||
|
||||
-- add menu for spawning crates
|
||||
local itemNbMain = 0
|
||||
local _cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Crates: Vehicle / FOB / Drone"), _rootPath)
|
||||
|
||||
for _i, _category in ipairs(crateCategories) do
|
||||
local _subMenuName = _category
|
||||
local _crates = ctld.spawnableCrates[_subMenuName]
|
||||
|
||||
-- add the submenu item
|
||||
itemNbMain = itemNbMain + 1
|
||||
if itemNbMain == 10 and _i < #crateCategories then -- page limit reached
|
||||
_cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Next page"), _cratesMenuPath)
|
||||
itemNbMain = 1
|
||||
end
|
||||
local itemNbSubmenu = 0
|
||||
local menuEntries = {}
|
||||
local _subMenuPath = missionCommands.addSubMenuForGroup(_groupId, _subMenuName, _cratesMenuPath)
|
||||
for _, _crate in pairs(_crates) do
|
||||
--ctld.logTrace("_crate = [%s]", ctld.p(_crate))
|
||||
if not (_crate.multiple) or ctld.enableAllCrates then
|
||||
local isJTAC = ctld.isJTACUnitType(_crate.unit)
|
||||
--ctld.logTrace("isJTAC = [%s]", ctld.p(isJTAC))
|
||||
if not isJTAC or (isJTAC and ctld.JTAC_dropEnabled) then
|
||||
if _crate.side == nil or (_crate.side == _unit:getCoalition()) then
|
||||
local _crateRadioMsg = _crate.desc
|
||||
--add in the number of crates required to build something
|
||||
if _crate.cratesRequired ~= nil and _crate.cratesRequired > 1 then
|
||||
_crateRadioMsg = _crateRadioMsg .. " (" .. _crate.cratesRequired ..
|
||||
")"
|
||||
end
|
||||
if _crate.multiple then
|
||||
_crateRadioMsg = "* " .. _crateRadioMsg
|
||||
end
|
||||
local _menuEntry = { text = _crateRadioMsg, crate = _crate }
|
||||
--ctld.logTrace("_menuEntry = [%s]", ctld.p(_menuEntry))
|
||||
table.insert(menuEntries, _menuEntry)
|
||||
local itemNbSubmenu = 0
|
||||
local menuEntries = {}
|
||||
local _subMenuPath = missionCommands.addSubMenuForGroup(_groupId, _subMenuName, _cratesMenuPath)
|
||||
for _, _crate in pairs(_crates) do
|
||||
--ctld.logTrace("_crate = [%s]", ctld.p(_crate))
|
||||
if not (_crate.multiple) or ctld.enableAllCrates then
|
||||
local isJTAC = ctld.isJTACUnitType(_crate.unit)
|
||||
--ctld.logTrace("isJTAC = [%s]", ctld.p(isJTAC))
|
||||
if not isJTAC or (isJTAC and ctld.JTAC_dropEnabled) then
|
||||
if _crate.side == nil or (_crate.side == _unit:getCoalition()) then
|
||||
local _crateRadioMsg = _crate.desc
|
||||
--add in the number of crates required to build something
|
||||
if _crate.cratesRequired ~= nil and _crate.cratesRequired > 1 then
|
||||
_crateRadioMsg = _crateRadioMsg .. " (" .. _crate.cratesRequired ..
|
||||
")"
|
||||
end
|
||||
if _crate.multiple then
|
||||
_crateRadioMsg = "* " .. _crateRadioMsg
|
||||
end
|
||||
local _menuEntry = { text = _crateRadioMsg, crate = _crate }
|
||||
--ctld.logTrace("_menuEntry = [%s]", ctld.p(_menuEntry))
|
||||
table.insert(menuEntries, _menuEntry)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _i, _menu in ipairs(menuEntries) do
|
||||
--ctld.logTrace("_menu = [%s]", ctld.p(_menu))
|
||||
-- add the submenu item
|
||||
itemNbSubmenu = itemNbSubmenu + 1
|
||||
if itemNbSubmenu == 10 and _i < #menuEntries then -- page limit reached
|
||||
_subMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Next page"), _subMenuPath)
|
||||
itemNbSubmenu = 1
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, _menu.text, _subMenuPath,
|
||||
ctld.spawnCrate, { _unitName, _menu.crate.weight })
|
||||
end
|
||||
for _i, _menu in ipairs(menuEntries) do
|
||||
--ctld.logTrace("_menu = [%s]", ctld.p(_menu))
|
||||
-- add the submenu item
|
||||
itemNbSubmenu = itemNbSubmenu + 1
|
||||
if itemNbSubmenu == 10 and _i < #menuEntries then -- page limit reached
|
||||
_subMenuPath = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Next page"), _subMenuPath)
|
||||
itemNbSubmenu = 1
|
||||
end
|
||||
missionCommands.addCommandForGroup(_groupId, _menu.text, _subMenuPath,
|
||||
ctld.spawnCrate, { _unitName, _menu.crate.weight })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (ctld.enabledFOBBuilding or ctld.enableCrates) and _unitActions.crates then
|
||||
local _crateCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("CTLD Commands"), _rootPath)
|
||||
if ctld.vehicleCommandsPath[_unitName] == nil then
|
||||
ctld.vehicleCommandsPath[_unitName] = mist.utils.deepCopy(_crateCommands)
|
||||
end
|
||||
if ctld.hoverPickup == false or ctld.loadCrateFromMenu == true then
|
||||
if ctld.loadCrateFromMenu then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load Nearby Crate(s)"),
|
||||
_crateCommands, ctld.loadNearbyCrate, _unitName)
|
||||
end
|
||||
end
|
||||
|
||||
if ctld.loadCrateFromMenu or ctld.hoverPickup then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Crate(s)"),
|
||||
_crateCommands, ctld.dropSlingCrate, { _unitName })
|
||||
end
|
||||
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unpack Any Crate"),
|
||||
_crateCommands, ctld.unpackCrates, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Nearby Crates"),
|
||||
_crateCommands, ctld.listNearbyCrates, { _unitName })
|
||||
|
||||
if ctld.enabledFOBBuilding then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List FOBs"), _crateCommands,
|
||||
ctld.listFOBS, { _unitName })
|
||||
end
|
||||
|
||||
if ctld.enableRepackingVehicles == true then
|
||||
ctld.updateRepackMenu( _unitName ) -- add repack menu
|
||||
end
|
||||
end
|
||||
|
||||
if ctld.enableSmokeDrop then
|
||||
local _smokeMenu = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Smoke Markers"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Red Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Blue Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Blue })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Orange Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Orange })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Green Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Green })
|
||||
end
|
||||
|
||||
if ctld.enabledRadioBeaconDrop then
|
||||
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Radio Beacons"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands,
|
||||
ctld.listRadioBeacons, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Beacon"), _radioCommands,
|
||||
ctld.dropRadioBeacon, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Remove Closest Beacon"),
|
||||
_radioCommands, ctld.removeRadioBeacon, { _unitName })
|
||||
elseif ctld.deployedRadioBeacons ~= {} then
|
||||
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Radio Beacons"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands,
|
||||
ctld.listRadioBeacons, { _unitName })
|
||||
end
|
||||
|
||||
ctld.addedTo[tostring(_groupId)] = true
|
||||
ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo))
|
||||
ctld.logTrace("done adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
||||
end
|
||||
|
||||
if (ctld.enabledFOBBuilding or ctld.enableCrates) and _unitActions.crates then
|
||||
local _crateCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("CTLD Commands"), _rootPath)
|
||||
if ctld.vehicleCommandsPath[_unitName] == nil then
|
||||
ctld.vehicleCommandsPath[_unitName] = mist.utils.deepCopy(_crateCommands)
|
||||
end
|
||||
if ctld.hoverPickup == false or ctld.loadCrateFromMenu == true then
|
||||
if ctld.loadCrateFromMenu then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load Nearby Crate(s)"),
|
||||
_crateCommands, ctld.loadNearbyCrate, _unitName)
|
||||
end
|
||||
end
|
||||
|
||||
if ctld.loadCrateFromMenu or ctld.hoverPickup then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Crate(s)"),
|
||||
_crateCommands, ctld.dropSlingCrate, { _unitName })
|
||||
end
|
||||
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unpack Any Crate"),
|
||||
_crateCommands, ctld.unpackCrates, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Nearby Crates"),
|
||||
_crateCommands, ctld.listNearbyCrates, { _unitName })
|
||||
|
||||
if ctld.enabledFOBBuilding then
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List FOBs"), _crateCommands,
|
||||
ctld.listFOBS, { _unitName })
|
||||
end
|
||||
|
||||
if ctld.enableRepackingVehicles == true then
|
||||
ctld.updateRepackMenu( _unitName ) -- add repack menu
|
||||
end
|
||||
end
|
||||
|
||||
if ctld.enableSmokeDrop then
|
||||
local _smokeMenu = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Smoke Markers"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Red Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Blue Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Blue })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Orange Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Orange })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Green Smoke"), _smokeMenu,
|
||||
ctld.dropSmoke, { _unitName, trigger.smokeColor.Green })
|
||||
end
|
||||
|
||||
if ctld.enabledRadioBeaconDrop then
|
||||
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Radio Beacons"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands,
|
||||
ctld.listRadioBeacons, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Beacon"), _radioCommands,
|
||||
ctld.dropRadioBeacon, { _unitName })
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Remove Closest Beacon"),
|
||||
_radioCommands, ctld.removeRadioBeacon, { _unitName })
|
||||
elseif ctld.deployedRadioBeacons ~= {} then
|
||||
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId,
|
||||
ctld.i18n_translate("Radio Beacons"), _rootPath)
|
||||
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands,
|
||||
ctld.listRadioBeacons, { _unitName })
|
||||
end
|
||||
|
||||
ctld.addedTo[tostring(_groupId)] = true
|
||||
ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo))
|
||||
ctld.logTrace("done adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
||||
end
|
||||
end
|
||||
end)
|
||||
if (not status) then
|
||||
ctld.logError(string.format("Error adding f10 to transport: %s", error))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7773,7 +7782,7 @@ function ctld.TreatOrbitJTAC(params, t)
|
||||
if ctld.JTACInRoute[k] == nil and ctld.OrbitInUse[k] == nil then -- if JTAC is in route
|
||||
ctld.JTACInRoute[k] = timer.getTime() -- update time of the last run
|
||||
end
|
||||
|
||||
|
||||
if ctld.jtacCurrentTargets[k] ~= nil then -- if target lased by JTAC
|
||||
local droneAlti = Unit.getByName(k):getPoint().y
|
||||
if ctld.OrbitInUse[k] == nil then -- if JTAC is not in orbit => start orbiting and update start time
|
||||
@@ -7784,7 +7793,7 @@ function ctld.TreatOrbitJTAC(params, t)
|
||||
if timer.getTime() > (ctld.OrbitInUse[k] + 60) then -- each 60" update orbit coord
|
||||
ctld.StartOrbitGroup(k, ctld.jtacCurrentTargets[k].name, droneAlti, 100) -- do orbit JTAC
|
||||
ctld.OrbitInUse[k] = timer.getTime() -- update time of the last orbit run
|
||||
end
|
||||
end
|
||||
end
|
||||
else -- if JTAC have no target
|
||||
if ctld.InOrbitList(k) == true then -- JTAC orbiting, without target => stop orbit
|
||||
@@ -7802,12 +7811,12 @@ end
|
||||
-- Make orbit the group "_grpName", on target "_unitTargetName". _alti in meters, speed in km/h
|
||||
function ctld.StartOrbitGroup(_jtacUnitName, _unitTargetName, _alti, _speed)
|
||||
if (Unit.getByName(_unitTargetName) ~= nil) and (Unit.getByName(_jtacUnitName) ~= nil) then -- si target unit and JTAC group exist
|
||||
local orbit = { id = 'Orbit',
|
||||
local orbit = { id = 'Orbit',
|
||||
params = {pattern = 'Circle',
|
||||
point = mist.utils.makeVec2(mist.getAvgPos(mist.makeUnitTable({_unitTargetName}))),
|
||||
speed = _speed,
|
||||
altitude = _alti
|
||||
}
|
||||
}
|
||||
}
|
||||
local jtacGroupName = Unit.getByName(_jtacUnitName):getGroup():getName()
|
||||
Unit.getByName(_jtacUnitName):getController():popTask() -- stop current Task
|
||||
@@ -7818,10 +7827,10 @@ end
|
||||
-- test if one unitName already is targeted by a JTAC
|
||||
function ctld.InOrbitList(_grpName)
|
||||
for k, v in pairs(ctld.OrbitInUse) do -- for each orbit in use
|
||||
if k == _grpName then
|
||||
if k == _grpName then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-------------------------------------------------------------------------------------------
|
||||
@@ -7853,10 +7862,10 @@ function ctld.backToRoute(_jtacUnitName)
|
||||
local jtacGroupName = Unit.getByName(_jtacUnitName):getGroup():getName()
|
||||
--local JTACRoute = mist.getGroupRoute(jtacGroupName, true) -- get the initial editor route of the current group
|
||||
local JTACRoute = mist.utils.deepCopy(mist.getGroupRoute(jtacGroupName, true)) -- get the initial editor route of the current group
|
||||
local newJTACRoute = ctld.adjustRoute(JTACRoute, ctld.getNearestWP(_jtacUnitName))
|
||||
local newJTACRoute = ctld.adjustRoute(JTACRoute, ctld.getNearestWP(_jtacUnitName))
|
||||
|
||||
local Mission = {}
|
||||
Mission = { id = 'Mission', params = {route = {points = newJTACRoute}}}
|
||||
local Mission = {}
|
||||
Mission = { id = 'Mission', params = {route = {points = newJTACRoute}}}
|
||||
|
||||
-- unactive orbit mode if it's on
|
||||
if ctld.InOrbitList(_jtacUnitName) == true then -- if JTAC orbiting => stop it
|
||||
@@ -7887,26 +7896,56 @@ function ctld.adjustRoute(_initialRouteTable, _firstWpOfNewRoute) -- create a r
|
||||
|
||||
-- apply offset (_firstWpOfNewRoute) to SwitchWaypoint tasks
|
||||
local lastWpAsAlreadySwitchWaypoint = false
|
||||
for idx = 1, #adjustedRoute do
|
||||
for j=1, #adjustedRoute[idx].task.params.tasks do
|
||||
if adjustedRoute[idx].task.params.tasks[j].id ~= "ControlledTask" then
|
||||
if adjustedRoute[idx].task.params.tasks[j].params.action.id == "SwitchWaypoint" then
|
||||
local goToWaypointIndex = adjustedRoute[idx].task.params.tasks[j].params.action.params.goToWaypointIndex
|
||||
adjustedRoute[idx].task.params.tasks[j].params.action.params.fromWaypointIndex = idx
|
||||
adjustedRoute[idx].task.params.tasks[j].params.action.params.goToWaypointIndex = mappingWP[goToWaypointIndex]
|
||||
if idx == #adjustedRoute then
|
||||
lastWpAsAlreadySwitchWaypoint = true
|
||||
for idx2 = 1, #adjustedRoute do
|
||||
if #adjustedRoute[idx2] and
|
||||
#adjustedRoute[idx2].task and
|
||||
#adjustedRoute[idx2].task.params and
|
||||
#adjustedRoute[idx2].task.params.tasks then
|
||||
|
||||
for j=1, #adjustedRoute[idx2].task.params.tasks do
|
||||
if adjustedRoute[idx2].task.params.tasks[j].id and
|
||||
adjustedRoute[idx2].task.params.tasks[j].id ~= "ControlledTask" then
|
||||
|
||||
if adjustedRoute[idx2].task.params.tasks[j].params and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.action and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.action.id and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.action.id == "SwitchWaypoint" then
|
||||
|
||||
if adjustedRoute[idx2].task.params.tasks[j].params.action.params then
|
||||
|
||||
local goToWaypointIndex = adjustedRoute[idx2].task.params.tasks[j].params.action.params.goToWaypointIndex
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.action.params.fromWaypointIndex = idx2
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.action.params.goToWaypointIndex = mappingWP[goToWaypointIndex]
|
||||
if idx2 == #adjustedRoute then
|
||||
lastWpAsAlreadySwitchWaypoint = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
else -- for "ControlledTask"
|
||||
if adjustedRoute[idx].task.params.tasks[j].params.task.params.action.id == "SwitchWaypoint" then
|
||||
local goToWaypointIndex = adjustedRoute[idx].task.params.tasks[j].params.task.params.action.params.goToWaypointIndex
|
||||
adjustedRoute[idx].task.params.tasks[j].params.task.params.action.params.fromWaypointIndex = idx
|
||||
adjustedRoute[idx].task.params.tasks[j].params.task.params.action.params.goToWaypointIndex = mappingWP[goToWaypointIndex]
|
||||
if idx == #adjustedRoute then
|
||||
lastWpAsAlreadySwitchWaypoint = true
|
||||
|
||||
else -- for "ControlledTask"
|
||||
if adjustedRoute[idx2].task.params.tasks[j].params and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params.action and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.id and
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.id == "SwitchWaypoint" then
|
||||
|
||||
if adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.params then
|
||||
|
||||
local goToWaypointIndex = adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.params.goToWaypointIndex
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.params.fromWaypointIndex = idx2
|
||||
adjustedRoute[idx2].task.params.tasks[j].params.task.params.action.params.goToWaypointIndex = mappingWP[goToWaypointIndex]
|
||||
if idx2 == #adjustedRoute then
|
||||
lastWpAsAlreadySwitchWaypoint = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7957,7 +7996,7 @@ ctld.reconMenuName = ctld.i18n_translate("RECON") --name of the
|
||||
ctld.reconRadioAdded = {} --stores the groups that have had the radio menu added
|
||||
ctld.reconLosSearchRadius = 2000 -- search radius in meters
|
||||
ctld.reconLosMarkRadius = 100 -- mark radius dimension in meters
|
||||
ctld.reconAutoRefreshLosTargetMarks = true -- if true recon LOS marks are automaticaly refreshed on F10 map
|
||||
ctld.reconAutoRefreshLosTargetMarks = false -- if true recon LOS marks are automaticaly refreshed on F10 map
|
||||
ctld.reconLastScheduleIdAutoRefresh = 0
|
||||
|
||||
---- F10 RECON Menus ------------------------------------------------------------------
|
||||
@@ -8465,7 +8504,7 @@ end
|
||||
--- Handle world events.
|
||||
ctld.eventHandler = {}
|
||||
function ctld.eventHandler:onEvent(event)
|
||||
ctld.logTrace("ctld.eventHandler:onEvent()")
|
||||
--ctld.logTrace("ctld.eventHandler:onEvent()")
|
||||
if event == nil then
|
||||
ctld.logError("Event handler was called with a nil event!")
|
||||
return
|
||||
@@ -8478,7 +8517,7 @@ function ctld.eventHandler:onEvent(event)
|
||||
elseif event.id == world.event.S_EVENT_BIRTH then
|
||||
eventName = "S_EVENT_BIRTH"
|
||||
else
|
||||
ctld.logTrace("Ignoring event %s", ctld.p(event))
|
||||
--ctld.logTrace("Ignoring event %s", ctld.p(event))
|
||||
return
|
||||
end
|
||||
ctld.logDebug("caught event %s: %s", ctld.p(eventName), ctld.p(event))
|
||||
@@ -8513,7 +8552,7 @@ function ctld.eventHandler:onEvent(event)
|
||||
for _, aircraftType in pairs(ctld.aircraftTypeTable) do
|
||||
if aircraftType == playerTypeName then
|
||||
ctld.logTrace("adding by aircraft type, unitName = %s", ctld.p(unitName))
|
||||
if ctld.isValueInIpairTable(ctld.transportPilotNames, unitName) == false then
|
||||
if ctld.tools.isValueInIpairTable(ctld.transportPilotNames, unitName) == false then
|
||||
table.insert(ctld.transportPilotNames, unitName) -- add transport unit to the list
|
||||
end
|
||||
if ctld.addedTo[tostring(_groupId)] == nil then -- only if menu not already set up
|
||||
@@ -8591,7 +8630,8 @@ function ctld.RandomReal(mini, maxi)
|
||||
end
|
||||
|
||||
-- Tools
|
||||
function ctld.isValueInIpairTable(tab, value)
|
||||
ctld.tools = {}
|
||||
function ctld.tools.isValueInIpairTable(tab, value)
|
||||
for i, v in ipairs(tab) do
|
||||
if v == value then
|
||||
return true -- La valeur existe
|
||||
@@ -8599,6 +8639,58 @@ function ctld.isValueInIpairTable(tab, value)
|
||||
end
|
||||
return false -- La valeur n'existe pas
|
||||
end
|
||||
------------------------------------------------------------------------------------
|
||||
--- Calculates the orientation of an end point relative to a reference point.
|
||||
--- The calculation takes into account the current orientation of the reference point.
|
||||
---
|
||||
--- @param refLat number Latitude of the reference point in degrees.
|
||||
--- @param refLon number Longitude of the reference point in degrees.
|
||||
--- @param refHeading number Current orientation of the reference point in degrees (0 = North, 90 = East).
|
||||
--- @param destLat number Latitude of the arrival point in degrees.
|
||||
--- @param destLon number Longitude of the arrival point in degrees.
|
||||
--- @param resultFormat string The desired output format: "radian", "degree" or "clock".
|
||||
--- @return number The relative orientation in the specified resultFormat.
|
||||
function ctld.tools.getRelativeBearing(refLat, refLon, refHeading, destLat, destLon, resultFormat)
|
||||
-- Converting degrees to radians for geometric calculations
|
||||
local radrefLat = math.rad(refLat)
|
||||
local raddestLat = math.rad(destLat)
|
||||
local radrefLon = math.rad(refLon)
|
||||
local raddestLon = math.rad(destLon)
|
||||
local radrefHeading = math.rad(refHeading)
|
||||
|
||||
-- Calculating the longitude difference between the two points
|
||||
local deltaLon = raddestLon - radrefLon
|
||||
|
||||
-- Using the great circle formula for azimuth (bearing)
|
||||
-- This formula is based on spherical trigonometry and uses atan2
|
||||
-- to correctly handle all quadrants.
|
||||
local y = math.sin(deltaLon) * math.cos(raddestLat)
|
||||
local x = math.cos(radrefLat) * math.sin(raddestLat) - math.sin(radrefLat) * math.cos(raddestLat) * math.cos(deltaLon)
|
||||
local absoluteBearingRad = math.atan2(y, x)
|
||||
|
||||
-- Calculate relative orientation by subtracting the reference refHeading
|
||||
local relativeBearingRad = absoluteBearingRad - radrefHeading
|
||||
|
||||
-- Normalizes the angle to be in the range [-pi, pi]
|
||||
-- This ensures a consistent angle, whether positive or negative.
|
||||
local normalizedRad = (relativeBearingRad + math.pi) % (2 * math.pi) - math.pi
|
||||
|
||||
-- Returns the value in the requested resultFormat
|
||||
if resultFormat == "radian" then
|
||||
return normalizedRad, resultFormat
|
||||
elseif resultFormat == "clock" then
|
||||
-- Convert to clock position (12h = front, 3h = right, 6h = back, etc..)
|
||||
local bearingDeg = math.deg(normalizedRad)
|
||||
local clockPosition = ((bearingDeg + 360) % 360) / 30
|
||||
clockPosition = clockPosition >= 0 and math.floor(clockPosition + 0.5) or math.ceil(clockPosition - 0.5), resultFormat -- rounded clockPosition
|
||||
if clockPosition == 0 then clockPosition = 12 end
|
||||
return clockPosition, resultFormat
|
||||
else -- By default, the resultFormat is "degree"
|
||||
resultFormat = "degree"
|
||||
local bearingDeg = math.deg(normalizedRad)
|
||||
return (bearingDeg + 360) % 360, resultFormat
|
||||
end
|
||||
end
|
||||
|
||||
--- Enable/Disable error boxes displayed on screen.
|
||||
env.setErrorMessageBoxEnabled(false)
|
||||
|
||||
@@ -11,6 +11,8 @@ It's open-source and free as in free beer (you don't have to pay to use it), and
|
||||
|
||||
We're always looking for help, please reach out to [Zip on Discord](https://discordapp.com/users/421317390807203850) if you want to participate in maintenance or development.
|
||||
|
||||
And if you'd like to support our work, you can [buy me a coffee](https://coff.ee/veaf_zip)!
|
||||
|
||||
## Contents
|
||||
|
||||
This script is a rewrite of some of the functionality of the original Complete Combat Troop Transport Script (CTTS) by Geloxo (http://forums.eagle.ru/showthread.php?t=108523), as well as adding new features.
|
||||
@@ -130,7 +132,7 @@ Optionaly, you can disable the STTS (text to speech over SRS) feature (4).
|
||||
![dynamic_loading]
|
||||
|
||||
### Script Setup
|
||||
**This script requires MIST version 4.0.57 or above: https://github.com/mrSkortch/MissionScriptingTools**
|
||||
🔴IMPORTANT: This script requires MIST supplied in the CTLD zipped pack to correctly manage dynamic spwans.
|
||||
|
||||
First make sure MIST is loaded, either as an Initialization Script for the mission or the first DO SCRIPT with a "TIME MORE" of 1. "TIME MORE" means run the actions after X seconds into the mission.
|
||||
|
||||
@@ -865,10 +867,11 @@ For example, if the laser code is *1688*, the frequency will be *40.40Mhz*.
|
||||
JTAC frequency is available through the "JTAC Status" radio menu
|
||||
|
||||
#### Jtac-automatic-orbiting-over-lased-target
|
||||
By setting parameter ctld.enableAutoOrbitingFlyingJtacOnTarget = true, a script
|
||||
dedicated script puts in orbit each flying JTAC over his detected target.
|
||||
Associated with CTLD/JTAC functions, you can assign a fly route to the JTAC drone,
|
||||
this one follow it, and start orbiting when he detects a target.
|
||||
|
||||
By setting parameter ctld.enableAutoOrbitingFlyingJtacOnTarget = true, a script dedicated script puts in orbit each flying JTAC over his detected target.
|
||||
|
||||
Associated with CTLD/JTAC functions, you can assign a fly route to the JTAC drone, this one follow it, and start orbiting when he detects a target.
|
||||
|
||||
As soon as it don't detect a target, it restart following its initial route at the nearest waypoint
|
||||
|
||||
# In Game
|
||||
@@ -927,11 +930,13 @@ ctld.JTAC_WEIGHT = 15 -- kg
|
||||
```
|
||||
## Limit troop Loading
|
||||
The number of Infantries units in mission can be limited by setting the table below :
|
||||
ctld.nbLimitSpwanedTroops = {0, 0} -- {redLimitInfantryCount, blueLimitInfantryCount}
|
||||
|
||||
`ctld.nbLimitSpawnedTroops = {0, 0} -- {redLimitInfantryCount, blueLimitInfantryCount}`
|
||||
|
||||
When this cumulative number of troops is reached for a coalition, no more troops can be loaded onboard, and a message is sent to player.
|
||||
|
||||
If ctld.nbLimitSpwanedTroops = {0, 0} (both values at 0) the limit control is disabled.
|
||||
If set to `ctld.nbLimitSpawnedTroops = {0, 0}` (both values at 0) the limit control is disabled. This is the default.
|
||||
|
||||
If either value is non-zero, limit control becomes active for both coalitions.
|
||||
|
||||
## Cargo Spawning and Sling Loading
|
||||
@@ -1024,13 +1029,14 @@ Rearming:
|
||||
You can also repair a partially destroyed HAWK / BUK or KUB system by dropping a repair crate next to it and unpacking. A repair crate will also re-arm the system.
|
||||
|
||||
## Crate Repacking
|
||||
The F10 menu allows you to repack units having associated crate types in the "ctld.spawnableCrates" table.
|
||||
Simply land near the unit you wish to repack and select it from the list presented by the "CTLD//Vehicle/FOB transport...//Repack Vehicles" menu.
|
||||
The defined radius of vehicles detection is specified by the parameter
|
||||
|
||||
ctld.maximumDistanceRepackableUnitsSearch = 200 -- max distance from transportUnit to search force repackable units in meters
|
||||
The F10 menu allows you to repack units having associated crate types in the "ctld.spawnableCrates" table.
|
||||
|
||||
Simply land near the unit you wish to repack and select it from the list presented by the "CTLD//Vehicle/FOB transport...//Repack Vehicles" menu.
|
||||
|
||||
The defined radius of vehicles detection is specified by the parameter `ctld.maximumDistanceRepackableUnitsSearch` (default 200 meters).
|
||||
|
||||
WARNING: Due to technical reasons related to the refresh time of the F10 menus, there may be inconsistencies between the type of vehicles requested and those provided. It is recommended to wait 5 to 10 seconds without moving after landing and opening the F10 menu for a packaging order.
|
||||
*WARNING*: Due to technical reasons related to the refresh time of the F10 menus, there may be inconsistencies between the type of vehicles requested and those provided. It is recommended to wait 5 to 10 seconds without moving after landing and opening the F10 menu for a packaging order.
|
||||
|
||||
## Forward Operating Base (FOB) Construction
|
||||
FOBs can be built by loading special FOB crates from a **Logistics** unit into a C-130 or other large aircraft configured in the script. To load the crate use the F10 - Troop Commands Menu. The idea behind FOBs is to make player vs player missions even more dynamic as these can be deployed in most locations. Once destroyed the FOB can no longer be used.
|
||||
|
||||
@@ -35,7 +35,7 @@ mist = {}
|
||||
-- don't change these
|
||||
mist.majorVersion = 4
|
||||
mist.minorVersion = 5
|
||||
mist.build = "128-DYNSLOTS-01"
|
||||
mist.build = "128-DYNSLOTS-02"
|
||||
|
||||
-- forward declaration of log shorthand
|
||||
local log
|
||||
@@ -826,11 +826,11 @@ do -- the main scope
|
||||
if zone.verticies then
|
||||
local offset = {}
|
||||
for i = 1, #zone.verticies do
|
||||
table.insert(offset, {dist = mist.utils.get2DDist(uRef.point, zone.verticies[i]), heading = mist.getHeadingPoints(uRef.point, zone.verticies[i]) + uRef.heading})
|
||||
table.insert(offset, {dist = mist.utils.get2DDist(uRef.point, zone.verticies[i]), heading = mist.utils.getHeadingPoints(uRef.point, zone.verticies[i]) + uRef.heading})
|
||||
end
|
||||
zone.offset = offset
|
||||
else
|
||||
zone.offset = {dist = mist.utils.get2DDist(uRef.point, zone.point), heading = mist.getHeadingPoints(uRef.point, zone.point) + uRef.heading}
|
||||
zone.offset = {dist = mist.utils.get2DDist(uRef.point, zone.point), heading = mist.utils.getHeadingPoints(uRef.point, zone.point) + uRef.heading}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user