4YA Project Overlord – HQ Mission Subscription Script

Version: 2.2

Overview

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.


How It Works

1. Script Initialisation

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

2. Player Discovery (every 15 seconds)

HQ.PlayerGroups() scans all [all][plane] and [all][helicopter] units via mist.makeUnitTable(). For each unit that:

the group is added to HQ.PilotGroups and HQ.F10Menu() is called to build customised per-group F10 menus.

3. F10 Menu Construction

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.

4. Mission Registration

When a player clicks a role, HQ.registeredMission(args) runs:

  1. Looks up the pilot name from HQ.PilotGroups.
  2. Checks HQ.PlayerOccupied – a player can only register once until they land, crash, die, or leave the aircraft.
  3. Calculates RV, IP, and TGT coordinates (ground elevation is filled automatically).
  4. Stores the player in HQ.assignedPlayers (global pool + per-role pool).
  5. Drops F10 map marks (RV, IP, TGT) for that group only.
  6. Broadcasts the intent to the whole coalition.
  7. Schedules a text briefing to be sent 15 seconds later.

5. Event Handling (clearing state)

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

6. AI Support Trigger

HQ.countAssignedPlayers() runs every 20 seconds:

7. AI Setup & Spawn

HQ.setupAI():

  1. Generates random offsets around the IP (spawn) and target points.
  2. Deep-copies an AI template from AIDB (so the original template is never mutated).
  3. Assigns fresh group/unit IDs and names (AI support 1, AI support 2, …).
  4. Overwrites the template's waypoints with the actual mission RV, IP and TGT coordinates.
  5. Schedules the spawn after TimeToRV seconds.
  6. Schedules a proximity check (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).

8. Data Tables

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
}

AIDB

Two 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.


Logic Flow Diagrams

Overall Script Lifecycle

Mission StartsHQ.InitHQ.UnitsListassignedPlayers_Initupdate LoopcountAssignedPlayers Loop

Overall Script Lifecycle

Player Discovery & F10 Menu

start update LoopClear PilotGroupsScan all planes + helosUnit has active player?Add to PilotGroupsMore units?F10MenuMenu already built?Build Mission Root MenuAdd Objectives + RolesCache in builtF10MenusSchedule next loop YESNOYESNOYESNO

Player Discovery & F10 Menu

Mission Registration Flow

Player clicks F10 RoleregisteredMissionPlayer already occupied?Reject - already assignedLook up Mission DataBuild RV/IP/TGT PointsAdd to assignedPlayersAdd to PlayerOccupiedDrop F10 Map MarksBroadcast to CoalitionSchedule Briefing +15sReturn YESNO

Mission Registration Flow

AI Support Spawn Flow

countAssignedPlayersFind highest-player mission per coalitionPlayers = threshold?Schedule next checkAI already active?Cooldown expired?setupAIDeep-copy AIDB TemplateAssign fresh IDs + NamesOverwrite Route PointsSchedule spawnAI at TimeToRVSchedule AIswitchWP at TimeToRV + 300Store currentAI state NOYESYESNONOYES

AI Support Spawn Flow

Event Handler (State Cleanup)

DCS Event FiresEvent Type?Get InitiatorIgnorePlayer or AI?Remove from PlayerOccupiedclearAssignedPlayersRemove Map MarksRemove from RolesclearCurrentAI LANDCRASH/DEATHPLAYER_LEAVEOtherPlayerAI contains 'AI support'

Event Handler (State Cleanup)


Installation

  1. Copy 4YA_HQ.lua into your DCS mission's "Scripts" folder or "l10n/DEFAULT" folder.
  2. In the Mission Editor, create a trigger flag named "Mission" and set it to a value between 1 and 8 (the server rotation day).
  3. Add a "Do Script File" trigger at mission start that loads 4YA_HQ.lua.
  4. Ensure the MIST scripting framework is loaded before this script.

Fix List (4YA_HQ.lua vs 4YA_HQ_old.lua)

Critical Runtime Bugs

# 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)

Structural / Scope Fixes

# 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

Data / Memory Fixes

# 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

Typos / Consistency

# 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

Style / Lint

# 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.

Files

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 History

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