Version: 2.2
4YA_HQ.lua is a DCS World Lua mission script that adds a dynamic HQ mission
subscription system. Players use the F10 menu to register for pre-defined
strike / escort objectives. Once enough players sign up for the same target, an
AI support flight is automatically scheduled and spawned to accompany them.
Designed for the 4YA Project Overlord WWII Normandy theatre, the script coordinates ground-attack sorties for both Allied (blue) and Axis (red) coalitions across a rotating set of 8 mission days.
function HQ.Init()
HQ.UnitsList() -- Scan existing DCS groups to find safe IDs
HQ.assignedPlayers_Init() -- Build empty roster tables for current mission
HQ.update() -- Start periodic player-scan loop (every 15 s)
timer.scheduleFunction(HQ.countAssignedPlayers, nil, timer.getTime() + 20)
end
HQ.UnitsList() queries mist.DBs.groupsById / mist.DBs.unitsById to set the
starting counters for AI group/unit IDs so dynamically spawned flights never
collide with existing mission objects.assignedPlayers_Init() walks the mission data table and creates empty
player rosters for every target/role slot.HQ.PlayerGroups() scans all [all][plane] and [all][helicopter] units via
mist.makeUnitTable(). For each unit that:
Unit.getPlayerName() is not nil), andUnit.isActive())the group is added to HQ.PilotGroups and HQ.F10Menu() is called to build
customised per-group F10 menus.
HQ.F10Menu() creates a "Mission registration" root menu for each new
player group, then populates sub-menus like:
Mission registration
├─ Objective: TARGET 1 Coastwatcher radar
│ ├─ Intended Role: Ground attack
│ └─ Intended Role: Escort
└─ Objective: TARGET 2 Naval radar
...
Menu entries are cached (HQ.builtF10Menus) so they are built only once
per group.
When a player clicks a role, HQ.registeredMission(args) runs:
HQ.PilotGroups.HQ.PlayerOccupied – a player can only register once until they
land, crash, die, or leave the aircraft.HQ.assignedPlayers (global pool + per-role pool).A world.addEventHandler (HQ.playerEventHandler) listens for:
| Event | Action |
|---|---|
EVENTS.LAND |
Remove player from occupied list & roster |
EVENTS.CRASH / DEATH |
Same as above |
EVENTS.PLAYER_LEAVE_UNIT |
Same as above |
AI aircraft landing (UnitName contains "AI support") |
Clears currentAI for that coalition |
HQ.countAssignedPlayers() runs every 20 seconds:
>= HQ.requiredPlayersForAISupport (default 4) and an
AI flight is not already active for that mission, HQ.setupAI() is called.HQ.setupAI():
AIDB (so the original template is
never mutated).AI support 1, AI support 2, …).TimeToRV seconds.HQ.AIswitchWP) that switches the AI to
their bombing waypoint when the lead player gets within 8 km of the
IP.HQ.BombsAway() polls the AI group's bomb load every 20 seconds. Once all
bombs are expended, the AI are sent to their RTB waypoint (#4).
HQ.missions[missionNumber][coalition][targetIndex]{
Title = "TARGET 1",
Target = "Coastwatcher radar",
Roles = { { RoleName = "Ground attack" }, { RoleName = "Escort" } },
Briefing = "Ramrod 970. Coastwatcher radar station ...",
Coordinates = {
RV = { x = ..., y = ... },
IP = { x = ..., y = ... },
TGT = { x = ..., y = ... }
},
TimeToRV = 600 -- seconds until AI spawns
}
AIDBTwo templates per coalition (red[1], red[2], blue[1], blue[2]).
These are DCS Group table definitions (identical to mission.lua
exports) containing a 4-ship flight with waypoints, tasks, and weapons.
Overall Script Lifecycle
Player Discovery & F10 Menu
Mission Registration Flow
AI Support Spawn Flow
Event Handler (State Cleanup)
4YA_HQ.lua into your DCS mission's "Scripts" folder or
"l10n/DEFAULT" folder."Mission" and
set it to a value between 1 and 8 (the server rotation day).4YA_HQ.lua.| # | Fix | Reason |
|---|---|---|
| 1 | Red AI coalition ID changed from 66 -> 1 |
coalition.addGroup(66, ...) is invalid in DCS; it silently fails/crashes |
| 2 | AI templates are now deep-copied before mutation | Previously NewAI = AIDB.red[rand] assigned a reference; every spawn permanently overwrote the global template |
| 3 | Missing AIGroupName argument added to red storeCurrentAI() |
Blue branch passed the argument; red did not, leaving currentAI.Group nil and breaking later logic |
| 4 | currentMission flag validation added |
If the "Mission" trigger flag is missing or out-of-range, the old script crashed on first table access |
| 5 | Removed dead condition Weapons[j].category == 3 or == 3 |
Duplicate check left a logic error / dead code branch |
| 6 | briefing variable now local inside HQ.briefing() |
Was writing to a global named briefing, clobbering any other global table called briefing |
| 7 | UnitName nil guard added in EVENTS.LAND handler |
string.find(UnitName, "AI support") crashed when unit:getName() returned nil (destroyed/despawned aircraft) |
| # | Fix | Reason |
|---|---|---|
| 7 | All top-level globals (HQ, AIDB, etc.) are now local |
Prevents namespace pollution and accidental cross-script interference |
| 8 | currentMission localized with fallback guard |
if not HQ.missions[currentMission] then ... end + default to mission 1 |
| 9 | AI counters scoped under HQ (HQ.AI_group_ID, HQ.AI_units_ID, HQ.AIspawnNo) |
Previously declared at end of file after they were used, causing scope issues |
| 10 | HQ.nearestBase() returns local NearestBaseId |
Was leaking the return value back into the global namespace |
| 11 | Groups_By_ID and Units_By_ID localized |
Was leaking MIST table references to globals |
| 12 | Magic event IDs replaced by named constants (EVENTS table) |
4 -> EVENTS.LAND, 6 -> EVENTS.CRASH, 9 -> EVENTS.DEATH, 21 -> EVENTS.PLAYER_LEAVE_UNIT |
| # | Fix | Reason |
|---|---|---|
| 13 | HQ.PlayerOccupied now uses table.remove() |
Previously nilled entries (tbl[i] = nil) created holes. # on a holey array is unpredictable in Lua, causing skipped entries during cleanup |
| 14 | Renamed HQ.clearPlayerOccupied -> HQ.playerEventHandler |
The old name was misleading (it never "cleared" anything) and shadowed the real clearAssignedPlayers function name |
| 15 | Stale comment -- SET BACK TO 4 AFTER TESTING removed |
Value was already 4; comment was outdated and confusing |
| 16 | Removed dead AIDB empty table assignments |
AIDB.red[1] = {} immediately followed by AIDB.red[1] = <table> — the empty table was discarded |
| # | Fix | Found in |
|---|---|---|
| 17 | vegicles -> vehicles |
Mission 3, Target 4 briefing |
| 18 | thos position -> this position |
Mission 5, Target 3 briefing |
| 19 | carrid -> carried |
Mission 6, Target 3 briefing |
| 20 | vehivles -> vehicles |
Mission 7, Target 4 briefing |
| # | Fix | Old count -> New count |
|---|---|---|
| 21 | Trailing whitespace stripped from all lines | ~800 whitespace warnings -> 0 |
| 22 | Blank-only lines cleaned up | ~50 blank-line warnings -> 0 |
| 23 | Tab characters converted to 2-space indentation | Mixed tabs+spaces -> consistent |
| 24 | Removed ~20 commented-out env.info("HQ DEBUG ...") lines |
Dead debug code removed |
| 25 | Created .luacheckrc with DCS World globals |
Enables clean linting for env, timer, mist, trigger, coalition, etc. |
| File | Purpose |
|---|---|
4YA_HQ.lua |
Cleaned production script (use this one) |
4YA_HQ_old.lua |
Original script (kept for reference) |
.luacheckrc |
Luacheck configuration for DCS globals whitelist |
README.md |
This documentation |
| Version | Date | Changes |
|---|---|---|
| 1.0.4 | 30.08.2023 | Original release |
| 2.0 | -- | Critical bug fixes, scope cleanup, deep-copy for AI templates, event constants, full luacheck compliance |
| 2.1 | -- | |
| 2.2 | -- | |
| 2.2.1 | Current |