mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Updated Simple TADC system and created entire new one for general applicaiton.
This commit is contained in:
parent
850061cc87
commit
b267cb1f70
Binary file not shown.
Binary file not shown.
@ -5,8 +5,8 @@
|
|||||||
local TADC_CONFIG = {
|
local TADC_CONFIG = {
|
||||||
checkInterval = 30, -- Check for threats every 30 seconds
|
checkInterval = 30, -- Check for threats every 30 seconds
|
||||||
maxActiveCAP = 24, -- Max fighters airborne at once
|
maxActiveCAP = 24, -- Max fighters airborne at once
|
||||||
squadronCooldown = 1800, -- Squadron cooldown after launch (30 minutes)
|
squadronCooldown = 900, -- Squadron cooldown after launch (15 minutes)
|
||||||
interceptRatio = 0.5, -- Launch interceptors per threat (see chart below)
|
interceptRatio = 0.8, -- Launch interceptors per threat (see chart below)
|
||||||
}
|
}
|
||||||
--[[
|
--[[
|
||||||
INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft:
|
INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft:
|
||||||
@ -43,7 +43,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Kilpyavr",
|
templateName = "FIGHTER_SWEEP_RED_Kilpyavr",
|
||||||
displayName = "Kilpyavr CAP",
|
displayName = "Kilpyavr CAP",
|
||||||
airbaseName = "Kilpyavr",
|
airbaseName = "Kilpyavr",
|
||||||
aircraft = 1,
|
aircraft = 12, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 15000,
|
altitude = 15000,
|
||||||
speed = 300,
|
speed = 300,
|
||||||
@ -54,7 +54,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Severomorsk-1",
|
templateName = "FIGHTER_SWEEP_RED_Severomorsk-1",
|
||||||
displayName = "Severomorsk-1 CAP",
|
displayName = "Severomorsk-1 CAP",
|
||||||
airbaseName = "Severomorsk-1",
|
airbaseName = "Severomorsk-1",
|
||||||
aircraft = 1,
|
aircraft = 16, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 20000,
|
altitude = 20000,
|
||||||
speed = 350,
|
speed = 350,
|
||||||
@ -65,7 +65,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Severomorsk-3",
|
templateName = "FIGHTER_SWEEP_RED_Severomorsk-3",
|
||||||
displayName = "Severomorsk-3 CAP",
|
displayName = "Severomorsk-3 CAP",
|
||||||
airbaseName = "Severomorsk-3",
|
airbaseName = "Severomorsk-3",
|
||||||
aircraft = 1,
|
aircraft = 14, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 25000,
|
altitude = 25000,
|
||||||
speed = 400,
|
speed = 400,
|
||||||
@ -76,7 +76,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Murmansk",
|
templateName = "FIGHTER_SWEEP_RED_Murmansk",
|
||||||
displayName = "Murmansk CAP",
|
displayName = "Murmansk CAP",
|
||||||
airbaseName = "Murmansk International",
|
airbaseName = "Murmansk International",
|
||||||
aircraft = 1,
|
aircraft = 18, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 18000,
|
altitude = 18000,
|
||||||
speed = 320,
|
speed = 320,
|
||||||
@ -87,7 +87,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Monchegorsk",
|
templateName = "FIGHTER_SWEEP_RED_Monchegorsk",
|
||||||
displayName = "Monchegorsk CAP",
|
displayName = "Monchegorsk CAP",
|
||||||
airbaseName = "Monchegorsk",
|
airbaseName = "Monchegorsk",
|
||||||
aircraft = 1,
|
aircraft = 10, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 22000,
|
altitude = 22000,
|
||||||
speed = 380,
|
speed = 380,
|
||||||
@ -98,7 +98,7 @@ local squadronConfigs = {
|
|||||||
templateName = "FIGHTER_SWEEP_RED_Olenya",
|
templateName = "FIGHTER_SWEEP_RED_Olenya",
|
||||||
displayName = "Olenya CAP",
|
displayName = "Olenya CAP",
|
||||||
airbaseName = "Olenya",
|
airbaseName = "Olenya",
|
||||||
aircraft = 1,
|
aircraft = 20, -- Maximum aircraft in squadron
|
||||||
skill = AI.Skill.GOOD,
|
skill = AI.Skill.GOOD,
|
||||||
altitude = 30000,
|
altitude = 30000,
|
||||||
speed = 450,
|
speed = 450,
|
||||||
@ -125,13 +125,84 @@ local squadronConfigs = {
|
|||||||
local activeInterceptors = {}
|
local activeInterceptors = {}
|
||||||
local lastLaunchTime = {}
|
local lastLaunchTime = {}
|
||||||
local assignedThreats = {} -- Track which threats already have interceptors assigned
|
local assignedThreats = {} -- Track which threats already have interceptors assigned
|
||||||
local squadronCooldowns = {} -- Track squadron cooldowns after destruction
|
local squadronCooldowns = {} -- Track squadron cooldowns after launch
|
||||||
|
|
||||||
|
-- Squadron aircraft tracking
|
||||||
|
local squadronAircraftCounts = {} -- Current available aircraft per squadron
|
||||||
|
local cargoReplenishmentAmount = 4 -- Aircraft added per cargo delivery
|
||||||
|
|
||||||
|
-- Initialize squadron aircraft counts
|
||||||
|
for _, squadron in pairs(squadronConfigs) do
|
||||||
|
squadronAircraftCounts[squadron.templateName] = squadron.aircraft
|
||||||
|
end
|
||||||
|
|
||||||
-- Simple logging
|
-- Simple logging
|
||||||
local function log(message)
|
local function log(message)
|
||||||
env.info("[Simple TADC] " .. message)
|
env.info("[Simple TADC] " .. message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Monitor cargo aircraft landings for squadron replenishment
|
||||||
|
local function monitorCargoReplenishment()
|
||||||
|
-- Find all red cargo aircraft
|
||||||
|
local redCargo = SET_GROUP:New():FilterCoalitions("red"):FilterCategoryAirplane():FilterStart()
|
||||||
|
|
||||||
|
redCargo:ForEach(function(cargoGroup)
|
||||||
|
if cargoGroup and cargoGroup:IsAlive() then
|
||||||
|
-- Check if cargo aircraft contains "CARGO" or "TRANSPORT" in name
|
||||||
|
local cargoName = cargoGroup:GetName():upper()
|
||||||
|
if string.find(cargoName, "CARGO") or string.find(cargoName, "TRANSPORT") or
|
||||||
|
string.find(cargoName, "C130") or string.find(cargoName, "C-130") or
|
||||||
|
string.find(cargoName, "AN26") or string.find(cargoName, "AN-26") then
|
||||||
|
|
||||||
|
-- Check if landed at any squadron airbase
|
||||||
|
local cargoCoord = cargoGroup:GetCoordinate()
|
||||||
|
local cargoVelocity = cargoGroup:GetVelocityKMH()
|
||||||
|
|
||||||
|
-- Consider aircraft "landed" if velocity is very low
|
||||||
|
if cargoVelocity < 5 then
|
||||||
|
-- Check which airbase it's near
|
||||||
|
for _, squadron in pairs(squadronConfigs) do
|
||||||
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
|
if airbase and airbase:GetCoalition() == coalition.side.RED then
|
||||||
|
local airbaseCoord = airbase:GetCoordinate()
|
||||||
|
local distance = cargoCoord:Get2DDistance(airbaseCoord)
|
||||||
|
|
||||||
|
-- If within 3km of airbase, consider it a delivery
|
||||||
|
if distance < 3000 then
|
||||||
|
-- Check if we haven't already processed this delivery
|
||||||
|
local deliveryKey = cargoName .. "_" .. squadron.airbaseName
|
||||||
|
if not _G.processedDeliveries then
|
||||||
|
_G.processedDeliveries = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _G.processedDeliveries[deliveryKey] then
|
||||||
|
-- Process replenishment
|
||||||
|
local currentCount = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
local maxCount = squadron.aircraft
|
||||||
|
local newCount = math.min(currentCount + cargoReplenishmentAmount, maxCount)
|
||||||
|
local actualAdded = newCount - currentCount
|
||||||
|
|
||||||
|
if actualAdded > 0 then
|
||||||
|
squadronAircraftCounts[squadron.templateName] = newCount
|
||||||
|
log("CARGO DELIVERY: " .. cargoName .. " delivered " .. actualAdded ..
|
||||||
|
" aircraft to " .. squadron.displayName ..
|
||||||
|
" (" .. newCount .. "/" .. maxCount .. ")")
|
||||||
|
|
||||||
|
-- Mark delivery as processed
|
||||||
|
_G.processedDeliveries[deliveryKey] = timer.getTime()
|
||||||
|
else
|
||||||
|
log("CARGO DELIVERY: " .. squadron.displayName .. " already at max capacity")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
-- Send interceptor back to base
|
-- Send interceptor back to base
|
||||||
local function sendInterceptorHome(interceptor)
|
local function sendInterceptorHome(interceptor)
|
||||||
if not interceptor or not interceptor:IsAlive() then
|
if not interceptor or not interceptor:IsAlive() then
|
||||||
@ -227,6 +298,15 @@ local function findBestSquadron(threatCoord)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if squadronAvailable then
|
||||||
|
-- Check if squadron has available aircraft
|
||||||
|
local availableAircraft = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
if availableAircraft <= 0 then
|
||||||
|
log("Squadron " .. squadron.displayName .. " has no aircraft available (" .. availableAircraft .. "/" .. squadron.aircraft .. ")")
|
||||||
|
squadronAvailable = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if squadronAvailable then
|
if squadronAvailable then
|
||||||
-- Check if airbase is still under Red control
|
-- Check if airbase is still under Red control
|
||||||
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
@ -316,6 +396,15 @@ local function launchInterceptor(threatGroup)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Limit interceptors to available aircraft
|
||||||
|
local availableAircraft = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
interceptorsNeeded = math.min(interceptorsNeeded, availableAircraft)
|
||||||
|
|
||||||
|
if interceptorsNeeded <= 0 then
|
||||||
|
log("Squadron " .. squadron.displayName .. " has no aircraft to launch")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- Launch multiple interceptors to match threat
|
-- Launch multiple interceptors to match threat
|
||||||
local spawn = SPAWN:New(squadron.templateName)
|
local spawn = SPAWN:New(squadron.templateName)
|
||||||
local interceptors = {}
|
local interceptors = {}
|
||||||
@ -373,8 +462,13 @@ local function launchInterceptor(threatGroup)
|
|||||||
|
|
||||||
-- Log the launch and track assignment
|
-- Log the launch and track assignment
|
||||||
if #interceptors > 0 then
|
if #interceptors > 0 then
|
||||||
|
-- Decrement squadron aircraft count
|
||||||
|
local currentCount = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
squadronAircraftCounts[squadron.templateName] = math.max(0, currentCount - #interceptors)
|
||||||
|
local remainingCount = squadronAircraftCounts[squadron.templateName]
|
||||||
|
|
||||||
log("Launched " .. #interceptors .. " x " .. squadron.displayName .. " to intercept " ..
|
log("Launched " .. #interceptors .. " x " .. squadron.displayName .. " to intercept " ..
|
||||||
threatSize .. " x " .. threatName)
|
threatSize .. " x " .. threatName .. " (Remaining: " .. remainingCount .. "/" .. squadron.aircraft .. ")")
|
||||||
assignedThreats[threatName] = interceptors -- Track which interceptors are assigned to this threat
|
assignedThreats[threatName] = interceptors -- Track which interceptors are assigned to this threat
|
||||||
lastLaunchTime[threatName] = timer.getTime()
|
lastLaunchTime[threatName] = timer.getTime()
|
||||||
|
|
||||||
@ -465,6 +559,11 @@ local function checkAirbaseStatus()
|
|||||||
for _, squadron in pairs(squadronConfigs) do
|
for _, squadron in pairs(squadronConfigs) do
|
||||||
local usable, status = isAirbaseUsable(squadron.airbaseName)
|
local usable, status = isAirbaseUsable(squadron.airbaseName)
|
||||||
|
|
||||||
|
-- Add aircraft count to status
|
||||||
|
local aircraftCount = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
local maxAircraft = squadron.aircraft
|
||||||
|
local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft
|
||||||
|
|
||||||
-- Check if squadron is on cooldown
|
-- Check if squadron is on cooldown
|
||||||
local cooldownStatus = ""
|
local cooldownStatus = ""
|
||||||
if squadronCooldowns[squadron.templateName] then
|
if squadronCooldowns[squadron.templateName] then
|
||||||
@ -472,15 +571,16 @@ local function checkAirbaseStatus()
|
|||||||
if currentTime < cooldownEnd then
|
if currentTime < cooldownEnd then
|
||||||
local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
|
local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
|
||||||
cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)"
|
cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)"
|
||||||
status = status .. cooldownStatus
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if usable and cooldownStatus == "" then
|
local fullStatus = status .. aircraftStatus .. cooldownStatus
|
||||||
|
|
||||||
|
if usable and cooldownStatus == "" and aircraftCount > 0 then
|
||||||
usableCount = usableCount + 1
|
usableCount = usableCount + 1
|
||||||
log("✓ " .. squadron.airbaseName .. " - " .. status)
|
log("✓ " .. squadron.airbaseName .. " - " .. fullStatus)
|
||||||
else
|
else
|
||||||
log("✗ " .. squadron.airbaseName .. " - " .. status)
|
log("✗ " .. squadron.airbaseName .. " - " .. fullStatus)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -500,4 +600,14 @@ SCHEDULER:New(nil, monitorInterceptors, {}, 10, 30)
|
|||||||
-- Run airbase status check every 2 minutes
|
-- Run airbase status check every 2 minutes
|
||||||
SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, 120)
|
SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, 120)
|
||||||
|
|
||||||
log("Simple TADC operational!")
|
-- Monitor cargo aircraft for squadron replenishment every 15 seconds
|
||||||
|
SCHEDULER:New(nil, monitorCargoReplenishment, {}, 15, 15)
|
||||||
|
|
||||||
|
log("Simple TADC operational!")
|
||||||
|
log("Aircraft replenishment: " .. cargoReplenishmentAmount .. " aircraft per cargo delivery")
|
||||||
|
|
||||||
|
-- Log initial squadron aircraft counts
|
||||||
|
for _, squadron in pairs(squadronConfigs) do
|
||||||
|
local count = squadronAircraftCounts[squadron.templateName]
|
||||||
|
log("Initial: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft")
|
||||||
|
end
|
||||||
953
Universal_TADC.lua
Normal file
953
Universal_TADC.lua
Normal file
@ -0,0 +1,953 @@
|
|||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
UNIVERSAL TADC
|
||||||
|
Dual-Coalition Tactical Air Defense Controller
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
This script provides an automated air defense system for BOTH RED and BLUE coalitions.
|
||||||
|
Each side detects enemy aircraft and launches interceptors independently. Perfect for
|
||||||
|
creating dynamic air-to-air combat scenarios or testing AI vs AI engagements.
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
• Dual-coalition support (RED and BLUE operate independently)
|
||||||
|
• Automatic threat detection and response for both sides
|
||||||
|
• Multiple squadron management with individual cooldowns per side
|
||||||
|
• Aircraft inventory tracking and cargo replenishment
|
||||||
|
• Configurable intercept ratios and response patterns per coalition
|
||||||
|
• Smart interceptor routing and RTB behavior
|
||||||
|
• Airbase status monitoring (operational/captured/destroyed)
|
||||||
|
• Comprehensive logging and status reports
|
||||||
|
• Asymmetric warfare support (different capabilities per side)
|
||||||
|
|
||||||
|
SETUP INSTRUCTIONS:
|
||||||
|
1. Create fighter aircraft templates for BOTH coalitions in the mission editor
|
||||||
|
2. Configure RED squadrons in RED_SQUADRON_CONFIG section below
|
||||||
|
3. Configure BLUE squadrons in BLUE_SQUADRON_CONFIG section below
|
||||||
|
4. Set desired behavior for each coalition in TADC_SETTINGS
|
||||||
|
5. Add this script as a "DO SCRIPT" trigger at mission start
|
||||||
|
6. Optionally create cargo aircraft with standard naming for replenishment
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
• MOOSE framework (https://github.com/FlightControl-Master/MOOSE)
|
||||||
|
• Fighter aircraft templates must exist for each coalition
|
||||||
|
• Airbases must be under correct coalition control to launch
|
||||||
|
|
||||||
|
TACTICAL SCENARIOS:
|
||||||
|
• Balanced air warfare (equal capabilities)
|
||||||
|
• Asymmetric scenarios (one side stronger/weaker)
|
||||||
|
• Training missions (AI vs AI for observation)
|
||||||
|
• Dynamic frontline battles with air support
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
MAIN SETTINGS
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Core TADC behavior settings - applies to BOTH coalitions unless overridden
|
||||||
|
local TADC_SETTINGS = {
|
||||||
|
-- Enable/Disable coalitions
|
||||||
|
enableRed = true, -- Set to false to disable RED TADC
|
||||||
|
enableBlue = true, -- Set to false to disable BLUE TADC
|
||||||
|
|
||||||
|
-- Timing settings (applies to both coalitions)
|
||||||
|
checkInterval = 30, -- How often to scan for threats (seconds)
|
||||||
|
monitorInterval = 30, -- How often to check interceptor status (seconds)
|
||||||
|
statusReportInterval = 120, -- How often to report airbase status (seconds)
|
||||||
|
cargoCheckInterval = 15, -- How often to check for cargo deliveries (seconds)
|
||||||
|
|
||||||
|
-- RED Coalition Settings
|
||||||
|
red = {
|
||||||
|
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
|
||||||
|
squadronCooldown = 900, -- RED cooldown after squadron launch (seconds)
|
||||||
|
interceptRatio = 0.8, -- RED interceptors per threat aircraft
|
||||||
|
cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
|
||||||
|
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
|
||||||
|
rtbFlightBuffer = 300, -- RED extra landing time before cleanup (seconds)
|
||||||
|
},
|
||||||
|
|
||||||
|
-- BLUE Coalition Settings
|
||||||
|
blue = {
|
||||||
|
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
|
||||||
|
squadronCooldown = 900, -- BLUE cooldown after squadron launch (seconds)
|
||||||
|
interceptRatio = 0.8, -- BLUE interceptors per threat aircraft
|
||||||
|
cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
|
||||||
|
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
|
||||||
|
rtbFlightBuffer = 300, -- BLUE extra landing time before cleanup (seconds)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft:
|
||||||
|
|
||||||
|
Threat Size: 1 2 4 8 12 16 (aircraft)
|
||||||
|
====================================================================
|
||||||
|
interceptRatio 0.2: 1 1 1 2 3 4 (conservative)
|
||||||
|
interceptRatio 0.5: 1 1 2 4 6 8 (light response)
|
||||||
|
interceptRatio 0.8: 1 2 4 7 10 13 (balanced) <- DEFAULT
|
||||||
|
interceptRatio 1.0: 1 2 4 8 12 16 (1:1 parity)
|
||||||
|
interceptRatio 1.2: 2 3 5 10 15 20 (slight advantage)
|
||||||
|
interceptRatio 1.4: 2 3 6 12 17 23 (good advantage)
|
||||||
|
interceptRatio 1.6: 2 4 7 13 20 26 (strong response)
|
||||||
|
interceptRatio 1.8: 2 4 8 15 22 29 (overwhelming)
|
||||||
|
interceptRatio 2.0: 2 4 8 16 24 32 (overkill)
|
||||||
|
|
||||||
|
TACTICAL EFFECTS:
|
||||||
|
• 0.2-0.5: Minimal response, may be overwhelmed by large formations
|
||||||
|
• 0.8-1.0: Realistic parity, creates balanced dogfights
|
||||||
|
• 1.2-1.4: Coalition advantage, challenging for enemy
|
||||||
|
• 1.6-1.8: Strong defense, difficult penetration missions
|
||||||
|
• 1.9-2.0: Nearly impenetrable, may exhaust squadrons quickly
|
||||||
|
|
||||||
|
SQUADRON IMPACT:
|
||||||
|
• Low ratios (0.2-0.8): Squadrons last longer, sustained defense
|
||||||
|
• High ratios (1.6-2.0): Rapid squadron depletion, coverage gaps
|
||||||
|
• Sweet spot (1.0-1.4): Balanced response with good coverage duration
|
||||||
|
|
||||||
|
ASYMMETRIC SCENARIOS:
|
||||||
|
• Set RED ratio 1.2, BLUE ratio 0.8 = RED advantage
|
||||||
|
• Set RED ratio 0.6, BLUE ratio 1.4 = BLUE advantage
|
||||||
|
• Different maxActiveCAP values create capacity imbalances
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
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
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- RED COALITION SQUADRONS
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
local 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" -- Aircraft type
|
||||||
|
},
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- ADD YOUR RED SQUADRONS HERE
|
||||||
|
{
|
||||||
|
templateName = "RED_CAP_SQUADRON_1", -- Change to your RED template name
|
||||||
|
displayName = "RED Squadron 1", -- Change to your preferred name
|
||||||
|
airbaseName = "YOUR_RED_AIRBASE_1", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.GOOD, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "RED_CAP_SQUADRON_2", -- Change to your RED template name
|
||||||
|
displayName = "RED Squadron 2", -- Change to your preferred name
|
||||||
|
airbaseName = "YOUR_RED_AIRBASE_2", -- Change to your RED airbase
|
||||||
|
aircraft = 16, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.GOOD, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 25000, -- Patrol altitude (feet)
|
||||||
|
speed = 400, -- Patrol speed (knots)
|
||||||
|
patrolTime = 30, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- BLUE COALITION SQUADRONS
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
local 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 = "BLUE_CAP_SQUADRON_1", -- Change to your BLUE template name
|
||||||
|
displayName = "BLUE Squadron 1", -- Change to your preferred name
|
||||||
|
airbaseName = "YOUR_BLUE_AIRBASE_1", -- Change to your BLUE airbase
|
||||||
|
aircraft = 14, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.GOOD, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 22000, -- Patrol altitude (feet)
|
||||||
|
speed = 380, -- Patrol speed (knots)
|
||||||
|
patrolTime = 28, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "BLUE_CAP_SQUADRON_2", -- Change to your BLUE template name
|
||||||
|
displayName = "BLUE Squadron 2", -- Change to your preferred name
|
||||||
|
airbaseName = "YOUR_BLUE_AIRBASE_2", -- 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"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
ADVANCED SETTINGS
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
These settings control more detailed behavior. Most users won't need to change these.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local ADVANCED_SETTINGS = {
|
||||||
|
-- Cargo aircraft detection patterns (aircraft with these names will replenish squadrons)
|
||||||
|
cargoPatterns = {"CARGO", "TRANSPORT", "C130", "C-130", "AN26", "AN-26"},
|
||||||
|
|
||||||
|
-- Distance from airbase to consider cargo "landed" (meters)
|
||||||
|
cargoLandingDistance = 3000,
|
||||||
|
|
||||||
|
-- Velocity below which aircraft is considered "landed" (km/h)
|
||||||
|
cargoLandedVelocity = 5,
|
||||||
|
|
||||||
|
-- RTB settings
|
||||||
|
rtbAltitude = 3000, -- Return to base altitude (feet)
|
||||||
|
rtbSpeed = 250, -- Return to base speed (knots)
|
||||||
|
|
||||||
|
-- Logging settings
|
||||||
|
enableDetailedLogging = true, -- Set to false to reduce log spam
|
||||||
|
logPrefix = "[Universal TADC]", -- Prefix for all log messages
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
SYSTEM CODE
|
||||||
|
(DO NOT MODIFY BELOW THIS LINE)
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Internal tracking variables - separate for each coalition
|
||||||
|
local activeInterceptors = {
|
||||||
|
red = {},
|
||||||
|
blue = {}
|
||||||
|
}
|
||||||
|
local lastLaunchTime = {
|
||||||
|
red = {},
|
||||||
|
blue = {}
|
||||||
|
}
|
||||||
|
local assignedThreats = {
|
||||||
|
red = {},
|
||||||
|
blue = {}
|
||||||
|
}
|
||||||
|
local squadronCooldowns = {
|
||||||
|
red = {},
|
||||||
|
blue = {}
|
||||||
|
}
|
||||||
|
local squadronAircraftCounts = {
|
||||||
|
red = {},
|
||||||
|
blue = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Initialize squadron aircraft counts for both coalitions
|
||||||
|
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
||||||
|
if squadron.aircraft and squadron.templateName then
|
||||||
|
squadronAircraftCounts.red[squadron.templateName] = squadron.aircraft
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
||||||
|
if squadron.aircraft and squadron.templateName then
|
||||||
|
squadronAircraftCounts.blue[squadron.templateName] = squadron.aircraft
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Logging function
|
||||||
|
local function log(message, detailed)
|
||||||
|
if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then
|
||||||
|
env.info(ADVANCED_SETTINGS.logPrefix .. " " .. message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Coalition-specific settings helper
|
||||||
|
local function getCoalitionSettings(coalitionSide)
|
||||||
|
if coalitionSide == coalition.side.RED then
|
||||||
|
return TADC_SETTINGS.red, "RED"
|
||||||
|
elseif coalitionSide == coalition.side.BLUE then
|
||||||
|
return TADC_SETTINGS.blue, "BLUE"
|
||||||
|
else
|
||||||
|
return nil, "UNKNOWN"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Startup validation
|
||||||
|
local function validateConfiguration()
|
||||||
|
local errors = {}
|
||||||
|
|
||||||
|
-- Check coalition enablement
|
||||||
|
if not TADC_SETTINGS.enableRed and not TADC_SETTINGS.enableBlue then
|
||||||
|
table.insert(errors, "Both coalitions disabled - enable at least one in TADC_SETTINGS")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Validate RED squadrons if enabled
|
||||||
|
if TADC_SETTINGS.enableRed then
|
||||||
|
if #RED_SQUADRON_CONFIG == 0 then
|
||||||
|
table.insert(errors, "No RED squadrons configured but RED TADC is enabled")
|
||||||
|
else
|
||||||
|
for i, squadron in pairs(RED_SQUADRON_CONFIG) do
|
||||||
|
local prefix = "RED Squadron " .. i .. ": "
|
||||||
|
|
||||||
|
if not squadron.templateName or squadron.templateName == "" or
|
||||||
|
squadron.templateName == "RED_CAP_SQUADRON_1" or
|
||||||
|
squadron.templateName == "RED_CAP_SQUADRON_2" then
|
||||||
|
table.insert(errors, prefix .. "templateName not configured or using default example")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.displayName or squadron.displayName == "" then
|
||||||
|
table.insert(errors, prefix .. "displayName not configured")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.airbaseName or squadron.airbaseName == "" or
|
||||||
|
squadron.airbaseName:find("YOUR_RED_AIRBASE") then
|
||||||
|
table.insert(errors, prefix .. "airbaseName not configured or using default example")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.aircraft or squadron.aircraft <= 0 then
|
||||||
|
table.insert(errors, prefix .. "aircraft count not configured or invalid")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Validate BLUE squadrons if enabled
|
||||||
|
if TADC_SETTINGS.enableBlue then
|
||||||
|
if #BLUE_SQUADRON_CONFIG == 0 then
|
||||||
|
table.insert(errors, "No BLUE squadrons configured but BLUE TADC is enabled")
|
||||||
|
else
|
||||||
|
for i, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
||||||
|
local prefix = "BLUE Squadron " .. i .. ": "
|
||||||
|
|
||||||
|
if not squadron.templateName or squadron.templateName == "" or
|
||||||
|
squadron.templateName == "BLUE_CAP_SQUADRON_1" or
|
||||||
|
squadron.templateName == "BLUE_CAP_SQUADRON_2" then
|
||||||
|
table.insert(errors, prefix .. "templateName not configured or using default example")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.displayName or squadron.displayName == "" then
|
||||||
|
table.insert(errors, prefix .. "displayName not configured")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.airbaseName or squadron.airbaseName == "" or
|
||||||
|
squadron.airbaseName:find("YOUR_BLUE_AIRBASE") then
|
||||||
|
table.insert(errors, prefix .. "airbaseName not configured or using default example")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not squadron.aircraft or squadron.aircraft <= 0 then
|
||||||
|
table.insert(errors, prefix .. "aircraft count not configured or invalid")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Report errors
|
||||||
|
if #errors > 0 then
|
||||||
|
log("CONFIGURATION ERRORS DETECTED:")
|
||||||
|
for _, error in pairs(errors) do
|
||||||
|
log(" ✗ " .. error)
|
||||||
|
end
|
||||||
|
log("Please fix configuration before using Universal TADC!")
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
log("Configuration validation passed ✓")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Monitor cargo aircraft landings for squadron replenishment
|
||||||
|
local function monitorCargoReplenishment()
|
||||||
|
-- Process RED cargo aircraft
|
||||||
|
if TADC_SETTINGS.enableRed then
|
||||||
|
local redCargo = SET_GROUP:New():FilterCoalitions("red"):FilterCategoryAirplane():FilterStart()
|
||||||
|
|
||||||
|
redCargo:ForEach(function(cargoGroup)
|
||||||
|
if cargoGroup and cargoGroup:IsAlive() then
|
||||||
|
local cargoName = cargoGroup:GetName():upper()
|
||||||
|
local isCargoAircraft = false
|
||||||
|
|
||||||
|
-- Check if aircraft name matches cargo patterns
|
||||||
|
for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
|
||||||
|
if string.find(cargoName, pattern) then
|
||||||
|
isCargoAircraft = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isCargoAircraft then
|
||||||
|
local cargoCoord = cargoGroup:GetCoordinate()
|
||||||
|
local cargoVelocity = cargoGroup:GetVelocityKMH()
|
||||||
|
|
||||||
|
-- Consider aircraft "landed" if velocity is very low
|
||||||
|
if cargoVelocity < ADVANCED_SETTINGS.cargoLandedVelocity then
|
||||||
|
-- Check which RED airbase it's near
|
||||||
|
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
||||||
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
|
if airbase and airbase:GetCoalition() == coalition.side.RED then
|
||||||
|
local airbaseCoord = airbase:GetCoordinate()
|
||||||
|
local distance = cargoCoord:Get2DDistance(airbaseCoord)
|
||||||
|
|
||||||
|
-- If within configured distance of airbase, consider it a delivery
|
||||||
|
if distance < ADVANCED_SETTINGS.cargoLandingDistance then
|
||||||
|
-- Check if we haven't already processed this delivery
|
||||||
|
local deliveryKey = cargoName .. "_RED_" .. squadron.airbaseName
|
||||||
|
if not _G.processedDeliveries then
|
||||||
|
_G.processedDeliveries = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _G.processedDeliveries[deliveryKey] then
|
||||||
|
-- Process replenishment
|
||||||
|
local currentCount = squadronAircraftCounts.red[squadron.templateName] or 0
|
||||||
|
local maxCount = squadron.aircraft
|
||||||
|
local newCount = math.min(currentCount + TADC_SETTINGS.red.cargoReplenishmentAmount, maxCount)
|
||||||
|
local actualAdded = newCount - currentCount
|
||||||
|
|
||||||
|
if actualAdded > 0 then
|
||||||
|
squadronAircraftCounts.red[squadron.templateName] = newCount
|
||||||
|
log("RED CARGO DELIVERY: " .. cargoName .. " delivered " .. actualAdded ..
|
||||||
|
" aircraft to " .. squadron.displayName ..
|
||||||
|
" (" .. newCount .. "/" .. maxCount .. ")")
|
||||||
|
|
||||||
|
-- Mark delivery as processed
|
||||||
|
_G.processedDeliveries[deliveryKey] = timer.getTime()
|
||||||
|
else
|
||||||
|
log("RED CARGO DELIVERY: " .. squadron.displayName .. " already at max capacity", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Process BLUE cargo aircraft
|
||||||
|
if TADC_SETTINGS.enableBlue then
|
||||||
|
local blueCargo = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart()
|
||||||
|
|
||||||
|
blueCargo:ForEach(function(cargoGroup)
|
||||||
|
if cargoGroup and cargoGroup:IsAlive() then
|
||||||
|
local cargoName = cargoGroup:GetName():upper()
|
||||||
|
local isCargoAircraft = false
|
||||||
|
|
||||||
|
-- Check if aircraft name matches cargo patterns
|
||||||
|
for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
|
||||||
|
if string.find(cargoName, pattern) then
|
||||||
|
isCargoAircraft = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isCargoAircraft then
|
||||||
|
local cargoCoord = cargoGroup:GetCoordinate()
|
||||||
|
local cargoVelocity = cargoGroup:GetVelocityKMH()
|
||||||
|
|
||||||
|
-- Consider aircraft "landed" if velocity is very low
|
||||||
|
if cargoVelocity < ADVANCED_SETTINGS.cargoLandedVelocity then
|
||||||
|
-- Check which BLUE airbase it's near
|
||||||
|
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
||||||
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
|
if airbase and airbase:GetCoalition() == coalition.side.BLUE then
|
||||||
|
local airbaseCoord = airbase:GetCoordinate()
|
||||||
|
local distance = cargoCoord:Get2DDistance(airbaseCoord)
|
||||||
|
|
||||||
|
-- If within configured distance of airbase, consider it a delivery
|
||||||
|
if distance < ADVANCED_SETTINGS.cargoLandingDistance then
|
||||||
|
-- Check if we haven't already processed this delivery
|
||||||
|
local deliveryKey = cargoName .. "_BLUE_" .. squadron.airbaseName
|
||||||
|
if not _G.processedDeliveries then
|
||||||
|
_G.processedDeliveries = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _G.processedDeliveries[deliveryKey] then
|
||||||
|
-- Process replenishment
|
||||||
|
local currentCount = squadronAircraftCounts.blue[squadron.templateName] or 0
|
||||||
|
local maxCount = squadron.aircraft
|
||||||
|
local newCount = math.min(currentCount + TADC_SETTINGS.blue.cargoReplenishmentAmount, maxCount)
|
||||||
|
local actualAdded = newCount - currentCount
|
||||||
|
|
||||||
|
if actualAdded > 0 then
|
||||||
|
squadronAircraftCounts.blue[squadron.templateName] = newCount
|
||||||
|
log("BLUE CARGO DELIVERY: " .. cargoName .. " delivered " .. actualAdded ..
|
||||||
|
" aircraft to " .. squadron.displayName ..
|
||||||
|
" (" .. newCount .. "/" .. maxCount .. ")")
|
||||||
|
|
||||||
|
-- Mark delivery as processed
|
||||||
|
_G.processedDeliveries[deliveryKey] = timer.getTime()
|
||||||
|
else
|
||||||
|
log("BLUE CARGO DELIVERY: " .. squadron.displayName .. " already at max capacity", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Send interceptor back to base (dual coalition)
|
||||||
|
local function sendInterceptorHome(interceptor, coalitionSide)
|
||||||
|
if not interceptor or not interceptor:IsAlive() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find nearest friendly airbase
|
||||||
|
local interceptorCoord = interceptor:GetCoordinate()
|
||||||
|
local nearestAirbase = nil
|
||||||
|
local shortestDistance = math.huge
|
||||||
|
|
||||||
|
-- Check all squadron airbases to find the nearest one that's still friendly
|
||||||
|
for _, squadron in pairs(SQUADRON_CONFIG) do
|
||||||
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
|
if airbase and airbase:GetCoalition() == coalition.side.RED and airbase:IsAlive() then
|
||||||
|
local airbaseCoord = airbase:GetCoordinate()
|
||||||
|
local distance = interceptorCoord:Get2DDistance(airbaseCoord)
|
||||||
|
if distance < shortestDistance then
|
||||||
|
shortestDistance = distance
|
||||||
|
nearestAirbase = airbase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if nearestAirbase then
|
||||||
|
local airbaseCoord = nearestAirbase:GetCoordinate()
|
||||||
|
local rtbAltitude = ADVANCED_SETTINGS.rtbAltitude * 0.3048 -- Convert feet to meters
|
||||||
|
local rtbCoord = airbaseCoord:SetAltitude(rtbAltitude)
|
||||||
|
|
||||||
|
-- Clear current tasks and route home
|
||||||
|
interceptor:ClearTasks()
|
||||||
|
interceptor:RouteAirTo(rtbCoord, ADVANCED_SETTINGS.rtbSpeed * 0.5144, "BARO") -- Convert knots to m/s
|
||||||
|
|
||||||
|
log("Sending " .. interceptor:GetName() .. " back to " .. nearestAirbase:GetName(), true)
|
||||||
|
|
||||||
|
-- Schedule cleanup after they should have landed
|
||||||
|
local flightTime = math.ceil(shortestDistance / (ADVANCED_SETTINGS.rtbSpeed * 0.5144)) + TADC_SETTINGS.rtbFlightBuffer
|
||||||
|
SCHEDULER:New(nil, function()
|
||||||
|
if activeInterceptors[interceptor:GetName()] then
|
||||||
|
activeInterceptors[interceptor:GetName()] = nil
|
||||||
|
log("Cleaned up " .. interceptor:GetName() .. " after RTB", true)
|
||||||
|
end
|
||||||
|
end, {}, flightTime)
|
||||||
|
else
|
||||||
|
log("No friendly airbase found for " .. interceptor:GetName() .. ", will clean up normally")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if airbase is still usable
|
||||||
|
local function isAirbaseUsable(airbaseName)
|
||||||
|
local airbase = AIRBASE:FindByName(airbaseName)
|
||||||
|
if not airbase then
|
||||||
|
return false, "not found"
|
||||||
|
elseif airbase:GetCoalition() ~= coalition.side.RED then
|
||||||
|
return false, "captured by " .. (airbase:GetCoalition() == coalition.side.BLUE and "Blue" or "Neutral")
|
||||||
|
elseif not airbase:IsAlive() then
|
||||||
|
return false, "destroyed"
|
||||||
|
else
|
||||||
|
return true, "operational"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count active red fighters
|
||||||
|
local function countActiveFighters()
|
||||||
|
local count = 0
|
||||||
|
for _, interceptorData in pairs(activeInterceptors) do
|
||||||
|
if interceptorData and interceptorData.group and interceptorData.group:IsAlive() then
|
||||||
|
count = count + interceptorData.group:GetSize()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find best squadron to launch
|
||||||
|
local function findBestSquadron(threatCoord)
|
||||||
|
local bestSquadron = nil
|
||||||
|
local shortestDistance = math.huge
|
||||||
|
local currentTime = timer.getTime()
|
||||||
|
|
||||||
|
for _, squadron in pairs(SQUADRON_CONFIG) do
|
||||||
|
-- Check if squadron is on cooldown
|
||||||
|
local squadronAvailable = true
|
||||||
|
if squadronCooldowns[squadron.templateName] then
|
||||||
|
local cooldownEnd = squadronCooldowns[squadron.templateName]
|
||||||
|
if currentTime < cooldownEnd then
|
||||||
|
local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
|
||||||
|
log("Squadron " .. squadron.displayName .. " on cooldown for " .. timeLeft .. " more minutes", true)
|
||||||
|
squadronAvailable = false
|
||||||
|
else
|
||||||
|
-- Cooldown expired, remove it
|
||||||
|
squadronCooldowns[squadron.templateName] = nil
|
||||||
|
log("Squadron " .. squadron.displayName .. " cooldown expired, available for launch", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if squadronAvailable then
|
||||||
|
-- Check if squadron has available aircraft
|
||||||
|
local availableAircraft = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
if availableAircraft <= 0 then
|
||||||
|
log("Squadron " .. squadron.displayName .. " has no aircraft available (" .. availableAircraft .. "/" .. squadron.aircraft .. ")", true)
|
||||||
|
squadronAvailable = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if squadronAvailable then
|
||||||
|
-- Check if airbase is still under Red control
|
||||||
|
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||||
|
if not airbase then
|
||||||
|
log("Warning: Airbase " .. squadron.airbaseName .. " not found")
|
||||||
|
elseif airbase:GetCoalition() ~= coalition.side.RED then
|
||||||
|
log("Warning: Airbase " .. squadron.airbaseName .. " no longer under Red control")
|
||||||
|
elseif not airbase:IsAlive() then
|
||||||
|
log("Warning: Airbase " .. squadron.airbaseName .. " is destroyed")
|
||||||
|
else
|
||||||
|
-- Airbase is valid, check if squadron template exists and can spawn
|
||||||
|
local template = GROUP:FindByName(squadron.templateName)
|
||||||
|
if template then
|
||||||
|
local airbaseCoord = template:GetCoordinate()
|
||||||
|
if airbaseCoord then
|
||||||
|
local distance = airbaseCoord:Get2DDistance(threatCoord)
|
||||||
|
if distance < shortestDistance then
|
||||||
|
shortestDistance = distance
|
||||||
|
bestSquadron = squadron
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log("Warning: Template " .. squadron.templateName .. " not found in mission", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bestSquadron
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Launch interceptor
|
||||||
|
local function launchInterceptor(threatGroup)
|
||||||
|
if not threatGroup or not threatGroup:IsAlive() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local threatCoord = threatGroup:GetCoordinate()
|
||||||
|
local threatName = threatGroup:GetName()
|
||||||
|
local threatSize = threatGroup:GetSize()
|
||||||
|
|
||||||
|
-- Check if threat already has interceptors assigned
|
||||||
|
if assignedThreats[threatName] then
|
||||||
|
local assignedInterceptors = assignedThreats[threatName]
|
||||||
|
local aliveCount = 0
|
||||||
|
|
||||||
|
-- Check if assigned interceptors are still alive
|
||||||
|
if type(assignedInterceptors) == "table" then
|
||||||
|
for _, interceptor in pairs(assignedInterceptors) do
|
||||||
|
if interceptor and interceptor:IsAlive() then
|
||||||
|
aliveCount = aliveCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Handle legacy single interceptor assignment
|
||||||
|
if assignedInterceptors and assignedInterceptors:IsAlive() then
|
||||||
|
aliveCount = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if aliveCount > 0 then
|
||||||
|
return -- Still being intercepted
|
||||||
|
else
|
||||||
|
-- All interceptors are dead, clear the assignment
|
||||||
|
assignedThreats[threatName] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Calculate how many interceptors to launch
|
||||||
|
local interceptorsNeeded = math.max(threatSize, math.ceil(threatSize * TADC_SETTINGS.interceptRatio))
|
||||||
|
|
||||||
|
-- Check if we have capacity
|
||||||
|
if countActiveFighters() + interceptorsNeeded > TADC_SETTINGS.maxActiveCAP then
|
||||||
|
interceptorsNeeded = TADC_SETTINGS.maxActiveCAP - countActiveFighters()
|
||||||
|
if interceptorsNeeded <= 0 then
|
||||||
|
log("Max fighters airborne, skipping launch")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find best squadron
|
||||||
|
local squadron = findBestSquadron(threatCoord)
|
||||||
|
if not squadron then
|
||||||
|
log("No squadron available")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Limit interceptors to available aircraft
|
||||||
|
local availableAircraft = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
interceptorsNeeded = math.min(interceptorsNeeded, availableAircraft)
|
||||||
|
|
||||||
|
if interceptorsNeeded <= 0 then
|
||||||
|
log("Squadron " .. squadron.displayName .. " has no aircraft to launch")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Launch multiple interceptors to match threat
|
||||||
|
local spawn = SPAWN:New(squadron.templateName)
|
||||||
|
local interceptors = {}
|
||||||
|
|
||||||
|
for i = 1, interceptorsNeeded do
|
||||||
|
local interceptor = spawn:Spawn()
|
||||||
|
|
||||||
|
if interceptor then
|
||||||
|
table.insert(interceptors, interceptor)
|
||||||
|
|
||||||
|
-- Wait a moment for initialization
|
||||||
|
SCHEDULER:New(nil, function()
|
||||||
|
if interceptor and interceptor:IsAlive() then
|
||||||
|
-- Set aggressive AI
|
||||||
|
interceptor:OptionROEOpenFire()
|
||||||
|
interceptor:OptionROTVertical()
|
||||||
|
|
||||||
|
-- Route to threat
|
||||||
|
local currentThreatCoord = threatGroup:GetCoordinate()
|
||||||
|
if currentThreatCoord then
|
||||||
|
local interceptCoord = currentThreatCoord:SetAltitude(squadron.altitude * 0.3048) -- Convert feet to meters
|
||||||
|
interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO") -- Convert knots to m/s
|
||||||
|
|
||||||
|
-- Attack the threat
|
||||||
|
local attackTask = {
|
||||||
|
id = 'AttackGroup',
|
||||||
|
params = {
|
||||||
|
groupId = threatGroup:GetID(),
|
||||||
|
weaponType = 'Auto',
|
||||||
|
attackQtyLimit = 0,
|
||||||
|
priority = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interceptor:PushTask(attackTask, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, {}, 3)
|
||||||
|
|
||||||
|
-- Track the interceptor with squadron info
|
||||||
|
activeInterceptors[interceptor:GetName()] = {
|
||||||
|
group = interceptor,
|
||||||
|
squadron = squadron.templateName,
|
||||||
|
displayName = squadron.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Emergency cleanup (safety net)
|
||||||
|
SCHEDULER:New(nil, function()
|
||||||
|
if activeInterceptors[interceptor:GetName()] then
|
||||||
|
log("Emergency cleanup of " .. interceptor:GetName() .. " (should have RTB'd)")
|
||||||
|
activeInterceptors[interceptor:GetName()] = nil
|
||||||
|
end
|
||||||
|
end, {}, TADC_SETTINGS.emergencyCleanupTime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Log the launch and track assignment
|
||||||
|
if #interceptors > 0 then
|
||||||
|
-- Decrement squadron aircraft count
|
||||||
|
local currentCount = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
squadronAircraftCounts[squadron.templateName] = math.max(0, currentCount - #interceptors)
|
||||||
|
local remainingCount = squadronAircraftCounts[squadron.templateName]
|
||||||
|
|
||||||
|
log("Launched " .. #interceptors .. " x " .. squadron.displayName .. " to intercept " ..
|
||||||
|
threatSize .. " x " .. threatName .. " (Remaining: " .. remainingCount .. "/" .. squadron.aircraft .. ")")
|
||||||
|
assignedThreats[threatName] = interceptors
|
||||||
|
lastLaunchTime[threatName] = timer.getTime()
|
||||||
|
|
||||||
|
-- Apply cooldown immediately when squadron launches
|
||||||
|
local currentTime = timer.getTime()
|
||||||
|
squadronCooldowns[squadron.templateName] = currentTime + TADC_SETTINGS.squadronCooldown
|
||||||
|
local cooldownMinutes = TADC_SETTINGS.squadronCooldown / 60
|
||||||
|
log("Squadron " .. squadron.displayName .. " LAUNCHED! Applying " .. cooldownMinutes .. " minute cooldown")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main threat detection loop
|
||||||
|
local function detectThreats()
|
||||||
|
log("Scanning for threats...", true)
|
||||||
|
|
||||||
|
-- Clean up dead threats from tracking
|
||||||
|
local currentThreats = {}
|
||||||
|
|
||||||
|
-- Find all blue aircraft
|
||||||
|
local blueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart()
|
||||||
|
local threatCount = 0
|
||||||
|
|
||||||
|
blueAircraft:ForEach(function(blueGroup)
|
||||||
|
if blueGroup and blueGroup:IsAlive() then
|
||||||
|
threatCount = threatCount + 1
|
||||||
|
currentThreats[blueGroup:GetName()] = true
|
||||||
|
log("Found threat: " .. blueGroup:GetName() .. " (" .. blueGroup:GetTypeName() .. ")", true)
|
||||||
|
|
||||||
|
-- Launch interceptor for this threat
|
||||||
|
launchInterceptor(blueGroup)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Clean up assignments for threats that no longer exist and send interceptors home
|
||||||
|
for threatName, assignedInterceptors in pairs(assignedThreats) do
|
||||||
|
if not currentThreats[threatName] then
|
||||||
|
log("Threat " .. threatName .. " eliminated, sending interceptors home...")
|
||||||
|
|
||||||
|
-- Send assigned interceptors back to base
|
||||||
|
if type(assignedInterceptors) == "table" then
|
||||||
|
for _, interceptor in pairs(assignedInterceptors) do
|
||||||
|
if interceptor and interceptor:IsAlive() then
|
||||||
|
sendInterceptorHome(interceptor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Handle legacy single interceptor assignment
|
||||||
|
if assignedInterceptors and assignedInterceptors:IsAlive() then
|
||||||
|
sendInterceptorHome(assignedInterceptors)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assignedThreats[threatName] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count assigned threats
|
||||||
|
local assignedCount = 0
|
||||||
|
for _ in pairs(assignedThreats) do assignedCount = assignedCount + 1 end
|
||||||
|
|
||||||
|
log("Scan complete: " .. threatCount .. " threats, " .. countActiveFighters() .. " active fighters, " ..
|
||||||
|
assignedCount .. " assigned")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Monitor interceptor groups for cleanup when destroyed
|
||||||
|
local function monitorInterceptors()
|
||||||
|
-- Check all active interceptors for cleanup
|
||||||
|
for interceptorName, interceptorData in pairs(activeInterceptors) do
|
||||||
|
if interceptorData and interceptorData.group then
|
||||||
|
if not interceptorData.group:IsAlive() then
|
||||||
|
-- Interceptor group is destroyed - just clean up tracking
|
||||||
|
local displayName = interceptorData.displayName
|
||||||
|
log("Interceptor from " .. displayName .. " destroyed: " .. interceptorName, true)
|
||||||
|
|
||||||
|
-- Remove from active tracking
|
||||||
|
activeInterceptors[interceptorName] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Periodic airbase status check
|
||||||
|
local function checkAirbaseStatus()
|
||||||
|
log("=== AIRBASE STATUS REPORT ===")
|
||||||
|
local usableCount = 0
|
||||||
|
local currentTime = timer.getTime()
|
||||||
|
|
||||||
|
for _, squadron in pairs(SQUADRON_CONFIG) do
|
||||||
|
local usable, status = isAirbaseUsable(squadron.airbaseName)
|
||||||
|
|
||||||
|
-- Add aircraft count to status
|
||||||
|
local aircraftCount = squadronAircraftCounts[squadron.templateName] or 0
|
||||||
|
local maxAircraft = squadron.aircraft
|
||||||
|
local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft
|
||||||
|
|
||||||
|
-- Check if squadron is on cooldown
|
||||||
|
local cooldownStatus = ""
|
||||||
|
if squadronCooldowns[squadron.templateName] then
|
||||||
|
local cooldownEnd = squadronCooldowns[squadron.templateName]
|
||||||
|
if currentTime < cooldownEnd then
|
||||||
|
local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
|
||||||
|
cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullStatus = status .. aircraftStatus .. cooldownStatus
|
||||||
|
|
||||||
|
if usable and cooldownStatus == "" and aircraftCount > 0 then
|
||||||
|
usableCount = usableCount + 1
|
||||||
|
log("✓ " .. squadron.airbaseName .. " - " .. fullStatus)
|
||||||
|
else
|
||||||
|
log("✗ " .. squadron.airbaseName .. " - " .. fullStatus)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log("Status: " .. usableCount .. "/" .. #SQUADRON_CONFIG .. " airbases operational")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- System initialization
|
||||||
|
local function initializeSystem()
|
||||||
|
log("Universal TADC starting...")
|
||||||
|
|
||||||
|
-- Validate configuration
|
||||||
|
if not validateConfiguration() then
|
||||||
|
log("System startup aborted due to configuration errors!")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
log("Squadrons configured: " .. #SQUADRON_CONFIG)
|
||||||
|
|
||||||
|
-- Log initial squadron aircraft counts
|
||||||
|
for _, squadron in pairs(SQUADRON_CONFIG) do
|
||||||
|
local count = squadronAircraftCounts[squadron.templateName]
|
||||||
|
log("Initial: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start schedulers
|
||||||
|
SCHEDULER:New(nil, detectThreats, {}, 5, TADC_SETTINGS.checkInterval)
|
||||||
|
SCHEDULER:New(nil, monitorInterceptors, {}, 10, TADC_SETTINGS.monitorInterval)
|
||||||
|
SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, TADC_SETTINGS.statusReportInterval)
|
||||||
|
SCHEDULER:New(nil, monitorCargoReplenishment, {}, 15, TADC_SETTINGS.cargoCheckInterval)
|
||||||
|
|
||||||
|
log("Universal TADC operational!")
|
||||||
|
log("Aircraft replenishment: " .. TADC_SETTINGS.cargoReplenishmentAmount .. " aircraft per cargo delivery")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start the system
|
||||||
|
initializeSystem()
|
||||||
1525
Universal_TADC_Dual.lua
Normal file
1525
Universal_TADC_Dual.lua
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user