Inital Commit

This commit is contained in:
iTracerFacer 2025-11-17 07:34:08 -06:00
commit 38c45e8ef0
8 changed files with 6244 additions and 0 deletions

BIN
Cargo_Delivered.ogg Normal file

Binary file not shown.

View File

@ -0,0 +1,871 @@
--[[
Moose_TDAC_CargoDispatcher.lua
Automated Logistics System for TADC Squadron Replenishment
DESCRIPTION:
This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them.
It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system.
CONFIGURATION:
- Update static templates and airfield lists as needed for your mission.
- Set thresholds and supply airfields in CARGO_SUPPLY_CONFIG.
- Replace static templates with actual group templates from the mission editor for realism.
REQUIRES:
- MOOSE framework (for SPAWN, AIRBASE, etc.)
- Optional: MIST for deep copy of templates
]]
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
if _G.__TDAC_DISPATCHER_RUNNING then
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
return
end
_G.__TDAC_DISPATCHER_RUNNING = true
--[[
GLOBAL STATE AND CONFIGURATION
--------------------------------------------------------------------------
Tracks all active cargo missions and dispatcher configuration.
]]
if not cargoMissions then
cargoMissions = { red = {}, blue = {} }
end
-- Dispatcher config (interval in seconds)
if not DISPATCHER_CONFIG then
-- default interval (seconds) and a slightly larger grace period to account for slow servers/networks
DISPATCHER_CONFIG = { interval = 60, gracePeriod = 25 }
end
-- Safety flag: when false, do NOT fall back to spawning from in-memory template tables.
-- Set to true if you understand the tweaked-template warning and accept the risk.
if DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE == nil then
DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE = false
end
--[[
CARGO SUPPLY CONFIGURATION
--------------------------------------------------------------------------
Set supply airfields, cargo template names, and resupply thresholds for each coalition.
]]
local CARGO_SUPPLY_CONFIG = {
red = {
supplyAirfields = { "Sochi-Adler", "Gudauta", "Sukhumi-Babushara", "Nalchik", "Beslan", "Maykop-Khanskaya" }, -- replace with your RED supply airbase names
cargoTemplate = "CARGO_RED_AN26", -- replace with your RED cargo aircraft template name
threshold = 0.90 -- ratio below which to trigger resupply (testing)
},
blue = {
supplyAirfields = { "Batumi", "Kobuleti", "Senaki-Kolkhi", "Kutaisi", "Soganlug" }, -- replace with your BLUE supply airbase names
cargoTemplate = "CARGO_BLUE_C130", -- replace with your BLUE cargo aircraft template name
threshold = 0.90 -- ratio below which to trigger resupply (testing)
}
}
--[[
UTILITY STUBS
--------------------------------------------------------------------------
selectRandomAirfield: Picks a random airfield from a list.
announceToCoalition: Stub for in-game coalition messaging.
Replace with your own logic as needed.
]]
if not selectRandomAirfield then
function selectRandomAirfield(airfieldList)
if type(airfieldList) == "table" and #airfieldList > 0 then
return airfieldList[math.random(1, #airfieldList)]
end
return nil
end
end
-- Stub for announceToCoalition (replace with your own logic if needed)
if not announceToCoalition then
function announceToCoalition(coalitionKey, message)
-- Replace with actual in-game message logic
env.info("[ANNOUNCE] [" .. tostring(coalitionKey) .. "]: " .. tostring(message))
end
end
--[[
LOGGING
--------------------------------------------------------------------------
Advanced logging configuration and helper function for debug output.
]]
local ADVANCED_LOGGING = {
enableDetailedLogging = false,
logPrefix = "[TADC Cargo]"
}
-- Logging function (must be defined before any log() calls)
local function log(message, detailed)
if not detailed or ADVANCED_LOGGING.enableDetailedLogging then
env.info(ADVANCED_LOGGING.logPrefix .. " " .. message)
end
end
log("═══════════════════════════════════════════════════════════════════════════════", true)
log("Moose_TDAC_CargoDispatcher.lua loaded.", true)
log("═══════════════════════════════════════════════════════════════════════════════", true)
-- Provide a safe deepCopy if MIST is not available
local function deepCopy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do
if type(v) == 'table' then
res[k] = deepCopy(v)
else
res[k] = v
end
end
return res
end
-- Dispatch cooldown per airbase (seconds) to avoid repeated immediate retries
local CARGO_DISPATCH_COOLDOWN = DISPATCHER_CONFIG and DISPATCHER_CONFIG.cooldown or 300 -- default 5 minutes
local lastDispatchAttempt = { red = {}, blue = {} }
local function getCoalitionSide(coalitionKey)
if coalitionKey == 'blue' then return coalition.side.BLUE end
if coalitionKey == 'red' then return coalition.side.RED end
return nil
end
-- Forward-declare parking check helper so functions defined earlier can call it
local destinationHasSuitableParking
-- Validate dispatcher configuration: check that supply airfields exist and templates appear valid
local function validateDispatcherConfig()
local problems = {}
-- Check supply airfields exist
for coalitionKey, cfg in pairs(CARGO_SUPPLY_CONFIG) do
if cfg and cfg.supplyAirfields and type(cfg.supplyAirfields) == 'table' then
for _, abName in ipairs(cfg.supplyAirfields) do
local ok, ab = pcall(function() return AIRBASE:FindByName(abName) end)
if not ok or not ab then
table.insert(problems, string.format("Missing airbase for %s supply list: '%s'", tostring(coalitionKey), tostring(abName)))
end
end
else
table.insert(problems, string.format("Missing or invalid supplyAirfields for coalition '%s'", tostring(coalitionKey)))
end
-- Check cargo template presence (best-effort using SPAWN:New if available)
if cfg and cfg.cargoTemplate and type(cfg.cargoTemplate) == 'string' and cfg.cargoTemplate ~= '' then
local okSpawn, spawnObj = pcall(function() return SPAWN:New(cfg.cargoTemplate) end)
if not okSpawn or not spawnObj then
-- SPAWN:New may not be available at load time; warn but don't fail hard
table.insert(problems, string.format("Cargo template suspicious or missing: '%s' (coalition: %s)", tostring(cfg.cargoTemplate), tostring(coalitionKey)))
end
else
table.insert(problems, string.format("Missing cargoTemplate for coalition '%s'", tostring(coalitionKey)))
end
end
if #problems == 0 then
log("TDAC Dispatcher config validation passed ✓", true)
MESSAGE:New("TDAC Dispatcher config validation passed ✓", 15):ToAll()
return true, {}
else
log("TDAC Dispatcher config validation found issues:", true)
MESSAGE:New("TDAC Dispatcher config validation found issues:" .. table.concat(problems, ", "), 15):ToAll()
for _, p in ipairs(problems) do
log("" .. p, true)
end
return false, problems
end
end
-- Expose console helper to run the check manually
function _G.TDAC_RunConfigCheck()
local ok, problems = validateDispatcherConfig()
if ok then
return true, "OK"
else
return false, problems
end
end
--[[
getSquadronStatus(squadron, coalitionKey)
--------------------------------------------------------------------------
Returns the current, max, and ratio of aircraft for a squadron.
If you track current aircraft in a table, update this logic accordingly.
Returns: currentCount, maxCount, ratio
]]
local function getSquadronStatus(squadron, coalitionKey)
local current = squadron.current or squadron.count or squadron.aircraft or 0
local max = squadron.max or squadron.aircraft or 1
if squadron.templateName and _G.squadronAircraftCounts and _G.squadronAircraftCounts[coalitionKey] then
current = _G.squadronAircraftCounts[coalitionKey][squadron.templateName] or current
end
local ratio = (max > 0) and (current / max) or 0
return current, max, ratio
end
--[[
hasActiveCargoMission(coalitionKey, airbaseName)
--------------------------------------------------------------------------
Returns true if there is an active (not completed/failed) cargo mission for the given airbase.
Failed missions are immediately removed from tracking to allow retries.
]]
local function hasActiveCargoMission(coalitionKey, airbaseName)
for i = #cargoMissions[coalitionKey], 1, -1 do
local mission = cargoMissions[coalitionKey][i]
if mission.destination == airbaseName then
-- Remove completed or failed missions immediately to allow retries
if mission.status == "completed" or mission.status == "failed" then
log("Removing " .. mission.status .. " cargo mission for " .. airbaseName .. " from tracking")
table.remove(cargoMissions[coalitionKey], i)
else
-- Consider mission active only if the group is alive OR we're still within the grace window
local stillActive = false
if mission.group and mission.group.IsAlive and mission.group:IsAlive() then
stillActive = true
else
local pending = mission._pendingStartTime
local grace = mission._gracePeriod or DISPATCHER_CONFIG.gracePeriod or 8
if pending and (timer.getTime() - pending) <= grace then
stillActive = true
end
end
if stillActive then
log("Active cargo mission found for " .. airbaseName .. " (" .. coalitionKey .. ")")
return true
end
end
end
end
log("No active cargo mission for " .. airbaseName .. " (" .. coalitionKey .. ")")
return false
end
--[[
trackCargoMission(coalitionKey, mission)
--------------------------------------------------------------------------
Adds a new cargo mission to the tracking table and logs it.
]]
local function trackCargoMission(coalitionKey, mission)
table.insert(cargoMissions[coalitionKey], mission)
log("Tracking new cargo mission: " .. (mission.group and mission.group:GetName() or "nil group") .. " from " .. mission.origin .. " to " .. mission.destination)
end
--[[
cleanupCargoMissions()
--------------------------------------------------------------------------
Removes failed cargo missions from the tracking table if their group is no longer alive.
]]
local function cleanupCargoMissions()
for _, coalitionKey in ipairs({"red", "blue"}) do
for i = #cargoMissions[coalitionKey], 1, -1 do
local m = cargoMissions[coalitionKey][i]
if m.status == "failed" or m.status == "completed" then
if not (m.group and m.group:IsAlive()) then
log("Cleaning up " .. m.status .. " cargo mission: " .. (m.group and m.group:GetName() or "nil group"))
table.remove(cargoMissions[coalitionKey], i)
end
end
end
end
end
--[[
dispatchCargo(squadron, coalitionKey)
--------------------------------------------------------------------------
Spawns a cargo aircraft from a supply airfield to the destination squadron airbase.
Uses static templates for each coalition, assigns a unique group name, and sets a custom route.
Tracks the mission and schedules route assignment with a delay to ensure group is alive.
]]
local function dispatchCargo(squadron, coalitionKey)
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
local origin
local attempts = 0
local maxAttempts = 10
local coalitionSide = getCoalitionSide(coalitionKey)
repeat
origin = selectRandomAirfield(config.supplyAirfields)
attempts = attempts + 1
-- Ensure origin is not the same as destination
if origin == squadron.airbaseName then
origin = nil
else
-- Validate that origin airbase exists and is controlled by correct coalition
local originAirbase = AIRBASE:FindByName(origin)
if not originAirbase then
log("WARNING: Origin airbase '" .. tostring(origin) .. "' does not exist. Trying another...")
origin = nil
elseif originAirbase:GetCoalition() ~= coalitionSide then
log("WARNING: Origin airbase '" .. tostring(origin) .. "' is not controlled by " .. coalitionKey .. " coalition. Trying another...")
origin = nil
end
end
until origin or attempts >= maxAttempts
-- enforce cooldown per destination to avoid immediate retries
lastDispatchAttempt[coalitionKey] = lastDispatchAttempt[coalitionKey] or {}
local last = lastDispatchAttempt[coalitionKey][squadron.airbaseName]
if last and (timer.getTime() - last) < CARGO_DISPATCH_COOLDOWN then
log("Skipping dispatch to " .. squadron.airbaseName .. " (cooldown active)")
return
end
if not origin then
log("No valid origin airfield found for cargo dispatch to " .. squadron.airbaseName .. " (avoiding same origin/destination)")
return
end
local destination = squadron.airbaseName
local cargoTemplate = config.cargoTemplate
-- Safety: check if destination has suitable parking for larger transports. If not, warn in log.
local okParking = true
-- Only check for likely large transports (C-130 / An-26 are large-ish) — keep conservative
if cargoTemplate and (string.find(cargoTemplate:upper(), "C130") or string.find(cargoTemplate:upper(), "C-17") or string.find(cargoTemplate:upper(), "C17") or string.find(cargoTemplate:upper(), "AN26") ) then
okParking = destinationHasSuitableParking(destination)
if not okParking then
log("WARNING: Destination '" .. tostring(destination) .. "' may not have suitable parking for " .. tostring(cargoTemplate) .. ". Skipping dispatch to prevent despawn.")
return
end
end
local groupName = cargoTemplate .. "_to_" .. destination .. "_" .. math.random(1000,9999)
log("Dispatching cargo: " .. groupName .. " from " .. origin .. " to " .. destination)
-- Spawn cargo aircraft at origin using the template name ONLY for SPAWN
-- Note: cargoTemplate is a config string; script uses in-file Lua template tables (CARGO_AIRCRAFT_TEMPLATE_*)
log("DEBUG: Attempting spawn for group: '" .. groupName .. "' at airbase: '" .. origin .. "' (using in-file Lua template)", true)
local airbaseObj = AIRBASE:FindByName(origin)
if not airbaseObj then
log("ERROR: AIRBASE:FindByName failed for '" .. tostring(origin) .. "'. Airbase object is nil!")
else
log("DEBUG: AIRBASE object found for '" .. origin .. "'. Proceeding with spawn.", true)
end
-- Select the correct template based on coalition
local templateBase, uniqueGroupName
if coalitionKey == "blue" then
templateBase = CARGO_AIRCRAFT_TEMPLATE_BLUE
uniqueGroupName = "CARGO_C130_DYNAMIC_" .. math.random(1000,9999)
else
templateBase = CARGO_AIRCRAFT_TEMPLATE_RED
uniqueGroupName = "CARGO_AN26_DYNAMIC_" .. math.random(1000,9999)
end
-- Clone the template and set the group/unit name
-- Prepare a mission placeholder. We'll set the group and spawnPos after successful spawn.
local mission = {
group = nil,
origin = origin,
destination = destination,
squadron = squadron,
status = "pending",
-- Anchor a pending start time now to avoid the monitor loop expiring a mission
-- before MOOSE has a chance to finalize the OnSpawnGroup callback.
_pendingStartTime = timer.getTime(),
_spawnPos = nil,
_gracePeriod = DISPATCHER_CONFIG.gracePeriod or 8
}
-- Helper to finalize mission after successful spawn
local function finalizeMissionAfterSpawn(spawnedGroup, spawnPos)
mission.group = spawnedGroup
mission._spawnPos = spawnPos
trackCargoMission(coalitionKey, mission)
lastDispatchAttempt[coalitionKey][squadron.airbaseName] = timer.getTime()
end
-- MOOSE-only spawn-by-name flow
if type(cargoTemplate) ~= 'string' or cargoTemplate == '' then
log("ERROR: cargoTemplate for coalition '" .. tostring(coalitionKey) .. "' must be a valid mission template name string. Aborting dispatch.")
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (invalid cargo template)!")
return
end
-- Use a per-dispatch RAT object to spawn and route cargo aircraft.
-- Create a unique alias to avoid naming collisions and let RAT handle routing/landing.
local alias = cargoTemplate .. "_TO_" .. destination .. "_" .. tostring(math.random(1000,9999))
log("DEBUG: Attempting RAT spawn for template: '" .. cargoTemplate .. "' alias: '" .. alias .. "'", true)
-- Validate destination airbase: RAT's "Airbase doesn't exist" error actually means
-- "Airbase not found OR not owned by the correct coalition" because RAT filters by coalition internally.
-- We perform the same validation here to fail fast with better error messages.
local destAirbase = AIRBASE:FindByName(destination)
local coalitionSide = getCoalitionSide(coalitionKey)
if not destAirbase then
log("ERROR: Destination airbase '" .. destination .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (airbase not found on map)!")
-- Mark mission as failed and cleanup immediately
mission.status = "failed"
return
end
local destCoalition = destAirbase:GetCoalition()
if destCoalition ~= coalitionSide then
log("INFO: Destination airbase '" .. destination .. "' captured by enemy - cargo dispatch skipped (normal mission state).", true)
-- No announcement to coalition - this is expected behavior when base is captured
-- Mark mission as failed and cleanup immediately
mission.status = "failed"
return
end
-- Validate origin airbase with same coalition filtering logic
local originAirbase = AIRBASE:FindByName(origin)
if not originAirbase then
log("ERROR: Origin airbase '" .. origin .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (airbase not found on map)!")
-- Mark mission as failed and cleanup immediately
mission.status = "failed"
return
end
local originCoalition = originAirbase:GetCoalition()
if originCoalition ~= coalitionSide then
log("INFO: Origin airbase '" .. origin .. "' captured by enemy - trying another supply source.", true)
-- Don't announce or mark as failed - the dispatcher will try another origin
return
end
local okNew, rat = pcall(function() return RAT:New(cargoTemplate, alias) end)
if not okNew or not rat then
log("ERROR: RAT:New failed for template '" .. tostring(cargoTemplate) .. "'. Error: " .. tostring(rat))
if debug and debug.traceback then
log("TRACEBACK: " .. tostring(debug.traceback(rat)), true)
end
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn init error)!")
-- Mark mission as failed and cleanup immediately - do NOT track failed RAT spawns
mission.status = "failed"
return
end
-- Configure RAT for a single, non-respawning dispatch
rat:SetDeparture(origin)
rat:SetDestination(destination)
rat:NoRespawn()
rat:InitUnControlled(false) -- force departing transports to spawn in a controllable state
rat:InitLateActivated(false)
rat:SetSpawnLimit(1)
rat:SetSpawnDelay(1)
-- CRITICAL: Force takeoff from runway to prevent aircraft getting stuck at parking
-- SetTakeoffRunway() ensures aircraft spawn directly on runway and take off immediately
if rat.SetTakeoffRunway then
rat:SetTakeoffRunway()
log("DEBUG: Configured cargo to take off from runway at " .. origin, true)
else
log("WARNING: SetTakeoffRunway() not available - falling back to SetTakeoffHot()", true)
if rat.SetTakeoffHot then rat:SetTakeoffHot() end
end
-- Ensure RAT will look for parking and not despawn the group immediately on landing.
-- This makes the group taxi to parking and come to a stop so other scripts (e.g. Load2nd)
-- that detect parked/stopped cargo aircraft can register the delivery.
if rat.SetParkingScanRadius then rat:SetParkingScanRadius(80) end
if rat.SetParkingSpotSafeON then rat:SetParkingSpotSafeON() end
if rat.SetDespawnAirOFF then rat:SetDespawnAirOFF() end
-- Check on runway to ensure proper landing behavior (distance in meters)
if rat.CheckOnRunway then rat:CheckOnRunway(true, 75) end
rat:OnSpawnGroup(function(spawnedGroup)
-- Mark the canonical start time when MOOSE reports the group exists
mission._pendingStartTime = timer.getTime()
local spawnPos = nil
local dcsGroup = spawnedGroup:GetDCSObject()
if dcsGroup then
local units = dcsGroup:getUnits()
if units and #units > 0 then
spawnPos = units[1]:getPoint()
end
end
log("RAT spawned cargo aircraft group: " .. tostring(spawnedGroup:GetName()))
-- Temporary debug: log group state every 10s for 10 minutes to trace landing/parking behavior
local debugChecks = 60 -- 60 * 10s = 10 minutes
local checkInterval = 10
local function debugLogState(iter)
if iter > debugChecks then return end
local ok, err = pcall(function()
local name = spawnedGroup:GetName()
local dcs = spawnedGroup:GetDCSObject()
if dcs then
local units = dcs:getUnits()
if units and #units > 0 then
local u = units[1]
local pos = u:getPoint()
-- Use dot accessor to test for function existence; colon-call to invoke
local vel = (u.getVelocity and u:getVelocity()) or {x=0,y=0,z=0}
local speed = math.sqrt((vel.x or 0)^2 + (vel.y or 0)^2 + (vel.z or 0)^2)
local controller = dcs:getController()
local airbaseObj = AIRBASE:FindByName(destination)
local dist = nil
if airbaseObj then
local dest = airbaseObj:GetCoordinate():GetVec2()
local dx = pos.x - dest.x
local dz = pos.z - dest.y
dist = math.sqrt(dx*dx + dz*dz)
end
log(string.format("[TDAC DEBUG] %s state check %d: alive=%s pos=(%.1f,%.1f) speed=%.2f m/s distToDest=%s", name, iter, tostring(spawnedGroup:IsAlive()), pos.x or 0, pos.z or 0, speed, tostring(dist)), true)
else
log(string.format("[TDAC DEBUG] %s state check %d: DCS group has no units", tostring(spawnedGroup:GetName()), iter), true)
end
else
log(string.format("[TDAC DEBUG] %s state check %d: no DCS group object", tostring(spawnedGroup:GetName()), iter), true)
end
end)
if not ok then
log("[TDAC DEBUG] Error during debugLogState: " .. tostring(err), true)
end
timer.scheduleFunction(function() debugLogState(iter + 1) end, {}, timer.getTime() + checkInterval)
end
timer.scheduleFunction(function() debugLogState(1) end, {}, timer.getTime() + checkInterval)
-- RAT should handle routing/taxi/parking. Finalize mission tracking now.
finalizeMissionAfterSpawn(spawnedGroup, spawnPos)
mission.status = "enroute"
mission._pendingStartTime = timer.getTime()
announceToCoalition(coalitionKey, "CARGO aircraft departing (airborne) for " .. destination .. ". Defend it!")
end)
local okSpawn, errSpawn = pcall(function() rat:Spawn(1) end)
if not okSpawn then
log("ERROR: rat:Spawn() failed for template '" .. tostring(cargoTemplate) .. "'. Error: " .. tostring(errSpawn))
if debug and debug.traceback then
log("TRACEBACK: " .. tostring(debug.traceback(errSpawn)), true)
end
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn error)!")
-- Mark mission as failed and cleanup immediately - do NOT track failed spawns
mission.status = "failed"
return
end
end
-- Parking diagnostics helper
-- Call from DCS console: _G.TDAC_LogAirbaseParking("Luostari Pechenga")
function _G.TDAC_LogAirbaseParking(airbaseName)
if type(airbaseName) ~= 'string' then
log("TDAC Parking helper: airbaseName must be a string", true)
return false
end
local base = AIRBASE:FindByName(airbaseName)
if not base then
log("TDAC Parking helper: AIRBASE:FindByName returned nil for '" .. tostring(airbaseName) .. "'", true)
return false
end
local function spotsFor(term)
local ok, n = pcall(function() return base:GetParkingSpotsNumber(term) end)
if not ok then return nil end
return n
end
local openBig = spotsFor(AIRBASE.TerminalType.OpenBig)
local openMed = spotsFor(AIRBASE.TerminalType.OpenMed)
local openMedOrBig = spotsFor(AIRBASE.TerminalType.OpenMedOrBig)
local runway = spotsFor(AIRBASE.TerminalType.Runway)
log(string.format("TDAC Parking: %s -> OpenBig=%s OpenMed=%s OpenMedOrBig=%s Runway=%s", airbaseName, tostring(openBig), tostring(openMed), tostring(openMedOrBig), tostring(runway)), true)
return true
end
-- Pre-dispatch safety check: ensure destination can accommodate larger transport types
destinationHasSuitableParking = function(destination, preferredTermTypes)
local base = AIRBASE:FindByName(destination)
if not base then return false end
preferredTermTypes = preferredTermTypes or { AIRBASE.TerminalType.OpenBig, AIRBASE.TerminalType.OpenMedOrBig, AIRBASE.TerminalType.OpenMed }
for _, term in ipairs(preferredTermTypes) do
local ok, n = pcall(function() return base:GetParkingSpotsNumber(term) end)
if ok and n and n > 0 then
return true
end
end
return false
end
--[[
monitorSquadrons()
--------------------------------------------------------------------------
Checks all squadrons for each coalition. If a squadron is below the resupply threshold and has no active cargo mission,
triggers a supply request and dispatches a cargo aircraft.
Skips squadrons that are captured or not operational.
]]
local function monitorSquadrons()
for _, coalitionKey in ipairs({"red", "blue"}) do
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
local squadrons = (coalitionKey == "red") and RED_SQUADRON_CONFIG or BLUE_SQUADRON_CONFIG
for _, squadron in ipairs(squadrons) do
-- Skip non-operational squadrons (captured, destroyed, etc.)
if squadron.state and squadron.state ~= "operational" then
log("Squadron " .. squadron.displayName .. " (" .. coalitionKey .. ") is " .. squadron.state .. " - skipping cargo dispatch", true)
else
local current, max, ratio = getSquadronStatus(squadron, coalitionKey)
log("Squadron status: " .. squadron.displayName .. " (" .. coalitionKey .. ") " .. current .. "/" .. max .. " ratio: " .. string.format("%.2f", ratio))
if ratio <= config.threshold and not hasActiveCargoMission(coalitionKey, squadron.airbaseName) then
log("Supply request triggered for " .. squadron.displayName .. " at " .. squadron.airbaseName)
announceToCoalition(coalitionKey, "Supply requested for " .. squadron.airbaseName .. "! Squadron: " .. squadron.displayName)
dispatchCargo(squadron, coalitionKey)
end
end
end
end
end
--[[
monitorCargoMissions()
--------------------------------------------------------------------------
Monitors all cargo missions, updates their status, and cleans up failed ones.
Handles mission failure after a grace period.
]]
local function monitorCargoMissions()
for _, coalitionKey in ipairs({"red", "blue"}) do
for _, mission in ipairs(cargoMissions[coalitionKey]) do
if mission.group == nil then
log("[DEBUG] Mission group object is nil for mission to " .. tostring(mission.destination), true)
else
log("[DEBUG] Mission group: " .. tostring(mission.group:GetName()) .. ", IsAlive(): " .. tostring(mission.group:IsAlive()), true)
local dcsGroup = mission.group:GetDCSObject()
if dcsGroup then
local units = dcsGroup:getUnits()
if units and #units > 0 then
local pos = units[1]:getPoint()
log(string.format("[DEBUG] Group position: x=%.1f y=%.1f z=%.1f", pos.x, pos.y, pos.z), true)
else
log("[DEBUG] No units found in DCS group for mission to " .. tostring(mission.destination), true)
end
else
log("[DEBUG] DCS group object is nil for mission to " .. tostring(mission.destination), true)
end
end
local graceElapsed = mission._pendingStartTime and (timer.getTime() - mission._pendingStartTime > (mission._gracePeriod or 8))
-- Only allow mission to be failed after grace period, and only if group is truly dead.
-- Some DCS/MOOSE group objects may momentarily report IsAlive() == false while units still exist, so
-- also check DCS object/unit presence before declaring failure.
if (mission.status == "pending" or mission.status == "enroute") and graceElapsed then
local isAlive = mission.group and mission.group:IsAlive()
local dcsGroup = mission.group and mission.group:GetDCSObject()
local unitsPresent = false
if dcsGroup then
local units = dcsGroup:getUnits()
unitsPresent = units and (#units > 0)
end
if not isAlive and not unitsPresent then
mission.status = "failed"
log("Cargo mission failed (after grace period): " .. (mission.group and mission.group:GetName() or "nil group") .. " to " .. mission.destination)
announceToCoalition(coalitionKey, "Resupply mission to " .. mission.destination .. " failed!")
else
log("DEBUG: Mission appears to still have DCS units despite IsAlive=false; skipping failure for " .. tostring(mission.destination), true)
end
end
end
end
cleanupCargoMissions()
end
--[[
MAIN DISPATCHER LOOP
--------------------------------------------------------------------------
Runs the main dispatcher logic on a timer interval.
]]
local function cargoDispatcherMain()
log("═══════════════════════════════════════════════════════════════════════════════", true)
log("Cargo Dispatcher main loop running.", true)
monitorSquadrons()
monitorCargoMissions()
-- Schedule the next run inside a protected call to avoid unhandled errors
timer.scheduleFunction(function()
local ok, err = pcall(cargoDispatcherMain)
if not ok then
log("FATAL: cargoDispatcherMain crashed on scheduled run: " .. tostring(err))
-- do not reschedule to avoid crash loops
end
end, {}, timer.getTime() + DISPATCHER_CONFIG.interval)
end
-- Start the dispatcher
local ok, err = pcall(cargoDispatcherMain)
if not ok then
log("FATAL: cargoDispatcherMain crashed on startup: " .. tostring(err))
end
log("═══════════════════════════════════════════════════════════════════════════════", true)
-- End Moose_TDAC_CargoDispatcher.lua
--[[
DIAGNOSTIC CONSOLE HELPERS
--------------------------------------------------------------------------
Functions you can call from the DCS Lua console (F12) to debug issues.
]]
-- Check airbase coalition ownership for all configured supply airbases
-- Usage: _G.TDAC_CheckAirbaseOwnership()
function _G.TDAC_CheckAirbaseOwnership()
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
env.info("[TDAC DIAGNOSTIC] Checking Coalition Ownership of All Supply Airbases")
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
for _, coalitionKey in ipairs({"red", "blue"}) do
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
local expectedCoalition = getCoalitionSide(coalitionKey)
env.info(string.format("[TDAC DIAGNOSTIC] %s COALITION (expected coalition ID: %s)", coalitionKey:upper(), tostring(expectedCoalition)))
if config and config.supplyAirfields then
for _, airbaseName in ipairs(config.supplyAirfields) do
local airbase = AIRBASE:FindByName(airbaseName)
if not airbase then
env.info(string.format("[TDAC DIAGNOSTIC] ✗ %-30s - NOT FOUND (invalid name or not on this map)", airbaseName))
else
local actualCoalition = airbase:GetCoalition()
local coalitionName = "UNKNOWN"
local status = ""
if actualCoalition == coalition.side.NEUTRAL then
coalitionName = "NEUTRAL"
elseif actualCoalition == coalition.side.RED then
coalitionName = "RED"
elseif actualCoalition == coalition.side.BLUE then
coalitionName = "BLUE"
end
if actualCoalition == expectedCoalition then
status = ""
end
env.info(string.format("[TDAC DIAGNOSTIC] %s %-30s - %s (coalition ID: %s)", status, airbaseName, coalitionName, tostring(actualCoalition)))
end
end
else
env.info("[TDAC DIAGNOSTIC] ERROR: No supply airfields configured!")
end
env.info("[TDAC DIAGNOSTIC] ───────────────────────────────────────")
end
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
env.info("[TDAC DIAGNOSTIC] Check complete. ✓ = Owned by correct coalition, ✗ = Wrong coalition or not found")
return true
end
-- Check specific airbase coalition ownership
-- Usage: _G.TDAC_CheckAirbase("Olenya")
function _G.TDAC_CheckAirbase(airbaseName)
if type(airbaseName) ~= 'string' then
env.info("[TDAC DIAGNOSTIC] ERROR: airbaseName must be a string")
return false
end
local airbase = AIRBASE:FindByName(airbaseName)
if not airbase then
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' NOT FOUND (invalid name or not on this map)", airbaseName))
return false, "not_found"
end
local actualCoalition = airbase:GetCoalition()
local coalitionName = "UNKNOWN"
if actualCoalition == coalition.side.NEUTRAL then
coalitionName = "NEUTRAL"
elseif actualCoalition == coalition.side.RED then
coalitionName = "RED"
elseif actualCoalition == coalition.side.BLUE then
coalitionName = "BLUE"
end
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' - Coalition: %s (ID: %s)", airbaseName, coalitionName, tostring(actualCoalition)))
env.info(string.format("[TDAC DIAGNOSTIC] IsAlive: %s", tostring(airbase:IsAlive())))
-- Check parking spots
local function spotsFor(term, termName)
local ok, n = pcall(function() return airbase:GetParkingSpotsNumber(term) end)
if ok and n then
env.info(string.format("[TDAC DIAGNOSTIC] Parking %-15s: %d spots", termName, n))
end
end
spotsFor(AIRBASE.TerminalType.OpenBig, "OpenBig")
spotsFor(AIRBASE.TerminalType.OpenMed, "OpenMed")
spotsFor(AIRBASE.TerminalType.OpenMedOrBig, "OpenMedOrBig")
spotsFor(AIRBASE.TerminalType.Runway, "Runway")
return true, coalitionName, actualCoalition
end
env.info("[TDAC DIAGNOSTIC] Console helpers loaded:")
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbaseOwnership() - Check all supply airbases")
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbase('Olenya') - Check specific airbase")
env.info("[TDAC DIAGNOSTIC] _G.TDAC_RunConfigCheck() - Validate dispatcher config")
env.info("[TDAC DIAGNOSTIC] _G.TDAC_LogAirbaseParking('Olenya') - Check parking availability")
-- Diagnostic helper: call from DCS console to test spawn-by-name and routing.
-- Example (paste into DCS Lua console):
-- _G.TDAC_CargoDispatcher_TestSpawn("CARGO_BLUE_C130_TEMPLATE", "Kittila", "Luostari Pechenga")
function _G.TDAC_CargoDispatcher_TestSpawn(templateName, originAirbase, destinationAirbase)
log("[TDAC TEST] Starting test spawn for template: " .. tostring(templateName), true)
local ok, err
if type(templateName) ~= 'string' then
env.info("[TDAC TEST] templateName must be a string")
return false, "invalid templateName"
end
local spawnByName = nil
ok, spawnByName = pcall(function() return SPAWN:New(templateName) end)
if not ok or not spawnByName then
log("[TDAC TEST] SPAWN:New failed for template " .. tostring(templateName) .. ". Error: " .. tostring(spawnByName), true)
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(spawnByName))), true) end
return false, "spawn_new_failed"
end
spawnByName:OnSpawnGroup(function(spawnedGroup)
log("[TDAC TEST] OnSpawnGroup called for: " .. tostring(spawnedGroup:GetName()), true)
local dcsGroup = spawnedGroup:GetDCSObject()
if dcsGroup then
local units = dcsGroup:getUnits()
if units and #units > 0 then
local pos = units[1]:getPoint()
log(string.format("[TDAC TEST] Spawned pos x=%.1f y=%.1f z=%.1f", pos.x, pos.y, pos.z), true)
end
end
if destinationAirbase then
local okAssign, errAssign = pcall(function()
local base = AIRBASE:FindByName(destinationAirbase)
if base and spawnedGroup and spawnedGroup.RouteToAirbase then
spawnedGroup:RouteToAirbase(base, AI_Task_Land.Runway)
log("[TDAC TEST] RouteToAirbase assigned to " .. tostring(destinationAirbase), true)
else
log("[TDAC TEST] RouteToAirbase not available or base not found", true)
end
end)
if not okAssign then
log("[TDAC TEST] RouteToAirbase pcall failed: " .. tostring(errAssign), true)
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(errAssign))), true) end
end
end
end)
ok, err = pcall(function() spawnByName:Spawn() end)
if not ok then
log("[TDAC TEST] spawnByName:Spawn() failed: " .. tostring(err), true)
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(err))), true) end
return false, "spawn_failed"
end
log("[TDAC TEST] spawnByName:Spawn() returned successfully", true)
return true
end
log("═══════════════════════════════════════════════════════════════════════════════", true)
-- End Moose_TDAC_CargoDispatcher.lua

2617
Moose_TADC_Load2nd.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,244 @@
--[[ THIS FILE MUST BE LOADED BEFORE THE MAIN Moose_TADC.lua SCRIPT
SQUADRON CONFIGURATION
INSTRUCTIONS:
1. Create fighter aircraft templates for BOTH coalitions in the mission editor
2. Place them at or near the airbases you want them to operate from
3. Configure RED squadrons in RED_SQUADRON_CONFIG
4. Configure BLUE squadrons in BLUE_SQUADRON_CONFIG
TEMPLATE NAMING SUGGESTIONS:
RED: "RED_CAP_Batumi_F15", "RED_INTERCEPT_Senaki_MiG29"
BLUE: "BLUE_CAP_Nellis_F16", "BLUE_INTERCEPT_Creech_F22"
Include coalition and airbase name for easy identification
AIRBASE NAMES:
Use exact names as they appear in DCS (case sensitive)
RED examples: "Batumi", "Senaki", "Gudauta"
BLUE examples: "Nellis AFB", "McCarran International", "Tonopah Test Range"
Find airbase names in the mission editor
AIRCRAFT NUMBERS:
Set realistic numbers based on mission requirements
Consider aircraft consumption and cargo replenishment
Balance between realism and gameplay performance
ZONE-BASED AREAS OF RESPONSIBILITY:
Create zones in mission editor (MOOSE polygons, circles, etc.)
primaryZone: Squadron's main area (full response)
secondaryZone: Backup/support area (reduced response)
tertiaryZone: Emergency fallback area (enhanced response)
Leave zones as nil for global threat response
Multiple squadrons can share overlapping zones
Use zone names exactly as they appear in mission editor
ZONE BEHAVIOR EXAMPLES:
Border Defense: primaryZone = "SECTOR_ALPHA", secondaryZone = "BUFFER_ZONE"
Base Defense: tertiaryZone = "BASE_PERIMETER", enableFallback = true
Layered Defense: Different zones per squadron with overlap
Emergency Response: High tertiaryResponse ratio for critical areas
]]
-- ═══════════════════════════════════════════════════════════════════════════
-- RED COALITION SQUADRONS
-- ═══════════════════════════════════════════════════════════════════════════
RED_SQUADRON_CONFIG = {
--[[ EXAMPLE RED SQUADRON - CUSTOMIZE FOR YOUR MISSION
{
templateName = "RED_CAP_Batumi_F15", -- Template name from mission editor
displayName = "Batumi F-15C CAP", -- Human-readable name for logs
airbaseName = "Batumi", -- Exact airbase name from DCS
aircraft = 12, -- Maximum aircraft in squadron
skill = AI.Skill.GOOD, -- AI skill level
altitude = 20000, -- Patrol altitude (feet)
speed = 350, -- Patrol speed (knots)
patrolTime = 25, -- Time on station (minutes)
type = "FIGHTER"
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = nil, -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = false, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
]]
-- ADD YOUR RED SQUADRONS HERE
{
templateName = "Sukhumi CAP", -- Change to your RED template name
displayName = "Sukhumi CAP", -- Change to your preferred name
airbaseName = "Sukhumi-Babushara", -- Change to your RED airbase
aircraft = 12, -- Adjust aircraft count
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT, ACE
altitude = 20000, -- Patrol altitude (feet)
speed = 350, -- Patrol speed (knots)
patrolTime = 25, -- Time on station (minutes)
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = nil, -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = false, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
{
templateName = "Gudauta CAP-MiG-21", -- Change to your RED template name
displayName = "Gudauta CAP-MiG-21", -- Change to your preferred name
airbaseName = "Gudauta", -- Change to your RED airbase
aircraft = 12, -- Adjust aircraft count
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
altitude = 20000, -- Patrol altitude (feet)
speed = 350, -- Patrol speed (knots)
patrolTime = 25, -- Time on station (minutes)
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "GUDAUTA_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = nil, -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = false, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
{
templateName = "Gudauta CAP-MiG-23", -- Change to your RED template name
displayName = "Gudauta CAP-MiG-23", -- Change to your preferred name
airbaseName = "Gudauta", -- Change to your RED airbase
aircraft = 14, -- Adjust aircraft count
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
altitude = 20000, -- Patrol altitude (feet)
speed = 350, -- Patrol speed (knots)
patrolTime = 25, -- Time on station (minutes)
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "GUDAUTA_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = "RED_BORDER", -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = false, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
}
-- ═══════════════════════════════════════════════════════════════════════════
-- BLUE COALITION SQUADRONS
-- ═══════════════════════════════════════════════════════════════════════════
BLUE_SQUADRON_CONFIG = {
--[[ EXAMPLE BLUE SQUADRON - CUSTOMIZE FOR YOUR MISSION
{
templateName = "BLUE_CAP_Nellis_F16", -- Template name from mission editor
displayName = "Nellis F-16C CAP", -- Human-readable name for logs
airbaseName = "Nellis AFB", -- Exact airbase name from DCS
aircraft = 14, -- Maximum aircraft in squadron
skill = AI.Skill.EXCELLENT, -- AI skill level
altitude = 22000, -- Patrol altitude (feet)
speed = 380, -- Patrol speed (knots)
patrolTime = 28, -- Time on station (minutes)
type = "FIGHTER" -- Aircraft type
},
]]
-- ADD YOUR BLUE SQUADRONS HERE
{
templateName = "Kutaisi CAP", -- Change to your BLUE template name
displayName = "Kutaisi CAP", -- Change to your preferred name
airbaseName = "Kutaisi", -- Change to your BLUE airbase
aircraft = 18, -- Adjust aircraft count
skill = AI.Skill.EXCELLENT, -- AVERAGE, GOOD, HIGH, EXCELLENT
altitude = 18000, -- Patrol altitude (feet)
speed = 320, -- Patrol speed (knots)
patrolTime = 22, -- Time on station (minutes)
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "BLUE_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = nil, -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = true, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
{
templateName = "Batumi CAP", -- Change to your BLUE template name
displayName = "Batumi CAP", -- Change to your preferred name
airbaseName = "Batumi", -- Change to your BLUE airbase
aircraft = 18, -- Adjust aircraft count
skill = AI.Skill.EXCELLENT, -- AVERAGE, GOOD, HIGH, EXCELLENT
altitude = 18000, -- Patrol altitude (feet)
speed = 320, -- Patrol speed (knots)
patrolTime = 22, -- Time on station (minutes)
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
primaryZone = "BATUMI_BORDER", -- Main responsibility area (zone name from mission editor)
secondaryZone = "BLUE_BORDER", -- Secondary coverage area (zone name)
tertiaryZone = "BATUMI_BORDER", -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
maxRange = 200, -- Maximum engagement range from airbase (nm)
enableFallback = true, -- Auto-switch to tertiary when base threatened
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
}

BIN
TADC_Example.miz Normal file

Binary file not shown.

194
TADC_Menu.lua Normal file
View File

@ -0,0 +1,194 @@
-- ================================================================
-- UNIVERSAL MOOSE SPAWNER UTILITY MENU
-- ================================================================
-- Allows spawning any group template (fighter, cargo, etc.) at any airbase
-- for either coalition, with options for cold/hot/runway start.
-- Includes cleanup and status commands.
-- ================================================================
-- List of available airbases (Caucasus map, add/remove as needed)
local AIRBASES = {
"Kutaisi", "Senaki-Kolkhi", "Sukhumi-Babushara", "Gudauta", "Sochi-Adler",
"Krymsk", "Anapa-Vityazevo", "Krasnodar-Pashkovsky", "Mineralnye Vody",
"Nalchik", "Mozdok", "Beslan"
}
-- List of example templates (add your own as needed)
local TEMPLATES = {
"CARGO", "CARGO_RU", "Kutaisi CAP", "Sukhumi CAP", "Batumi CAP", "Gudauta CAP"
-- Add more fighter/cargo templates here
}
-- Coalition options
local COALITIONS = {
{name = "Blue", side = coalition.side.BLUE},
{name = "Red", side = coalition.side.RED}
}
-- Start types
local START_TYPES = {
{name = "Cold Start", value = SPAWN.Takeoff.Cold},
{name = "Hot Start", value = SPAWN.Takeoff.Hot},
{name = "Runway", value = SPAWN.Takeoff.Runway}
}
-- Track spawned groups for cleanup
local spawnedGroups = {}
-- Utility: Add group to cleanup tracking
local function TrackGroup(group)
if group and group:IsAlive() then
table.insert(spawnedGroups, group)
end
end
-- Utility: Cleanup all spawned groups
local function CleanupAll()
local cleaned = 0
for _, group in ipairs(spawnedGroups) do
if group and group:IsAlive() then
group:Destroy()
cleaned = cleaned + 1
end
end
spawnedGroups = {}
MESSAGE:New("Cleaned up " .. cleaned .. " spawned groups", 10):ToAll()
end
-- Utility: Show status of spawned groups
local function ShowStatus()
local alive = 0
for _, group in ipairs(spawnedGroups) do
if group and group:IsAlive() then alive = alive + 1 end
end
MESSAGE:New("Spawner Status:\nAlive groups: " .. alive .. "\nTotal spawned: " .. #spawnedGroups, 15):ToAll()
end
-- Main menu
local MenuRoot = MENU_MISSION:New("Universal Spawner")
-- Submenus for coalition
local MenuBlue = MENU_MISSION:New("Spawn for BLUE", MenuRoot)
local MenuRed = MENU_MISSION:New("Spawn for RED", MenuRoot)
-- For each coalition, create template/airbase/start type menus
for _, coalitionData in ipairs(COALITIONS) do
local menuCoalition = (coalitionData.side == coalition.side.BLUE) and MenuBlue or MenuRed
for _, templateName in ipairs(TEMPLATES) do
local menuTemplate = MENU_MISSION:New("Template: " .. templateName, menuCoalition)
for _, airbaseName in ipairs(AIRBASES) do
local menuAirbase = MENU_MISSION:New("Airbase: " .. airbaseName, menuTemplate)
for _, startType in ipairs(START_TYPES) do
local menuStartType = MENU_MISSION:New(startType.name, menuAirbase)
for numToSpawn = 1, 5 do
MENU_MISSION_COMMAND:New(
"Spawn " .. numToSpawn,
menuStartType,
function()
local airbase = AIRBASE:FindByName(airbaseName)
if not airbase then
MESSAGE:New("Airbase not found: " .. airbaseName, 10):ToAll()
return
end
local spawnObj = SPAWN:New(templateName)
spawnObj:InitLimit(10, 20)
local spawned = 0
for i = 1, numToSpawn do
local group = spawnObj:SpawnAtAirbase(airbase, startType.value)
if group then
TrackGroup(group)
spawned = spawned + 1
end
end
if spawned > 0 then
MESSAGE:New("Spawned " .. spawned .. " '" .. templateName .. "' at " .. airbaseName .. " (" .. startType.name .. ")", 10):ToAll()
else
MESSAGE:New("Failed to spawn '" .. templateName .. "' at " .. airbaseName, 10):ToAll()
end
end
)
end
end
end
end
end
-- Quick spawn (first template, first airbase, cold start)
MENU_MISSION_COMMAND:New(
"Quick Spawn (" .. TEMPLATES[1] .. ")",
MenuRoot,
function()
local airbase = AIRBASE:FindByName(AIRBASES[1])
local spawnObj = SPAWN:New(TEMPLATES[1])
spawnObj:InitLimit(10, 20)
local spawned = 0
for i = 1, 1 do
local group = spawnObj:SpawnAtAirbase(airbase, SPAWN.Takeoff.Cold)
if group then
TrackGroup(group)
spawned = spawned + 1
end
end
if spawned > 0 then
MESSAGE:New("Quick spawned '" .. TEMPLATES[1] .. "' at " .. AIRBASES[1], 10):ToAll()
else
MESSAGE:New("Failed to quick spawn '" .. TEMPLATES[1] .. "' at " .. AIRBASES[1], 10):ToAll()
end
end
)
-- Status and cleanup commands
MENU_MISSION_COMMAND:New("Show Spawner Status", MenuRoot, ShowStatus)
MENU_MISSION_COMMAND:New("Cleanup All Spawned Groups", MenuRoot, CleanupAll)
-- ================================================================
-- CONFIGURATION
-- ================================================================
-- Menu configuration
local MENU_CONFIG = {
rootMenuText = "CARGO OPERATIONS",
coalitionSide = coalition.side.BLUE, -- Change to RED if needed
debugMode = true
}
-- Spawn configuration
local SPAWN_CONFIG = {
templateName = "CARGO", -- Template name in mission editor
maxActive = 3, -- Maximum active aircraft
maxSpawns = 10, -- Maximum total spawns
cleanupTime = 300, -- Cleanup time in seconds (5 minutes)
spawnAirbase = "Kutaisi", -- Default spawn airbase
takeoffType = SPAWN.Takeoff.Cold -- Cold start by default
}
-- Available airbases for spawning (Caucasus map)
local AVAILABLE_AIRBASES = {
"Kutaisi",
"Senaki-Kolkhi",
"Sukhumi-Babushara",
"Gudauta",
"Sochi-Adler",
"Krymsk",
"Anapa-Vityazevo",
"Krasnodar-Pashkovsky",
"Mineralnye Vody",
"Nalchik",
"Mozdok",
"Beslan"
}
-- ================================================================
-- GLOBAL VARIABLES
-- ================================================================
-- Spawn object
local CargoSpawn = nil
-- Menu objects
local MenuRoot = nil
local MenuSpawn = nil

1353
TADC_SYSTEM_GUIDE.html Normal file

File diff suppressed because it is too large Load Diff

965
TADC_SYSTEM_GUIDE.md Normal file
View File

@ -0,0 +1,965 @@
# Universal TADC System - Mission Maker's Guide
## Tactical Air Defense Controller with Automated Logistics
---
## 📋 Table of Contents
1. [What is TADC?](#what-is-tadc)
2. [System Overview](#system-overview)
3. [Quick Start Guide](#quick-start-guide)
4. [Detailed Configuration](#detailed-configuration)
5. [Zone-Based Defense Setup](#zone-based-defense-setup)
6. [Cargo Replenishment System](#cargo-replenishment-system)
7. [Testing & Troubleshooting](#testing--troubleshooting)
8. [Advanced Features](#advanced-features)
9. [Common Scenarios](#common-scenarios)
---
## What is TADC?
**TADC (Tactical Air Defense Controller)** is an automated air defense system for DCS missions that creates realistic, dynamic fighter aircraft responses to airborne threats. Think of it as an AI commander that:
- **Detects enemy aircraft** automatically
- **Launches fighters** to intercept threats
- **Manages squadron resources** (aircraft availability, cooldowns)
- **Replenishes squadrons** through cargo aircraft deliveries
- **Operates independently** for both RED and BLUE coalitions
### Why Use TADC?
**Realistic Air Defense** - Squadrons respond intelligently to threats
**Dynamic Gameplay** - Air battles happen organically without manual triggers
**Balanced Competition** - Both sides operate with equal capabilities
**Sustainable Operations** - Cargo system allows long missions with resupply
**Easy Configuration** - Simple tables instead of complex scripting
---
## System Overview
The TADC system consists of **three main scripts** that work together:
### 1. Squadron Configuration (`Moose_TADC_SquadronConfigs_Load1st.lua`)
**Purpose:** Define all fighter squadrons for RED and BLUE coalitions
**Contains:** Aircraft templates, airbases, patrol parameters, zone assignments
**Load Order:** **FIRST** (must load before main TADC script)
### 2. Main TADC System (`Moose_TADC_Load2nd.lua`)
**Purpose:** Core threat detection and interceptor management
**Contains:** Threat scanning, squadron selection, intercept logic, F10 menus
**Load Order:** **SECOND** (after squadron config)
### 3. Cargo Dispatcher (`Moose_TADC_CargoDispatcher.lua`)
**Purpose:** Automated squadron resupply through cargo aircraft
**Contains:** Squadron monitoring, cargo spawning, delivery tracking
**Load Order:** **THIRD** (optional, only if using resupply system)
---
## Quick Start Guide
### Prerequisites
Before setting up TADC, you need:
- ✅ **MOOSE Framework** loaded in your mission (download from [MOOSE GitHub](https://github.com/FlightControl-Master/MOOSE))
- ✅ **Fighter aircraft templates** created in DCS mission editor (as GROUPS, not units)
- ✅ **Airbases** under correct coalition control
- ✅ (Optional) **Cargo aircraft templates** for resupply missions
### 5-Minute Setup
#### Step 1: Create Fighter Templates
1. Open your mission in DCS Mission Editor
2. Place fighter aircraft as **LATE ACTIVATION GROUPS** (not individual units)
3. Name them clearly (example: `RED_CAP_Kilpyavr_MiG29`)
4. Position them at or near the airbases they'll operate from
5. Set them to the correct coalition (RED or BLUE)
**Important:** Use GROUP templates, not UNIT templates!
#### Step 2: Load MOOSE Framework
1. In mission editor, go to **Triggers**
2. Create a new trigger: **MISSION START**
3. Add action: **DO SCRIPT FILE**
4. Select your MOOSE.lua file
5. This must be the FIRST script loaded
#### Step 3: Load Squadron Configuration
1. Create another **DO SCRIPT FILE** action (after MOOSE)
2. Select `Moose_TADC_SquadronConfigs_Load1st.lua`
3. Edit the file to configure your squadrons (see below)
#### Step 4: Load Main TADC System
1. Create another **DO SCRIPT FILE** action
2. Select `Moose_TADC_Load2nd.lua`
3. (Optional) Adjust settings in the file if needed
#### Step 5: (Optional) Load Cargo Dispatcher
1. If using resupply system, create another **DO SCRIPT FILE** action
2. Select `Moose_TADC_CargoDispatcher.lua`
**Load Order in Mission Editor:**
```
1. MOOSE.lua
2. Moose_TADC_SquadronConfigs_Load1st.lua
3. Moose_TADC_Load2nd.lua
4. Moose_TADC_CargoDispatcher.lua (optional)
```
---
## Detailed Configuration
### Squadron Configuration Explained
Open `Moose_TADC_SquadronConfigs_Load1st.lua` and find the squadron configuration sections.
#### Basic Squadron Example
```lua
{
templateName = "RED_CAP_Kilpyavr_MiG29", -- Must match mission editor template name
displayName = "Kilpyavr CAP MiG-29A", -- Human-readable name for logs/messages
airbaseName = "Kilpyavr", -- Exact airbase name from DCS
aircraft = 12, -- Maximum aircraft in squadron
skill = AI.Skill.EXCELLENT, -- AI pilot skill level
altitude = 20000, -- Patrol altitude (feet)
speed = 350, -- Patrol speed (knots)
patrolTime = 30, -- Time on station (minutes)
type = "FIGHTER" -- Aircraft role
}
```
#### Parameter Guide
| Parameter | Description | Example Values |
|-----------|-------------|----------------|
| **templateName** | Group name from mission editor (EXACT match) | `"RED_CAP_Base_F15"` |
| **displayName** | Friendly name shown in messages | `"Kilpyavr CAP Squadron"` |
| **airbaseName** | DCS airbase name (case sensitive) | `"Kilpyavr"`, `"Nellis AFB"` |
| **aircraft** | Max squadron size | `8`, `12`, `16` |
| **skill** | AI difficulty | `AI.Skill.AVERAGE`, `GOOD`, `HIGH`, `EXCELLENT`, `ACE` |
| **altitude** | CAP patrol altitude | `15000` (feet) |
| **speed** | CAP patrol speed | `300` (knots) |
| **patrolTime** | Minutes on station before RTB | `20`, `30`, `40` |
| **type** | Aircraft role | `"FIGHTER"` |
### Finding Airbase Names
**Method 1: Mission Editor**
1. Open mission editor
2. Click on any airbase
3. The exact name appears in the properties panel
4. Copy this name EXACTLY (case sensitive!)
**Method 2: Common Airbases**
**Kola Peninsula (Example Map):**
- RED: `"Kilpyavr"`, `"Severomorsk-1"`, `"Severomorsk-3"`, `"Murmansk International"`
- BLUE: `"Luostari Pechenga"`, `"Ivalo"`, `"Alakurtti"`
**Nevada:**
- `"Nellis AFB"`, `"McCarran International"`, `"Creech AFB"`, `"Tonopah Test Range"`
**Caucasus:**
- `"Batumi"`, `"Gudauta"`, `"Senaki-Kolkhi"`, `"Kobuleti"`, `"Kutaisi"`
### Adding Multiple Squadrons
You can add as many squadrons as you want. Just copy the squadron block and modify the values:
```lua
RED_SQUADRON_CONFIG = {
-- First Squadron
{
templateName = "RED_CAP_Base1_MiG29",
displayName = "Base 1 CAP",
airbaseName = "Kilpyavr",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER"
},
-- Second Squadron (different base)
{
templateName = "RED_CAP_Base2_SU27",
displayName = "Base 2 CAP",
airbaseName = "Severomorsk-1",
aircraft = 16,
skill = AI.Skill.ACE,
altitude = 25000,
speed = 380,
patrolTime = 25,
type = "FIGHTER"
},
-- Add more squadrons here...
}
```
**Repeat the same process for BLUE squadrons** in the `BLUE_SQUADRON_CONFIG` section.
---
## Zone-Based Defense Setup
Zones allow squadrons to have specific areas of responsibility, creating realistic layered defense.
### Why Use Zones?
- **Border Defense:** Squadrons patrol specific sectors
- **Layered Defense:** Multiple squadrons cover overlapping areas
- **Priority Response:** Squadrons respond differently based on threat location
- **Realistic Behavior:** Fighters don't fly across the entire map for minor threats
### Zone Types
Each squadron can have up to 3 zone types:
1. **Primary Zone** - Main area of responsibility (full response)
2. **Secondary Zone** - Support area (reduced response, 60% by default)
3. **Tertiary Zone** - Emergency fallback (enhanced response when squadron weakened)
### Creating Zones in Mission Editor
**Method: Helicopter Waypoint Method**
1. Place a **helicopter group** (late activation, any type)
2. Name it clearly (example: `"RED BORDER"`)
3. Add waypoints that outline your zone boundary
4. The script will automatically create a polygon zone from these waypoints
5. Repeat for each zone you want to create
**Example Zone Setup:**
```
Mission Editor:
- Helicopter Group: "RED BORDER" with waypoints forming a polygon
- Helicopter Group: "BLUE BORDER" with waypoints forming a polygon
- Helicopter Group: "CONTESTED ZONE" with waypoints forming a polygon
```
### Configuring Zone Response
Add zone configuration to your squadron:
```lua
{
templateName = "RED_CAP_Kilpyavr_MiG29",
displayName = "Kilpyavr CAP",
airbaseName = "Kilpyavr",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
-- Zone Configuration
primaryZone = "RED BORDER", -- Main responsibility area
secondaryZone = "CONTESTED ZONE", -- Backup coverage
tertiaryZone = nil, -- No tertiary zone
-- Optional: Customize zone behavior
zoneConfig = {
primaryResponse = 1.0, -- Full response in primary zone
secondaryResponse = 0.6, -- 60% response in secondary
tertiaryResponse = 1.4, -- 140% response in tertiary
enableFallback = false, -- Don't auto-switch to tertiary
fallbackThreshold = 0.3, -- Switch when <30% aircraft remain
secondaryLowPriorityFilter = true, -- Ignore small threats in secondary
secondaryLowPriorityThreshold = 2 -- "Small threat" = 2 or fewer aircraft
}
}
```
### Zone Behavior Examples
**Example 1: Border Defense Squadron**
```lua
primaryZone = "RED BORDER", -- Patrols the border
secondaryZone = "INTERIOR", -- Helps with interior threats if needed
tertiaryZone = nil -- No fallback
```
**Example 2: Base Defense with Fallback**
```lua
primaryZone = "NORTHERN SECTOR", -- Main patrol area
secondaryZone = nil, -- No secondary
tertiaryZone = "BASE PERIMETER", -- Falls back to defend base when weakened
enableFallback = true, -- Auto-switch to tertiary when low
fallbackThreshold = 0.4 -- Switch at 40% strength
```
**Example 3: Layered Defense**
```lua
-- Squadron A: Outer layer
primaryZone = "OUTER PERIMETER"
-- Squadron B: Middle layer
primaryZone = "MIDDLE PERIMETER"
-- Squadron C: Inner/base defense
primaryZone = "BASE DEFENSE"
```
### Global Response (No Zones)
If you **DON'T** want zone restrictions, simply leave all zones as `nil`:
```lua
{
templateName = "RED_CAP_Base_MiG29",
displayName = "Global Response CAP",
airbaseName = "Kilpyavr",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
-- No zones = responds to threats anywhere on the map
primaryZone = nil,
secondaryZone = nil,
tertiaryZone = nil
}
```
---
## Cargo Replenishment System
The cargo system automatically replenishes squadrons by spawning transport aircraft that fly supplies to airbases.
### How Cargo Works
1. **Monitoring:** Script checks squadron aircraft counts every minute
2. **Detection:** When squadron drops below threshold (90% by default), cargo is dispatched
3. **Spawning:** Transport aircraft spawns at a supply airfield
4. **Delivery:** Flies to destination airbase and lands
5. **Replenishment:** Squadron aircraft count increases upon delivery
6. **Cooldown:** 5-minute cooldown before next delivery to same base
### Cargo Aircraft Detection
The system detects cargo by aircraft name patterns:
- `CARGO`
- `TRANSPORT`
- `C130` or `C-130`
- `AN26` or `AN-26`
**Delivery Methods:**
- **Landing:** Aircraft lands at destination airbase
### Configuring Cargo Templates
Edit `Moose_TADC_CargoDispatcher.lua` and find `CARGO_SUPPLY_CONFIG`:
```lua
local CARGO_SUPPLY_CONFIG = {
red = {
cargoTemplate = "CARGO_RED_AN26_TEMPLATE", -- Template name from mission editor
supplyAirfields = {"Airbase1", "Airbase2"}, -- List of supply bases
replenishAmount = 4, -- Aircraft added per delivery
threshold = 0.90 -- Trigger at 90% capacity
},
blue = {
cargoTemplate = "CARGO_BLUE_C130_TEMPLATE",
supplyAirfields = {"Airbase3", "Airbase4"},
replenishAmount = 4,
threshold = 0.90
}
}
```
### Creating Cargo Templates
1. **In Mission Editor:**
- Place transport aircraft group (C-130, An-26, etc.)
- Name it: `CARGO_RED_AN26_TEMPLATE` or `CARGO_BLUE_C130_TEMPLATE`
- Set **LATE ACTIVATION**
- Position at any friendly airbase (starting position doesn't matter)
2. **In Configuration:**
- Use the EXACT template name in `cargoTemplate` field
- List supply airbases in `supplyAirfields` array
- Set how many aircraft each delivery adds (`replenishAmount`)
### Supply Airfield Strategy
**Choose rear/safe airbases for supplies:**
```lua
red = {
cargoTemplate = "CARGO_RED_AN26_TEMPLATE",
supplyAirfields = {
"Rear_Base_1", -- Far from frontline, safe
"Rear_Base_2", -- Alternate supply source
"Central_Logistics_Hub" -- Main supply depot
},
replenishAmount = 4,
threshold = 0.90
}
```
**Tips:**
- Use 3-5 supply airbases for redundancy
- Choose bases far from combat zones
- Ensure supply bases are well-defended
- Balance geographic coverage
### Disabling Cargo System
If you don't want automated resupply:
1. **Don't load** `Moose_TADC_CargoDispatcher.lua`
2. Squadrons will operate with their initial aircraft count only
3. System still works perfectly for shorter missions
---
## Testing & Troubleshooting
### Validation Tools
The system includes built-in validation. Check the DCS log file after mission start for:
```
[Universal TADC] ═══════════════════════════════════════
[Universal TADC] Configuration Validation Results:
[Universal TADC] ✓ All templates exist
[Universal TADC] ✓ All airbases valid
[Universal TADC] ✓ All zones found
[Universal TADC] Configuration is VALID
[Universal TADC] ═══════════════════════════════════════
```
### In-Game F10 Menu Commands
Press **F10** in-game to access TADC utilities:
**Available to Each Coalition:**
- **Show Squadron Resource Summary** - Current aircraft counts
- **Show Airbase Status Report** - Operational status of all bases
- **Show Active Interceptors** - Currently airborne fighters
- **Show Threat Summary** - Detected enemy aircraft
- **Broadcast Squadron Summary Now** - Force immediate status update
- **Show Cargo Delivery Log** - Recent supply missions
- **Show Zone Coverage Map** - Squadron zone assignments
**Available to All (Mission Commands):**
- **Emergency Cleanup Interceptors** - Remove stuck/dead groups
- **Show TADC System Status** - Uptime and system health
- **Check for Stuck Aircraft** - Manual stuck aircraft check
- **Show Airbase Health Status** - Parking/spawn issues
### Common Issues & Solutions
#### Issue: "Template not found in mission"
**Cause:** Template name in config doesn't match mission editor
**Solution:**
1. Check exact spelling and case
2. Ensure template is in mission editor
3. Verify template is a GROUP (not a unit)
4. Check template name in mission editor properties
#### Issue: "Airbase not found or wrong coalition"
**Cause:** Airbase name wrong or captured by enemy
**Solution:**
1. Check exact airbase spelling (case sensitive)
2. Verify airbase is owned by correct coalition in mission editor
3. Use `_G.TDAC_CheckAirbase("AirbaseName")` in DCS console
#### Issue: "No interceptors launching"
**Check:**
1. Are enemy aircraft detected? (F10 → Show Threat Summary)
2. Are squadrons operational? (F10 → Show Squadron Resource Summary)
3. Is airbase captured/destroyed? (F10 → Show Airbase Status Report)
4. Are squadrons on cooldown? (F10 → Show Squadron Resource Summary)
5. Check intercept ratio settings (might be too low)
#### Issue: "Cargo not delivering"
**Check:**
1. Is cargo template name correct?
2. Are supply airbases valid and friendly?
3. Is destination airbase captured/operational?
4. Check parking availability (F10 → Show Airbase Health Status)
5. Look for "Cargo delivery detected" messages in log
#### Issue: "Aircraft spawning stuck at parking"
**Cause:** Parking spots occupied or insufficient space
**Solution:**
1. Use F10 → Check for Stuck Aircraft
2. Use F10 → Emergency Cleanup Interceptors
3. Check airbase parking capacity (larger aircraft need more space)
4. Reduce squadron sizes if parking is limited
### DCS Console Diagnostics
Open DCS Lua console (**F12** or scripting console) and run:
```lua
-- Check all supply airbase ownership
_G.TDAC_CheckAirbaseOwnership()
-- Check specific airbase
_G.TDAC_CheckAirbase("Kilpyavr")
-- Validate dispatcher configuration
_G.TDAC_RunConfigCheck()
-- Check airbase parking availability
_G.TDAC_LogAirbaseParking("Kilpyavr")
-- Test cargo spawn (debugging)
_G.TDAC_CargoDispatcher_TestSpawn("CARGO_RED_AN26_TEMPLATE", "SupplyBase", "DestinationBase")
```
---
## Advanced Features
### Intercept Ratio System
The `interceptRatio` setting controls how many fighters launch per enemy aircraft.
**In `Moose_TADC_Load2nd.lua`:**
```lua
local TADC_SETTINGS = {
red = {
interceptRatio = 0.8, -- RED launches 0.8 fighters per threat
maxActiveCAP = 8, -- Max 8 groups in air simultaneously
defaultCooldown = 300, -- 5-minute cooldown after engagement
},
blue = {
interceptRatio = 1.2, -- BLUE launches 1.2 fighters per threat
maxActiveCAP = 10, -- Max 10 groups in air simultaneously
defaultCooldown = 300,
}
}
```
**Intercept Ratio Chart:**
| Ratio | 1 Enemy | 4 Enemies | 8 Enemies | Effect |
|-------|---------|-----------|-----------|--------|
| 0.5 | 1 fighter | 2 fighters | 4 fighters | Conservative response |
| 0.8 | 1 fighter | 4 fighters | 7 fighters | **Balanced (default)** |
| 1.0 | 1 fighter | 4 fighters | 8 fighters | 1:1 parity |
| 1.4 | 2 fighters | 6 fighters | 12 fighters | Strong response |
| 2.0 | 2 fighters | 8 fighters | 16 fighters | Overwhelming force |
**Tactical Effects:**
- **Low (0.5-0.8):** Sustainable defense, squadrons last longer
- **Medium (0.8-1.2):** Balanced dogfights, realistic attrition
- **High (1.4-2.0):** Strong defense, rapid squadron depletion
**Asymmetric Scenarios:**
```lua
-- RED advantage
red = { interceptRatio = 1.4 },
blue = { interceptRatio = 0.8 }
-- BLUE advantage
red = { interceptRatio = 0.8 },
blue = { interceptRatio = 1.4 }
```
### Distance-Based Engagement
Control how far squadrons will chase threats:
```lua
{
templateName = "RED_CAP_Base_MiG29",
displayName = "Base Defense",
airbaseName = "Kilpyavr",
aircraft = 12,
-- ... other settings ...
zoneConfig = {
maxEngagementRange = 50000, -- Won't engage threats >50km from base
primaryResponse = 1.0,
secondaryResponse = 0.6
}
}
```
### Cooldown System
After launching interceptors, squadrons go on cooldown to prevent spam:
```lua
local TADC_SETTINGS = {
red = {
defaultCooldown = 300, -- 5 minutes between launches
-- ... other settings ...
}
}
```
**Per-Squadron Cooldown (optional):**
```lua
{
templateName = "RED_CAP_Base_MiG29",
cooldownOverride = 600, -- This squadron: 10-minute cooldown
-- ... other settings ...
}
```
### Aircraft Skill Levels
Adjust AI difficulty per squadron:
```lua
skill = AI.Skill.AVERAGE -- Easiest, good for training
skill = AI.Skill.GOOD -- Below average
skill = AI.Skill.HIGH -- Average pilots
skill = AI.Skill.EXCELLENT -- Above average (recommended)
skill = AI.Skill.ACE -- Hardest, expert pilots
```
**Mixed Difficulty Example:**
```lua
RED_SQUADRON_CONFIG = {
{
displayName = "Elite Squadron",
skill = AI.Skill.ACE, -- Best pilots
aircraft = 8,
-- ...
},
{
displayName = "Regular Squadron",
skill = AI.Skill.GOOD, -- Average pilots
aircraft = 12,
-- ...
}
}
```
---
## Common Scenarios
### Scenario 1: Simple Border Defense
**Goal:** RED defends northern border, BLUE defends southern border
```lua
-- RED Configuration
RED_SQUADRON_CONFIG = {
{
templateName = "RED_CAP_North_MiG29",
displayName = "Northern Border CAP",
airbaseName = "Northern_Base",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
primaryZone = "RED BORDER"
}
}
-- BLUE Configuration
BLUE_SQUADRON_CONFIG = {
{
templateName = "BLUE_CAP_South_F16",
displayName = "Southern Border CAP",
airbaseName = "Southern_Base",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
primaryZone = "BLUE BORDER"
}
}
```
**In Mission Editor:**
- Create zone "RED BORDER" (helicopter waypoints on northern border)
- Create zone "BLUE BORDER" (helicopter waypoints on southern border)
---
### Scenario 2: Layered Defense Network
**Goal:** Multiple squadrons covering overlapping zones with different priorities
```lua
RED_SQUADRON_CONFIG = {
-- Outer Layer: Long-range interceptors
{
templateName = "RED_LONG_RANGE_MiG31",
displayName = "Long Range Interceptors",
airbaseName = "Forward_Base",
aircraft = 8,
skill = AI.Skill.EXCELLENT,
altitude = 35000,
speed = 450,
patrolTime = 20,
type = "FIGHTER",
primaryZone = "OUTER PERIMETER"
},
-- Middle Layer: General defense
{
templateName = "RED_CAP_MiG29",
displayName = "Middle Defense CAP",
airbaseName = "Central_Base",
aircraft = 12,
skill = AI.Skill.EXCELLENT,
altitude = 25000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
primaryZone = "MIDDLE PERIMETER",
secondaryZone = "OUTER PERIMETER"
},
-- Inner Layer: Point defense
{
templateName = "RED_BASE_DEFENSE_SU27",
displayName = "Base Defense",
airbaseName = "Main_Base",
aircraft = 16,
skill = AI.Skill.ACE,
altitude = 20000,
speed = 320,
patrolTime = 40,
type = "FIGHTER",
primaryZone = "BASE PERIMETER",
tertiaryZone = "BASE PERIMETER",
zoneConfig = {
enableFallback = true,
fallbackThreshold = 0.3
}
}
}
```
---
### Scenario 3: Sustained Operations with Resupply
**Goal:** Long mission with automated squadron replenishment
**Squadron Config:**
```lua
RED_SQUADRON_CONFIG = {
{
templateName = "RED_CAP_Frontline_MiG29",
displayName = "Frontline CAP",
airbaseName = "Frontline_Base",
aircraft = 12, -- Will be resupplied
skill = AI.Skill.EXCELLENT,
altitude = 20000,
speed = 350,
patrolTime = 30,
type = "FIGHTER",
primaryZone = "COMBAT ZONE"
}
}
```
**Cargo Config** (in `Moose_TADC_CargoDispatcher.lua`):
```lua
local CARGO_SUPPLY_CONFIG = {
red = {
cargoTemplate = "CARGO_RED_AN26",
supplyAirfields = {
"Rear_Base_1", -- Safe logistics hub
"Rear_Base_2", -- Backup supply source
"Central_Depot" -- Main supply depot
},
replenishAmount = 4, -- +4 aircraft per delivery
threshold = 0.75 -- Trigger at 75% (9/12 aircraft)
}
}
```
**Mission Flow:**
1. Frontline squadron intercepts threats
2. Squadron drops to 9 aircraft (75%)
3. Cargo automatically dispatched from rear base
4. Transport flies to frontline base
5. Cargo delivers, squadron back to 12 aircraft
6. Cycle repeats throughout mission
---
### Scenario 4: Asymmetric Warfare
**Goal:** RED has numerical superiority, BLUE has quality advantage
```lua
-- RED: More squadrons, lower skill
local TADC_SETTINGS = {
red = {
interceptRatio = 0.8, -- Conservative response
maxActiveCAP = 12, -- More groups allowed
}
}
RED_SQUADRON_CONFIG = {
{
templateName = "RED_CAP_1",
airbaseName = "Base_1",
aircraft = 16, -- Large squadron
skill = AI.Skill.GOOD, -- Average skill
-- ...
},
{
templateName = "RED_CAP_2",
airbaseName = "Base_2",
aircraft = 16,
skill = AI.Skill.GOOD,
-- ...
},
{
templateName = "RED_CAP_3",
airbaseName = "Base_3",
aircraft = 16,
skill = AI.Skill.GOOD,
-- ...
}
}
-- BLUE: Fewer squadrons, higher skill
local TADC_SETTINGS = {
blue = {
interceptRatio = 1.2, -- Aggressive response
maxActiveCAP = 8, -- Fewer groups
}
}
BLUE_SQUADRON_CONFIG = {
{
templateName = "BLUE_CAP_1",
airbaseName = "Base_1",
aircraft = 10, -- Smaller squadron
skill = AI.Skill.ACE, -- Elite pilots
-- ...
},
{
templateName = "BLUE_CAP_2",
airbaseName = "Base_2",
aircraft = 10,
skill = AI.Skill.ACE,
-- ...
}
}
```
---
## Tips for New Mission Makers
### Start Simple
1. **First Mission:** Use 1-2 squadrons per side with no zones
2. **Second Mission:** Add zone-based defense
3. **Third Mission:** Add cargo resupply system
4. **Advanced:** Multi-squadron layered defense with fallback
### Realistic Aircraft Numbers
**Small Airbase:** 6-8 aircraft per squadron
**Medium Airbase:** 10-12 aircraft per squadron
**Large Airbase:** 14-18 aircraft per squadron
**Balance across map:** If RED has 40 total aircraft, BLUE should have similar unless asymmetric
### Performance Considerations
- **Limit active groups:** Use `maxActiveCAP` to prevent FPS drops
- **Zone sizes matter:** Smaller zones = less scanning overhead
- **Cargo cooldowns:** Prevent cargo spam with reasonable cooldowns
- **Squadron counts:** 3-5 squadrons per side is a good starting point
### Testing Workflow
1. **Create minimal setup** (1 squadron each side)
2. **Test in mission editor** using "Fly Now"
3. **Check F10 menus** for squadron status
4. **Spawn enemy aircraft** to test intercepts
5. **Review DCS.log** for validation messages
6. **Expand gradually** once basic system works
### Common Mistakes to Avoid
**Using UNIT templates instead of GROUP templates**
✅ Use GROUP templates (late activation groups)
**Misspelling airbase names**
✅ Copy exact names from mission editor
**Loading scripts in wrong order**
✅ Squadron Config → Main TADC → Cargo Dispatcher
**Setting intercept ratio too high**
✅ Start with 0.8-1.0, adjust after testing
**Forgetting to load MOOSE first**
✅ MOOSE must be first script loaded
---
## Conclusion
The Universal TADC system provides mission makers with powerful, automated air defense capabilities that create dynamic, realistic air combat scenarios. By following this guide, even new mission makers can create sophisticated missions with minimal scripting knowledge.
### Key Takeaways
**Three scripts work together:** Squadron Config → Main TADC → Cargo Dispatcher
**Configuration is simple:** Edit tables, not complex code
**Both coalitions operate independently:** Balanced or asymmetric scenarios
**Zones enable tactical behavior:** Realistic area-of-responsibility system
**Cargo enables sustained operations:** Long missions with automatic resupply
**Built-in validation:** Checks configuration before mission starts
**F10 menus provide visibility:** Monitor system status in real-time
### Getting Help
If you encounter issues:
1. Check DCS.log for validation errors
2. Use F10 menu diagnostics
3. Run console commands for detailed info
4. Review this guide's troubleshooting section
5. Start simple and expand gradually
### Next Steps
1. Set up your first squadron (1 RED, 1 BLUE)
2. Test basic intercept behavior
3. Add zones for tactical depth
4. Implement cargo resupply for long missions
5. Experiment with advanced features
Happy mission making! 🚁✈️
---
*Author: F99th-TracerFacer
*Document Version: 1.0*
*Last Updated: October 2025*
*Compatible with: MOOSE Framework & DCS World*