Skip to content

GUER Air Defense Loop

rayswaynl edited this page Jul 6, 2026 · 5 revisions

GUER Air-Defense Loop (standalone Ka-137 / Mi-24 town defenders)

Source-verified 2026-07-06 against branch claude/cmdcon44-wddm @ 1ac5a3f (Build 89 line). Paths relative to Missions/[55-2hc]warfarev2_073v48co.chernarus/ unless noted. Arma 2 OA 1.64.

GUER ("Insurgents", resistance) has no AI commander — so it has no wildcard deck and no Commander-driven air. The GUER Air-Defense Loop (Server/Server_GuerAirDef.sqf, B61, Ray 2026-06-21) is therefore GUER's only AI air: a single server-only maintain loop that keeps one airframe orbiting over each active, GUER-held town. Since B61 the script has grown from a 3-outcome picker into a 5-outcome priority ladder (now 631 lines): an Igla counter-air Ka-137 when enemy aircraft are overhead, a cargo/paradrop variant that script-drops a GUER infantry stick under ground attack (build83), a Mi-24_P gunship on large contested towns, an EASA AT-5 Ka-137, and the default recon-MG Ka-137 — plus two cmdcon42 (2026-07-02) sub-systems layered onto the Ka-137 paths: swarm rolls (a 2nd/3rd drone into the same group) and a randomized flare stock (5–20 countermeasure budget). The loop self-cleans on a global alive cap, a per-defender lifetime recycle, and a quiet-despawn when no enemies are near.

The header is explicit about provenance (Server/Server_GuerAirDef.sqf:1-41): the loop's shape/guards are modelled on Server/Server_GuerStipend.sqf, and its self-clean (crew + hull + group teardown) is modelled on the W13 GUNSHIP STRIKE block in Server/Functions/AI_Commander_Wildcard.sqf. Nothing the loop spawns is tagged wfbe_persistent, so it can never accumulate across the registry rebuild.

Why it exists outside the playable-side gate

The loop keys entirely off town ownership (sideID == WFBE_C_GUER_ID, i.e. 2, Common/Init/Init_CommonConstants.sqf:32), not off whether GUER is a playable side. A B62 fix (2026-06-21) moved its execVM out of the old WFBE_C_GUER_PLAYERSIDE > 0 block — previously the loop was dead in production because the playable-side param was 0 (Server/Init/Init_Server.sqf:935-938 carries the B62 rationale comment). GUER is always the AI town-defender, so the air-def must run regardless.

Launch wiring (Server/Init/Init_Server.sqf)

Element Detail Source
AIPatrol compile AIPatrol = Compile preprocessFile "Server\AI\Orders\AI_Patrol.sqf" (the patrol order the loop calls) Server/Init/Init_Server.sqf:26
Launch gate if (isServer && {(missionNamespace getVariable ["WFBE_C_GUER_AIRDEF_ENABLE", 1]) > 0}) Server/Init/Init_Server.sqf:939
Launch [] execVM "Server\Server_GuerAirDef.sqf" Server/Init/Init_Server.sqf:940
Placement gated only on isServer + the enable flag, independent of WFBE_C_GUER_PLAYERSIDE (B62 comment block) Server/Init/Init_Server.sqf:935-942

The script re-checks isServer and WFBE_C_GUER_AIRDEF_ENABLE internally (Server/Server_GuerAirDef.sqf:42,46), so it is double-guarded.

Boot sequence (Server/Server_GuerAirDef.sqf)

Stage Behavior Source
Server gate if !(isServer) exitWith {} Server/Server_GuerAirDef.sqf:42
Enable gate if ((missionNamespace getVariable ["WFBE_C_GUER_AIRDEF_ENABLE", 1]) < 1) exitWith {} Server/Server_GuerAirDef.sqf:46
Read core tunables all WFBE_C_GUER_AIRDEF_* via 2-arg getVariable with inline fallbacks Server/Server_GuerAirDef.sqf:50-60
Read drop tunables _dropChance / _dropCount / _dropMax Server/Server_GuerAirDef.sqf:67-69
Read swarm tunables _swarmOn / _swarmChance / _swarmChance3 Server/Server_GuerAirDef.sqf:77-79
Read flare tunables _flareOn / _flareMin / _flareMax / _flareLauncher, with a _flareMax >= _flareMin guard Server/Server_GuerAirDef.sqf:95-100
Boot wait waitUntil for towns populated and WFBE_L_GUE side-logic non-null, then sleep 45 to let town ownership settle Server/Server_GuerAirDef.sqf:103-107
Crew classes _pilotClass = WFBE_GUERRESPILOT (default "GUE_Soldier_Pilot"), _crewClass = WFBE_GUERRESCREW (default "GUE_Soldier_Crew") Server/Server_GuerAirDef.sqf:109-110
Flare applicator _applyKaFlares helper defined (see flare-stock section) Server/Server_GuerAirDef.sqf:118-136
Registries _defenders = [] (air; entries [town, vehicle, group, spawnTime, lastEnemyTime]) and _drops = [] (paradropped squads; entries [town, group, spawnTime, lastEnemyTime]) — script-local, not wfbe_persistent Server/Server_GuerAirDef.sqf:140,146
Marker feed init WFBE_ACTIVE_GUER_AIR = []; publicVariable "WFBE_ACTIVE_GUER_AIR" so a JIP client never reads nil Server/Server_GuerAirDef.sqf:154-155

The main loop then runs while {!WFBE_GameOver} do { sleep _interval; ... } (Server/Server_GuerAirDef.sqf:157-158) — the cadence sleep is at the top, so the first maintain pass happens one full interval after boot.

Maintain cadence — three passes per interval

Every WFBE_C_GUER_AIRDEF_INTERVAL (default 120 s) the loop runs prune (air) → prune (paradropped squads) → maintain, then rebuilds the marker feed.

(1) Prune + self-clean the air registry (Server/Server_GuerAirDef.sqf:163-219)

The registry is rebuilt into _kept; any entry that should die is torn down and dropped, freeing a slot in the global cap.

Drop reason Condition Source
destroyed isNull _eVeh || !(alive _eVeh) Server/Server_GuerAirDef.sqf:180
town_lost town sideID != WFBE_C_GUER_ID Server/Server_GuerAirDef.sqf:186
town_inactive town wfbe_active is false Server/Server_GuerAirDef.sqf:187
quiet (_now - _eLastEnemy) > _quiet — no enemies near the town for WFBE_C_GUER_AIRDEF_QUIET_DESPAWN s Server/Server_GuerAirDef.sqf:197
lifetime (_now - _eSpawn) > _lifetime — forced recycle (anti-accumulation) Server/Server_GuerAirDef.sqf:200

Enemy presence is recomputed each pass: west/east units alive within (town range, min 600 m) of the town (Server/Server_GuerAirDef.sqf:191-194). A surviving entry with enemies present has its lastEnemyTime stamped to _now, which is what holds off the quiet despawn.

Teardown is player-safe (B66, Server/Server_GuerAirDef.sqf:209-210): the hull + its crew are deleted only if no crew member is a player ({isPlayer _x} count (crew _eVeh)) == 0); group teardown deletes only non-player units (if (!(isPlayer _x)) then {deleteVehicle _x}). GUER is playable, so a player who boarded a defender is never deleted — the registry entry is dropped either way, so the maintain sweep simply stops tracking that hull. Each despawn logs a GUERAIRDEF|DESPAWN diag line (Server/Server_GuerAirDef.sqf:211).

(1b) Prune + self-clean the paradropped squads (build83, Server/Server_GuerAirDef.sqf:220-269)

The ground squads dropped by the cargo variant get the same lifecycle so they cannot accumulate:

Drop reason Condition Source
wiped group null or no living units left Server/Server_GuerAirDef.sqf:239-240
town_lost / town_inactive same town-ownership checks as the air Server/Server_GuerAirDef.sqf:246-247
quiet / lifetime same _quiet / _lifetime constants as the air Server/Server_GuerAirDef.sqf:257-258

Enemy presence refresh mirrors the air pass (Server/Server_GuerAirDef.sqf:251-254); teardown deletes only non-player units then the group (Server/Server_GuerAirDef.sqf:262), logging GUERAIRDEF|DROPDESPAWN (:263). The surviving list becomes _drops and _dropAlive = count _drops feeds the global drop cap (Server/Server_GuerAirDef.sqf:268-269).

(3) Maintain — one defender per active GUER town (Server/Server_GuerAirDef.sqf:271-619)

Iterating forEach towns, the loop spawns a fresh defender for a town only when all of these hold (Server/Server_GuerAirDef.sqf:276-280):

Gate Condition Source
Under cap _aliveCount < _maxAir (WFBE_C_GUER_AIRDEF_MAX) Server/Server_GuerAirDef.sqf:276
Town valid !(isNull _town) Server/Server_GuerAirDef.sqf:277
GUER-held (_town getVariable ["sideID", -1]) == WFBE_C_GUER_ID Server/Server_GuerAirDef.sqf:278
Active _town getVariable ["wfbe_active", false] Server/Server_GuerAirDef.sqf:279
No live air already !(_town in _townsWithAir) Server/Server_GuerAirDef.sqf:280

Airframe selection — the 5-step priority ladder

Per qualifying town the loop first measures the threat (Server/Server_GuerAirDef.sqf:284-300):

Input Rule Source
Enemies near town west/east units alive within (town range, min 600 m) — counts allUnits, i.e. infantry + vehicle occupants Server/Server_GuerAirDef.sqf:285
Enemy AIR near town crewed west/east Air hulls in the same radius, counted over vehicles — an empty parked heli reads side CIV and is ignored Server/Server_GuerAirDef.sqf:287-290
LARGE-town test maxSupplyValue >= WFBE_C_GUER_AIRDEF_LARGE_SV OR wfbe_town_type ∈ {LargeTown1,LargeTown2,HugeTown1,HugeTown2} Server/Server_GuerAirDef.sqf:292-296
Town already has a drop any _drops entry for this town (one paradrop per town at a time) Server/Server_GuerAirDef.sqf:298-300

Then the rolls run in strict priority order (Server/Server_GuerAirDef.sqf:302-324 — the comment block spells the ladder out):

# Outcome Condition Source
1 Igla counter-air Ka-137 _enemyAir > 0 AND random 1 < WFBE_C_GUER_AIRDEF_AA_CHANCE (0.75) — beats everything so GUER contests hostile air instead of orbiting with an MG it cannot elevate Server/Server_GuerAirDef.sqf:313
2 Cargo/paradrop variant not AA, _enemies > 0, (_enemies - _enemyAir) > 0 (genuine GROUND threat — a pure air raid never pulls an infantry drop), !_townHasDrop, _dropAlive < _dropMax, random 1 < WFBE_C_GUER_AIRDEF_DROP_CHANCE Server/Server_GuerAirDef.sqf:314-322
3 Mi-24_P gunship not AA/drop, town is LARGE, _enemies > 0, random 1 < WFBE_C_GUER_AIRDEF_MI24_CHANCE Server/Server_GuerAirDef.sqf:323
4 EASA AT-5 Ka-137 not AA/drop/Mi-24, random 1 < WFBE_C_GUER_AIRDEF_AT_CHANCE Server/Server_GuerAirDef.sqf:324
5 Recon-MG Ka-137 (default) none of the above rolled

The hull class is _classMi24 only for outcome 3; every other outcome flies the Ka-137 (_class = if (_useMi24) then {_classMi24} else {_classKa}, Server/Server_GuerAirDef.sqf:327) — the paradrop variant deliberately keeps the default recon-MG drone as the delivery bird (Server/Server_GuerAirDef.sqf:326). A quiet town, or a small town, can never field a Mi-24.

Spawn, crew, and orders

Step Detail Source
Spawn point airborne 900 m off the town at a random bearing, height _flyHeight + 60 Server/Server_GuerAirDef.sqf:330-331
Create vehicle [_class, _spawnPos, resistance, _ang, false, true, true, "FLY"] Call WFBE_CO_FNC_CreateVehicle Server/Server_GuerAirDef.sqf:332
Create group [resistance, "guer-airdef"] Call WFBE_CO_FNC_CreateGroup Server/Server_GuerAirDef.sqf:335
Pilot WFBE_CO_FNC_CreateUnitmoveInDriver Server/Server_GuerAirDef.sqf:342-343
Gunner also a gunner for both airframes (B62: the Ka-137 MainTurret is gunner-fired, so a pilot-only Ka-137 flies but never engages) → moveInGunner Server/Server_GuerAirDef.sqf:344-346
Tags _veh setVariable ["wfbe_guer_airdef", true, true] and ["wfbe_guer_airdef_town", _town] Server/Server_GuerAirDef.sqf:374-375
Orders (never idle) flyInHeight _flyHeightAIPatrol over town → setBehaviour "COMBAT", setCombatMode "RED", setSpeedMode "NORMAL" Server/Server_GuerAirDef.sqf:394-398

The order sequence is load-bearing (B66, Server/Server_GuerAirDef.sqf:389-393): AIPatrol internally re-sets behaviour to AWARE/YELLOW, so it must run before the engage posture, otherwise it clobbers COMBAT/RED and the air just orbits passively. Failed spawns tear down the freshly-created hull player-safely and log GUERAIRDEF|SPAWNFAIL with a reason — no_pilot (Server/Server_GuerAirDef.sqf:602-609), no_group (:610-614) or createVehicle_null (:615-617).

Turret loadout swaps (server-side)

EASA_Init.sqf runs client-only, so WFBE_EASA_Loadouts / the client EASA_Equip path are not present in the server's missionNamespace (Server/Server_GuerAirDef.sqf:31-37). Both special loadouts are therefore applied directly on the server, on the MainTurret path [-1], using the exact classnames from the Ka-137 EASA entries:

Variant Commands Source
Igla AA (outcome 1) strip 100Rnd_762x54_PKT / PKT, then addMagazineTurret ["2Rnd_Igla","2Rnd_Igla"], addWeaponTurret ["Igla_twice"]; load tag "IglaAA" Server/Server_GuerAirDef.sqf:362-368
AT-5 (outcome 4) strip 100Rnd_762x54_PKT / PKT, then addMagazineTurret ["5Rnd_AT5_BRDM2","64Rnd_57mm"], addWeaponTurret ["AT5Launcher","57mmLauncher"]; load tag "AT5" Server/Server_GuerAirDef.sqf:352-358

Both are true swaps (PKT removed first), and the load tag lands in the GUERAIRDEF|SPAWN diag line. Swarm extras repeat the same swap idiom (Server/Server_GuerAirDef.sqf:446-451).

Cargo/paradrop variant (build83)

When outcome 2 wins, the recon-MG Ka-137 flies in as the "delivery bird" (load tag "cargoDrop", Server/Server_GuerAirDef.sqf:369-371) and the infantry are script-spawned under parachutes — the drone has no real cargo hold to eject from:

Step Behavior Source
Squad group [resistance, "guer-airdrop"] Call WFBE_CO_FNC_CreateGroup, registered into _drops immediately so the prune pass owns it from birth Server/Server_GuerAirDef.sqf:506,513-515
Roster / chute soldiers picked from WFBE_GUERPARACHUTELEVEL1 (default ["GUE_Soldier_1","GUE_Soldier_AT","GUE_Soldier_2","GUE_Soldier_3","GUE_Soldier_MG","GUE_Soldier_Medic"]), chute class WFBE_GUERPARACHUTE (default "ParachuteC") Server/Server_GuerAirDef.sqf:507-508
Drop thread Spawn-ed sub-thread: sleep 20 (lets the bird arrive), then WFBE_C_GUER_AIRDEF_DROP_COUNT (default 5) iterations of create soldier + create chute + moveInDriver, spaced 0.3 s Server/Server_GuerAirDef.sqf:517-558
Landing waits up to 75 s for all chutes to land, dismounts, deletes the chutes Server/Server_GuerAirDef.sqf:563-581
Orders AIPatrol over the town, then COMBAT/RED (same order-sensitive sequence as the air) Server/Server_GuerAirDef.sqf:585-590

The squad is bounded three ways: one live drop per town (_townHasDrop), a global alive cap (WFBE_C_GUER_AIRDEF_DROP_MAX, default 2 squads), and the shared quiet/lifetime prune in pass (1b).

Ka-137 swarm rolls (cmdcon42, 2026-07-02)

A freshly spawned Ka-137 (never the Mi-24, never the drop bird) can bring wingmen (Server/Server_GuerAirDef.sqf:404-493):

Rule Condition Source
2nd drone WFBE_C_GUER_KA137_SWARM >= 1, class is the Ka, not a drop, still under _maxAir, random 1 < WFBE_C_GUER_KA137_SWARM_CHANCE (0.25) Server/Server_GuerAirDef.sqf:416-420
3rd drone only if the 2nd rolled, (_aliveCount + 1) < _maxAir, random 1 < WFBE_C_GUER_KA137_SWARM_CHANCE3 (0.15) Server/Server_GuerAirDef.sqf:425
Same group extras' pilot/gunner are created into the leader's _grp — no new group (explicit comment: keeps the group count flat) Server/Server_GuerAirDef.sqf:415,436-442
Placement 30–50 m radial offset from the leader, height staggered +15 m per extra Server/Server_GuerAirDef.sqf:432-435
Registry each extra gets its own _defenders entry (own lifetime/quiet clock, counts against _maxAir) but shares the leader's group Server/Server_GuerAirDef.sqf:474

Extras inherit the leader's tags, the AT/Igla swap idiom where applicable, and the flare stock (Server/Server_GuerAirDef.sqf:446-451,461-462,467).

Flare stock (cmdcon42, 2026-07-02)

Every spawned Ka-137 (leader if _class == _classKa, Server/Server_GuerAirDef.sqf:381-382; every swarm extra, :467) gets a randomized countermeasure budget via the _applyKaFlares helper (Server/Server_GuerAirDef.sqf:118-136):

  • Budget roll: _n = _flareMin + floor(random (_flareMax - _flareMin + 1)) — inclusive WFBE_C_GUER_KA137_FLARES_MIN.._MAX, default 5–20 (Server/Server_GuerAirDef.sqf:123).
  • Mounts the manual OA launcher + flare magazine on turret path [-1]: the launcher is tunable (WFBE_C_GUER_KA137_FLARE_LAUNCHER, default "CMFlareLauncher"); the magazine classname "60Rnd_CMFlareMagazine" is a hardcoded literal, not a tunable (Server/Server_GuerAirDef.sqf:100,125-126).
  • A deferred sub-thread (sleep 3) then writes _veh setVariable ["FlareCount", _n, true] — deliberately after CM_Set.sqf's default write, to win the race (Server/Server_GuerAirDef.sqf:81-94,128-133). Client\Module\CM\CM_AutoCM_OA.sqf consumes FlareCount to auto-pop flares against incoming missiles — see Vehicle countermeasure flares & spoofing.
  • This is a different system from the player-bought Ka-137's kill-tier flare mags (WFBE_C_GUER_KA137_FLARE_MAGS, Common/Init/Init_CommonConstants.sqf:120-122) — only the launcher constant is shared between the two.

Map-marker feed (GUER players only)

After all passes, the loop rebuilds WFBE_ACTIVE_GUER_AIR = [[vehicle, sideID], ...] from the live air registry (alive hulls only) and publicVariables it every interval (B67, Server/Server_GuerAirDef.sqf:621-630). updatepatrolmarkers.sqf reads this array; the sideID is always the GUER id, and the client side-gates on WFBE_Client_SideID, so only GUER players see these air arrows. The re-broadcast each interval doubles as the JIP catch-up safety net (publicVariable is not JIP-replayed in A2-OA). Note the feed carries only the air — paradropped squads from pass (1b) are never fed into WFBE_ACTIVE_GUER_AIR.

Tunable set (Common/Init/Init_CommonConstants.sqf)

All values are isNil-guarded defaults — override any of them before mission init to retune without touching the loop.

Constant Default Meaning Source
WFBE_C_GUER_AIRDEF_ENABLE 1 master switch (set 0 to disable the loop entirely) Common/Init/Init_CommonConstants.sqf:195
WFBE_C_GUER_AIRDEF_INTERVAL 120 seconds between maintain sweeps Common/Init/Init_CommonConstants.sqf:196
WFBE_C_GUER_AIRDEF_MAX 4 global alive cap on GUER air defenders (hard FPS bound) Common/Init/Init_CommonConstants.sqf:197
WFBE_C_GUER_AIRDEF_AT_CHANCE 0.20 chance a spawned Ka-137 carries the EASA AT (AT-5) loadout Common/Init/Init_CommonConstants.sqf:198
WFBE_C_GUER_AIRDEF_MI24_CHANCE 0.25 chance a LARGE GUER town under attack fields a Mi-24 gunship instead Common/Init/Init_CommonConstants.sqf:199
WFBE_C_GUER_AIRDEF_AA_CHANCE 0.75 chance the counter-air Igla Ka-137 spawns when enemy aircraft are near (priority #1) Common/Init/Init_CommonConstants.sqf:200
WFBE_C_GUER_AIRDEF_CLASS_KA "Ka137_MG_PMC" default light air defender (recon/strike) Common/Init/Init_CommonConstants.sqf:201
WFBE_C_GUER_AIRDEF_CLASS_MI24 "Mi24_P" heavy gunship for large contested towns Common/Init/Init_CommonConstants.sqf:202
WFBE_C_GUER_AIRDEF_LIFETIME 900 max seconds a defender (or dropped squad) lives before forced recycle Common/Init/Init_CommonConstants.sqf:203
WFBE_C_GUER_AIRDEF_QUIET_DESPAWN 300 despawn after this many seconds with no enemies near the town Common/Init/Init_CommonConstants.sqf:204
WFBE_C_GUER_AIRDEF_LARGE_SV 2500 maxSupplyValue at/above which a town counts as LARGE (Mi-24 eligible); town_type Large/Huge also qualifies Common/Init/Init_CommonConstants.sqf:205
WFBE_C_GUER_AIRDEF_HEIGHT 120 flyInHeight for spawned GUER air Common/Init/Init_CommonConstants.sqf:206
WFBE_C_GUER_KA137_SWARM 1 swarm master switch Common/Init/Init_CommonConstants.sqf:212
WFBE_C_GUER_KA137_SWARM_CHANCE 0.25 chance of a 2nd same-group drone Common/Init/Init_CommonConstants.sqf:213
WFBE_C_GUER_KA137_SWARM_CHANCE3 0.15 chance of a 3rd drone (only rolled if the 2nd won) Common/Init/Init_CommonConstants.sqf:214
WFBE_C_GUER_KA137_FLARES 1 flare-stock master switch Common/Init/Init_CommonConstants.sqf:223
WFBE_C_GUER_KA137_FLARES_MIN 5 minimum flare budget per Ka-137 Common/Init/Init_CommonConstants.sqf:224
WFBE_C_GUER_KA137_FLARES_MAX 20 maximum flare budget per Ka-137 Common/Init/Init_CommonConstants.sqf:225
WFBE_C_GUER_AIRDEF_DROP_CHANCE 0.18 chance a ground-attacked town pulls the paradrop variant Common/Init/Init_CommonConstants.sqf:330
WFBE_C_GUER_AIRDEF_DROP_COUNT 5 soldiers per paradrop stick Common/Init/Init_CommonConstants.sqf:331
WFBE_C_GUER_AIRDEF_DROP_MAX 2 global alive cap on dropped squads Common/Init/Init_CommonConstants.sqf:332

Also read by the loop: WFBE_C_GUER_KA137_FLARE_LAUNCHER (default "CMFlareLauncher", Common/Init/Init_CommonConstants.sqf:121 — shared with the player-Ka-137 flare system) and, with inline fallbacks only (no central default), WFBE_GUERRESPILOT / WFBE_GUERRESCREW (Server/Server_GuerAirDef.sqf:109-110) and WFBE_GUERPARACHUTELEVEL1 / WFBE_GUERPARACHUTE (:507-508).

Map / port notes

The source tree carries the mission under Missions/, Missions_Vanilla/, and Modded_Missions/. The loop is map-agnostic — it keys off the runtime towns array and town sideID/maxSupplyValue/wfbe_town_type, not off map geometry — so it ports as a straight copy. The Takistan port at Missions_Vanilla/[61-2hc]warfarev2_073v48co.takistan/Server/Server_GuerAirDef.sqf is byte-identical to the Chernarus copy (631 lines, matching SHA-256), wired into that mission's Server/Init/Init_Server.sqf:939-940 with the same isServer && WFBE_C_GUER_AIRDEF_ENABLE gate; the shared Init_CommonConstants.sqf is likewise byte-identical between the two maps.

Continue Reading

Sidebar

Clone this wiki locally