diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua deleted file mode 100644 index c735d0b..0000000 --- a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua +++ /dev/null @@ -1,2682 +0,0 @@ --- Operation Polar Shield - MOOSE Mission Script --- --- KNOWN ISSUES WITH EASYGCICAP v0.1.30: --- 1. Internal MOOSE errors during intercept assignments (_AssignIntercept) --- 2. Inconsistent wing size requests (alternates between 1 and 2 aircraft) --- 3. These errors do NOT prevent CAP operations - system works despite them --- --- VERIFICATION: Check F10 map for active CAP flights patrolling border zones --- The script successfully initializes all squadrons and patrol points. - --- STATUS: OPERATIONAL ✅ --- All squadrons added, all patrol points configured, RedCAP started successfully --- --- NEW FEATURES: --- ✅ Randomized AI Patrol System - Aircraft pick random patrol areas within their zones --- ✅ Anti-Clustering Behavior - Each aircraft patrols different areas to prevent clustering --- ✅ Dynamic Patrol Rotation - Aircraft change patrol areas every AI_PATROL_TIME seconds --- ✅ Configurable Patrol Parameters - Adjust patrol timing and area sizes via config - --- Optional: Reduce MOOSE error message boxes in game --- env.setErrorMessageBoxEnabled(false) - ---================================================================================================ --- TACTICAL AIR DEFENSE CONTROLLER (TADC) CONFIGURATION --- ================================================================================================ - --- ================================================================================================ --- TADC LOGGING HELPER FUNCTION --- ================================================================================================ - --- Helper function to prefix all TADC logging with module identifier -local function TADC_Log(level, message) - if level == "error" then - env.error("[TADC Module] " .. message) - elseif level == "warning" then - env.warning("[TADC Module] " .. message) - else - env.info("[TADC Module] " .. message) - end -end - --- SIMPLE GCI Configuration -local GCI_Config = { - - -- Basic Response Parameters - threatRatio = 0.5, -- Send 0.5x defenders per attacker - -- THREAT RATIO REFERENCE TABLE: - -- Formula: defendersNeeded = math.ceil(threat.size * threatRatio) - -- - -- Threat Size → 1 2 3 4 5 6 7 8 10 - -- Ratio 0.5 1 1 2 2 3 3 4 4 5 - -- Ratio 0.75 1 2 3 3 4 5 6 6 8 - -- Ratio 1.0 1 2 3 4 5 6 7 8 10 - -- Ratio 1.25 2 3 4 5 7 8 9 10 13 - -- Ratio 1.5 2 3 5 6 8 9 11 12 15 - -- Ratio 2.0 2 4 6 8 10 12 14 16 20 - -- - -- Current (0.5) = Conservative Response (good for limited resources) - -- Recommended 1.0 = Proportional Response (balanced) - -- Aggressive 1.5+ = Overwhelming Response (resource intensive) - - maxSimultaneousCAP = 12, -- Maximum total airborne aircraft at once - - -- EWR Detection - useEWRDetection = true, -- Use EWR radar for detection - ewrDetectionRadius = 50000, -- EWR detection radius (50km) - - -- Simple Timing - mainLoopInterval = 15, -- Check for threats every 15 seconds - mainLoopDelay = 10, -- Initial delay before starting main loop - squadronCooldown = 120, -- 2 minutes between squadron launches - - -- Supply and Squadron Management - supplyMode = "INFINITE", -- INFINITE or FINITE - defaultSquadronSize = 4, -- Default aircraft per squadron - reservePercent = 0.3, -- Reserve 30% of aircraft - responseDelay = 5, -- Delay before responding to threats - - -- CAP Management - capSetupDelay = 30, -- Delay before setting up CAP - capOrbitRadius = 10000, -- CAP orbit radius in meters - capEngagementRange = 25000, -- CAP engagement range in meters - capZoneConstraint = true, -- Keep CAP within assigned zones - - -- Spawn Parameters - spawnDistanceMin = 5000, -- Minimum spawn distance from airbase (5km) - spawnDistanceMax = 15000, -- Maximum spawn distance from airbase (15km) - - -- Mission Parameters - minPatrolDuration = 900, -- Minimum patrol duration (15 minutes) - rtbDuration = 300, -- RTB and cleanup duration (5 minutes) - AI_PATROL_TIME = 1800, -- AI patrol rotation time (30 minutes) - - -- Status and Monitoring - statusReportInterval = 300, -- Status report every 5 minutes - engagementUpdateInterval = 30, -- Update engagements every 30 seconds - - -- Combat Effectiveness Ratios - fighterVsFighter = 1.0, -- Fighter vs Fighter effectiveness - fighterVsBomber = 1.5, -- Fighter vs Bomber effectiveness - fighterVsHelicopter = 2.0, -- Fighter vs Helicopter effectiveness - - -- Threat Management - threatTimeout = 300, -- Remove threats after 5 minutes of no contact - - -- Testing and Debug - forceOmniscientDetection = false, -- Force omniscient detection for testing - debugLevel = 2, -- Verbose logging - - -- Persistent CAP Configuration - enablePersistentCAP = true, -- Enable continuous standing patrols - persistentCAPCount = 2, -- Number of persistent CAP flights to maintain - persistentCAPInterval = 600, -- Check/maintain persistent CAP every 2 minutes - persistentCAPReserve = 0.3, -- Reserve 30% of maxSimultaneousCAP slots for threat response - - -- Dynamic Resource Management - enableDynamicReserves = true, -- Adjust reserves based on threat level - highThreatReserve = 0.4, -- 40% reserve during high threat - lowThreatReserve = 0.2, -- 20% reserve during low threat - threatThreshold = 3, -- Number of threats to trigger high alert - persistentCAPPriority = { -- Priority order for persistent CAP squadrons - "FIGHTER_SWEEP_RED_Severomorsk-1", -- Primary intercept base - "FIGHTER_SWEEP_RED_Olenya", -- Northern coverage - "FIGHTER_SWEEP_RED_Murmansk", -- Western coverage - "HELO_SWEEP_RED_Afrikanda" -- Helicopter patrol - }, - - -- Optional Features - initialStandingPatrols = true -- Launch standing patrols on startup (ENABLED FOR TESTING) -} - - --- Initialize TADC Data Structures - Must be defined before any usage -local TADC = { - -- Squadron Management - squadrons = {}, -- Squadron data and status - activeCAPs = {}, -- Currently airborne CAP flights - launchQueue = {}, -- Pending launch orders - - -- Threat Tracking - threats = {}, -- Detected threat contacts - threatHistory = {}, -- Historical threat data - - -- Mission Control - missions = {}, -- Active intercept missions - reserves = {}, -- Aircraft held in reserve - threatAssignments = {}, -- Maps threat IDs to assigned squadrons - squadronMissions = {}, -- Maps squadrons to their current threats - - -- Persistent CAP Management - persistentCAPs = {}, -- Currently active persistent CAP flights - lastPersistentCheck = 0, -- Last time persistent CAP was checked/maintained - - -- Enhanced Statistics & Performance Monitoring - stats = { - threatsDetected = 0, - interceptsLaunched = 0, - successfulEngagements = 0, - aircraftLost = 0, - avgResponseTime = 0, - maxResponseTime = 0, - systemLoadTime = 0, - memoryUsage = 0 - }, - - -- Performance tracking - performance = { - lastLoopTime = 0, - avgLoopTime = 0, - maxLoopTime = 0, - loopCount = 0 - } -} - --- Configuration Validation -local function validateConfiguration() - local errors = {} - - if GCI_Config.maxSimultaneousCAP < 1 then - table.insert(errors, "maxSimultaneousCAP must be at least 1") - end - - if GCI_Config.threatRatio <= 0 then - table.insert(errors, "threatRatio must be positive") - end - - if GCI_Config.reservePercent < 0 or GCI_Config.reservePercent > 1 then - table.insert(errors, "reservePercent must be between 0 and 1") - end - - if #errors > 0 then - TADC_Log("error", "TADC Configuration Errors:") - for _, error in pairs(errors) do - TADC_Log("error", " - " .. error) - end - return false - end - - return true -end - --- Setup Distributed Multi-Base CAP System --- This creates a dynamic response system where each squadron launches from its designated airbase --- and responds to threats based on proximity and zone coverage. - --- Create border zones with error checking -local redBorderGroup = GROUP:FindByName("RED BORDER") -local heloBorderGroup = GROUP:FindByName("HELO BORDER") - -if redBorderGroup then - CCCPBorderZone = ZONE_POLYGON:New("RED BORDER", redBorderGroup) - TADC_Log("info", "RED BORDER zone created successfully") -else - TADC_Log("error", "RED BORDER group not found!") - return -end - -if heloBorderGroup then - HeloBorderZone = ZONE_POLYGON:New("HELO BORDER", heloBorderGroup) - TADC_Log("info", "HELO BORDER zone created successfully") -else - TADC_Log("error", "HELO BORDER group not found!") - return -end - --- Define squadron configurations with their designated airbases and patrol zones -local squadronConfigs = { - -- Fixed-wing fighters patrol RED BORDER zone - { - templateName = "FIGHTER_SWEEP_RED_Kilpyavr", - displayName = "Kilpyavr CAP", - airbaseName = "Kilpyavr", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 15000, - speed = 300, - patrolTime = 20, - type = "FIGHTER" - }, - { - templateName = "FIGHTER_SWEEP_RED_Severomorsk-1", - displayName = "Severomorsk-1 CAP", - airbaseName = "Severomorsk-1", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 20000, - speed = 350, - patrolTime = 25, - type = "FIGHTER" - }, - { - templateName = "FIGHTER_SWEEP_RED_Severomorsk-3", - displayName = "Severomorsk-3 CAP", - airbaseName = "Severomorsk-3", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 25000, - speed = 400, - patrolTime = 30, - type = "FIGHTER" - }, - { - templateName = "FIGHTER_SWEEP_RED_Murmansk", - displayName = "Murmansk CAP", - airbaseName = "Murmansk International", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 18000, - speed = 320, - patrolTime = 22, - type = "FIGHTER" - }, - { - templateName = "FIGHTER_SWEEP_RED_Monchegorsk", - displayName = "Monchegorsk CAP", - airbaseName = "Monchegorsk", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 22000, - speed = 380, - patrolTime = 25, - type = "FIGHTER" - }, - { - templateName = "FIGHTER_SWEEP_RED_Olenya", - displayName = "Olenya CAP", - airbaseName = "Olenya", - patrolZone = CCCPBorderZone, - aircraft = 1, - skill = AI.Skill.GOOD, - altitude = 30000, - speed = 450, - patrolTime = 35, - type = "FIGHTER" - }, - -- Helicopter squadron patrols HELO BORDER zone - { - templateName = "HELO_SWEEP_RED_Afrikanda", - displayName = "Afrikanda Helo CAP", - airbaseName = "Afrikanda", - patrolZone = HeloBorderZone, - aircraft = 4, - skill = AI.Skill.GOOD, - altitude = 1000, - speed = 150, - patrolTime = 30, - type = "HELICOPTER" - } -} - --- Check which squadron templates exist in the mission -TADC_Log("info", "=== CHECKING SQUADRON TEMPLATES ===") -local availableSquadrons = {} - --- First, let's verify what airbases are actually available -TADC_Log("info", "=== VERIFYING AIRBASE NAMES ===") -local testAirbaseNames = { - "Kilpyavr", "Severomorsk-1", "Severomorsk-3", - "Murmansk International", "Monchegorsk", "Olenya", "Afrikanda" -} -for _, airbaseName in pairs(testAirbaseNames) do - local airbaseObj = AIRBASE:FindByName(airbaseName) - if airbaseObj then - TADC_Log("info", "✓ Airbase found: " .. airbaseName) - else - TADC_Log("info", "✗ Airbase NOT found: " .. airbaseName) - end -end - --- ================================================================================================ --- SQUADRON MANAGEMENT SYSTEM --- ================================================================================================ - -local function initializeSquadron(config) - return { - -- Basic Info - templateName = config.templateName, - displayName = config.displayName, - airbaseName = config.airbaseName, - type = config.type, - - -- Aircraft Management - totalAircraft = GCI_Config.supplyMode == "INFINITE" and 999 or (config.totalAircraft or GCI_Config.defaultSquadronSize), - availableAircraft = GCI_Config.supplyMode == "INFINITE" and 999 or (config.totalAircraft or GCI_Config.defaultSquadronSize), - airborneAircraft = 0, - reserveAircraft = 0, - - -- Mission Parameters - patrolZone = config.patrolZone, - altitude = config.altitude, - speed = config.speed, - patrolTime = config.patrolTime, - skill = config.skill, - homebase = AIRBASE:FindByName(config.airbaseName), -- Add homebase reference - - -- Enhanced Status Management - readinessLevel = "READY", -- READY, BUSY, MAINTENANCE, UNAVAILABLE, ALERT - lastLaunch = -GCI_Config.squadronCooldown, -- Allow immediate launch (set to -cooldown) - launchCooldown = GCI_Config.squadronCooldown, -- Cooldown from config - alertLevel = "GREEN", -- GREEN, YELLOW, RED - maintenanceUntil = 0, -- Time when maintenance completes - fatigue = 0, -- Pilot fatigue factor (0-100) - - -- Statistics - sorties = 0, - kills = 0, - losses = 0 - } -end - -TADC_Log("info", "=== INITIALIZING SQUADRON DATABASE ===") -for _, config in pairs(squadronConfigs) do - local template = GROUP:FindByName(config.templateName) - if template then - TADC_Log("info", "✓ Found squadron template: " .. config.templateName) - - -- Verify airbase exists and is Red coalition - local airbaseObj = AIRBASE:FindByName(config.airbaseName) - if airbaseObj then - local airbaseCoalition = airbaseObj:GetCoalition() - if airbaseCoalition == 1 then -- Red coalition - TADC_Log("info", " ✓ Airbase verified: " .. config.airbaseName .. " (Red Coalition)") - - -- Initialize squadron in TADC database - local squadron = initializeSquadron(config) - TADC.squadrons[config.templateName] = squadron - availableSquadrons[config.templateName] = config -- Keep for compatibility - - TADC_Log("info", " ✓ Squadron initialized: " .. squadron.availableAircraft .. " aircraft available") - else - TADC_Log("info", " ✗ Airbase " .. config.airbaseName .. " not Red coalition - squadron disabled") - end - else - TADC_Log("info", " ✗ Airbase NOT found: " .. config.airbaseName .. " - squadron disabled") - end - else - TADC_Log("info", "✗ Missing squadron template: " .. config.templateName) - end -end - -local squadronCount = 0 -local totalAircraft = 0 -for _, squadron in pairs(TADC.squadrons) do - squadronCount = squadronCount + 1 - totalAircraft = totalAircraft + squadron.availableAircraft -end - -TADC_Log("info", "✓ TADC Squadron Database: " .. squadronCount .. " squadrons, " .. totalAircraft .. " total aircraft") -if GCI_Config.supplyMode == "INFINITE" then - TADC_Log("info", "✓ Supply Mode: INFINITE - unlimited aircraft spawning") -else - TADC_Log("info", "✓ Supply Mode: FINITE - " .. totalAircraft .. " aircraft available") -end - --- ================================================================================================ --- TACTICAL AIR DEFENSE CONTROLLER (TADC) SYSTEM --- A comprehensive GCI system for intelligent air defense coordination --- ================================================================================================ - -TADC_Log("info", "=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===") - --- Create EWR Detection Network with Detection System -local RedEWR = SET_GROUP:New():FilterPrefixes("RED-EWR"):FilterStart() -local RedDetection = nil - --- Check EWR network availability -TADC_Log("info", "Searching for EWR groups with prefix 'RED-EWR'...") -TADC_Log("info", "Found " .. RedEWR:Count() .. " EWR groups") - --- TESTING: Force disable EWR detection if configured -if GCI_Config.forceOmniscientDetection then - TADC_Log("info", "✓ TESTING MODE: Forcing omniscient detection (EWR disabled)") - GCI_Config.useEWRDetection = false -end - -if GCI_Config.useEWRDetection and RedEWR:Count() > 0 then - TADC_Log("info", "✓ Red EWR Network: " .. RedEWR:Count() .. " detection groups") - - -- Create MOOSE Detection system using EWR network (basic version for compatibility) - local success, errorMsg = pcall(function() - RedDetection = DETECTION_AREAS:New(RedEWR, GCI_Config.ewrDetectionRadius) - RedDetection:Start() - end) - - if success then - TADC_Log("info", "✓ EWR-based threat detection system initialized (" .. (GCI_Config.ewrDetectionRadius/1000) .. "km range)") - else - TADC_Log("info", "⚠ EWR detection failed: " .. tostring(errorMsg) .. " - falling back to omniscient detection") - RedDetection = nil - end -else - if GCI_Config.useEWRDetection then - TADC_Log("info", "⚠ Warning: No RED-EWR groups found - falling back to omniscient detection") - else - TADC_Log("info", "✓ Using omniscient detection (EWR detection disabled in config)") - end -end - --- ================================================================================================ --- BACKUP IMMEDIATE RESPONSE SYSTEM (Like RU_INTERCEPT) --- ================================================================================================ - --- Forward declarations -local launchInterceptMission - --- Immediate response timer for aggressive intercepts -local lastImmediateCheck = 0 -local immediateResponseInterval = 5 -- Check every 5 seconds - --- Simple immediate intercept function (backup to complex TADC system) -local function immediateInterceptCheck() - local currentTime = timer.getTime() - if currentTime - lastImmediateCheck < immediateResponseInterval then - return - end - lastImmediateCheck = currentTime - - -- Quick scan for blue aircraft in border zones - local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() - local threatsFound = 0 - - BlueAircraft:ForEach(function(blueGroup) - if blueGroup and blueGroup:IsAlive() then - local blueCoord = blueGroup:GetCoordinate() - local inRedZone = CCCPBorderZone and CCCPBorderZone:IsCoordinateInZone(blueCoord) - local inHeloZone = HeloBorderZone and HeloBorderZone:IsCoordinateInZone(blueCoord) - - if inRedZone or inHeloZone then - threatsFound = threatsFound + 1 - TADC_Log("info", "🚨 IMMEDIATE THREAT: " .. blueGroup:GetName() .. " in " .. (inRedZone and "RED_BORDER" or "HELO_BORDER")) - - -- Find nearest ready squadron and launch immediately - local bestSquadron = nil - local bestDistance = 999999 - - for templateName, squadron in pairs(TADC.squadrons) do - if squadron.readinessLevel == "READY" and squadron.availableAircraft > 0 then - local squadronCoord = squadron.homebase and squadron.homebase:GetCoordinate() - if squadronCoord then - local distance = squadronCoord:Get2DDistance(blueCoord) - if distance < bestDistance then - bestDistance = distance - bestSquadron = squadron - end - end - end - end - - if bestSquadron then - TADC_Log("info", "🚀 IMMEDIATE LAUNCH: " .. bestSquadron.displayName .. " responding to immediate threat") - -- Launch using simplified parameters - local simpleThreat = { - id = blueGroup:GetName(), - group = blueGroup, - coordinate = blueCoord, - classification = "FIGHTER", - zone = inRedZone and "RED_BORDER" or "HELO_BORDER" - } - - -- Find the original squadron config - local squadronConfig = nil - for _, config in pairs(squadronConfigs) do - if config.templateName == bestSquadron.templateName then - squadronConfig = config - break - end - end - - if squadronConfig then - launchInterceptMission(squadronConfig, simpleThreat, "IMMEDIATE_RESPONSE") - else - TADC_Log("error", "Could not find config for immediate response squadron: " .. bestSquadron.templateName) - end - end - end - end - end) - - if threatsFound > 0 then - TADC_Log("info", "🚨 IMMEDIATE SCAN: " .. threatsFound .. " threats found in border zones") - end -end - --- ================================================================================================ --- THREAT DETECTION AND ASSESSMENT SYSTEM --- ================================================================================================ - -local function classifyThreat(group) - local category = group:GetCategory() - local typeName = group:GetTypeName() or "Unknown" - - -- Classify by DCS category and type name - if category == Group.Category.AIRPLANE then - if string.find(typeName:upper(), "B-") or string.find(typeName:upper(), "BOMBER") then - return "BOMBER" - elseif string.find(typeName:upper(), "A-") or string.find(typeName:upper(), "ATTACK") then - return "ATTACK" - else - return "FIGHTER" - end - elseif category == Group.Category.HELICOPTER then - return "HELICOPTER" - else - return "UNKNOWN" - end -end - --- ================================================================================================ --- SMART THREAT PRIORITIZATION SYSTEM --- ================================================================================================ - --- Strategic target definitions with importance weights -local STRATEGIC_TARGETS = { - -- Airbases (highest priority) - {name = "Severomorsk-1", coord = nil, importance = 100, type = "AIRBASE"}, - {name = "Olenya", coord = nil, importance = 95, type = "AIRBASE"}, - {name = "Murmansk", coord = nil, importance = 90, type = "AIRBASE"}, - {name = "Afrikanda", coord = nil, importance = 85, type = "AIRBASE"}, - - -- SAM sites (medium-high priority) - {name = "SA-10 Sites", coord = nil, importance = 70, type = "SAM"}, - {name = "SA-11 Sites", coord = nil, importance = 60, type = "SAM"}, - - -- Command centers (high priority) - {name = "Command Centers", coord = nil, importance = 80, type = "COMMAND"} -} - --- Initialize strategic target coordinates -local function initializeStrategicTargets() - for _, target in pairs(STRATEGIC_TARGETS) do - if target.type == "AIRBASE" then - local airbase = AIRBASE:FindByName(target.name) - if airbase then - target.coord = airbase:GetCoordinate() - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "✓ Strategic target: " .. target.name .. " (Importance: " .. target.importance .. ")") - end - end - end - end -end - --- Enhanced multi-factor threat priority calculation -local function calculateThreatPriority(classification, coordinate, size, velocity, heading, group) - local priority = 0.0 - - -- 1. BASE THREAT TYPE PRIORITY (40% weight) - local basePriority = 0 - if classification == "BOMBER" then - basePriority = 100 -- Highest threat - can destroy strategic targets - elseif classification == "ATTACK" then - basePriority = 85 -- High threat - ground attack capability - elseif classification == "FIGHTER" then - basePriority = 70 -- Medium threat - air superiority - elseif classification == "HELICOPTER" then - basePriority = 50 -- Lower threat but still dangerous - else - basePriority = 30 -- Unknown/other aircraft - end - priority = priority + (basePriority * 0.4) - - -- 2. FORMATION SIZE FACTOR (15% weight) - local sizeMultiplier = 1.0 - if size and size > 1 then - sizeMultiplier = 1.0 + ((size - 1) * 0.3) -- Each additional aircraft adds 30% - sizeMultiplier = math.min(sizeMultiplier, 2.5) -- Cap at 250% - end - priority = priority * sizeMultiplier - - -- 3. STRATEGIC PROXIMITY ANALYSIS (25% weight) - local proximityScore = 0 - if coordinate then - local maxProximityScore = 0 - - for _, target in pairs(STRATEGIC_TARGETS) do - if target.coord then - local distance = coordinate:Get2DDistance(target.coord) - local threatRadius = 75000 -- 75km threat radius - - if distance <= threatRadius then - -- Exponential decay - closer threats much more dangerous - local proximityFactor = math.exp(-distance / (threatRadius * 0.3)) - local targetScore = (target.importance / 100) * proximityFactor * 100 - maxProximityScore = math.max(maxProximityScore, targetScore) - - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", "Threat proximity: " .. target.name .. " (" .. math.floor(distance/1000) .. "km) Score: " .. math.floor(targetScore)) - end - end - end - end - - proximityScore = maxProximityScore - end - priority = priority + (proximityScore * 0.25) - - -- 4. SPEED AND HEADING ANALYSIS (10% weight) - local speedThreatFactor = 0 - if velocity and coordinate then - -- Calculate speed magnitude from velocity vector - local speed = 0 - if type(velocity) == "table" then - -- DCS velocity is typically a 3D vector {x, y, z} - local x = velocity.x or velocity[1] or 0 - local y = velocity.y or velocity[2] or 0 - local z = velocity.z or velocity[3] or 0 - speed = math.sqrt(x*x + y*y + z*z) - elseif type(velocity) == "number" then - speed = velocity - end - - -- Fast-moving aircraft are more threatening (less intercept time) - if speed > 250 then -- ~500 knots - speedThreatFactor = 25 -- Very fast (supersonic) - elseif speed > 150 then -- ~300 knots - speedThreatFactor = 15 -- Fast - elseif speed > 75 then -- ~150 knots - speedThreatFactor = 10 -- Medium speed - else - speedThreatFactor = 5 -- Slow/hovering - end - - -- Analyze heading threat (if heading toward strategic targets) - if heading and type(heading) == "number" then - local headingThreatBonus = 0 - for _, target in pairs(STRATEGIC_TARGETS) do - if target.coord then - local bearingToTarget = coordinate:GetAngleDegrees(coordinate:GetDirectionVec3(target.coord)) - local headingDiff = math.abs(heading - bearingToTarget) - if headingDiff > 180 then headingDiff = 360 - headingDiff end - - -- If heading within 30 degrees of target, significant bonus - if headingDiff <= 30 then - headingThreatBonus = math.max(headingThreatBonus, 20 * (target.importance / 100)) - elseif headingDiff <= 60 then - headingThreatBonus = math.max(headingThreatBonus, 10 * (target.importance / 100)) - end - end - end - speedThreatFactor = speedThreatFactor + headingThreatBonus - end - end - priority = priority + (speedThreatFactor * 0.1) - - -- 5. TEMPORAL FACTORS (10% weight) - local temporalFactor = 0 - local currentTime = timer.getTime() - - -- Night operations (reduced visibility for defenders) - local timeOfDay = (currentTime % 86400) / 3600 -- Hours since midnight - if timeOfDay >= 20 or timeOfDay <= 6 then -- Night time - temporalFactor = temporalFactor + 15 - end - - -- Weather considerations (if available) - -- Note: DCS weather API would be needed for full implementation - - priority = priority + (temporalFactor * 0.1) - - -- 6. ELECTRONIC WARFARE CONSIDERATIONS - local ewFactor = 0 - if group then - -- Check for jamming or stealth characteristics - local typeName = group:GetTypeName() or "" - if string.find(typeName:upper(), "EA-") or string.find(typeName:upper(), "EF-") then - ewFactor = 20 -- Electronic warfare aircraft are high priority - elseif string.find(typeName:upper(), "F-22") or string.find(typeName:upper(), "F-35") then - ewFactor = 15 -- Stealth aircraft harder to track - end - end - priority = priority + ewFactor - - -- Final priority clamping and rounding - priority = math.max(1, math.min(priority, 200)) -- Clamp between 1-200 - - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", string.format("Smart Priority: %s (x%d) = %.1f [Base:%.1f, Size:%.1f, Prox:%.1f, Speed:%.1f, Time:%.1f, EW:%.1f]", - classification, size or 1, priority, basePriority, sizeMultiplier, proximityScore, speedThreatFactor, temporalFactor, ewFactor)) - end - - return math.ceil(priority) -end - --- Enhanced threat assessment with predictive analysis -local function assessThreatWithPrediction(threats) - local assessedThreats = {} - local currentTime = timer.getTime() - - for threatId, threat in pairs(threats) do - local assessment = { - threat = threat, - priority = threat.priority or 1, - timeToTarget = nil, - predictedPosition = nil, - interceptWindow = nil, - recommendedResponse = "STANDARD" - } - - -- Predictive position analysis - if threat.coordinate and threat.velocity and threat.heading then - -- Calculate speed from velocity vector - local speed = 0 - if type(threat.velocity) == "table" then - local x = threat.velocity.x or threat.velocity[1] or 0 - local y = threat.velocity.y or threat.velocity[2] or 0 - local z = threat.velocity.z or threat.velocity[3] or 0 - speed = math.sqrt(x*x + y*y + z*z) - elseif type(threat.velocity) == "number" then - speed = threat.velocity - end - - -- Predict position in 5 minutes - local futureTime = 300 -- 5 minutes - local futureDistance = speed * futureTime - assessment.predictedPosition = threat.coordinate:Translate(futureDistance, threat.heading) - - -- Calculate time to closest strategic target - local minTimeToTarget = math.huge - for _, target in pairs(STRATEGIC_TARGETS) do - if target.coord then - local distance = threat.coordinate:Get2DDistance(target.coord) - local timeToTarget = distance / math.max(speed, 50) -- Minimum 50 m/s - minTimeToTarget = math.min(minTimeToTarget, timeToTarget) - end - end - assessment.timeToTarget = minTimeToTarget - - -- Determine recommended response urgency - if minTimeToTarget < 300 then -- Less than 5 minutes - assessment.recommendedResponse = "EMERGENCY" - assessment.priority = assessment.priority * 1.5 - elseif minTimeToTarget < 600 then -- Less than 10 minutes - assessment.recommendedResponse = "URGENT" - assessment.priority = assessment.priority * 1.3 - elseif minTimeToTarget < 1200 then -- Less than 20 minutes - assessment.recommendedResponse = "HIGH" - assessment.priority = assessment.priority * 1.1 - end - end - - assessedThreats[threatId] = assessment - end - - -- Sort by priority (highest first) - local sortedThreats = {} - for _, assessment in pairs(assessedThreats) do - table.insert(sortedThreats, assessment) - end - - table.sort(sortedThreats, function(a, b) - return a.priority > b.priority - end) - - return sortedThreats, assessedThreats -end - -local function assessThreatStrength(threats) - local fighters = 0 - local bombers = 0 - local helicopters = 0 - local totalThreat = 0 - - for _, threat in pairs(threats) do - local aircraft = threat.size or 1 - if threat.classification == "FIGHTER" then - fighters = fighters + aircraft - totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsFighter) - elseif threat.classification == "BOMBER" then - bombers = bombers + aircraft - totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsBomber) - elseif threat.classification == "HELICOPTER" then - helicopters = helicopters + aircraft - totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsHelicopter) - end - end - - return { - fighters = fighters, - bombers = bombers, - helicopters = helicopters, - totalAircraft = fighters + bombers + helicopters, - requiredDefenders = math.ceil(totalThreat) - } -end - --- SIMPLE THREAT DETECTION - Just find blue aircraft detected by EWR in border zones -local function simpleDetectThreats() - local newThreats = {} -- This will be our return value - local currentTime = timer.getTime() - - TADC_Log("info", "Checking for threats...") - - -- Use EWR detection if available and enabled - if GCI_Config.useEWRDetection and RedDetection then - local detectedItems = RedDetection:GetDetectedItems() - TADC_Log("info", "EWR detected " .. #detectedItems .. " items") - - for _, detectedItem in pairs(detectedItems) do - local detectedSet = detectedItem.Set - if detectedSet then - detectedSet:ForEach(function(blueGroup) - if blueGroup and blueGroup:IsAlive() and blueGroup:GetCoalition() == coalition.side.BLUE then - local coord = blueGroup:GetCoordinate() - - -- Check if in border zones - local inRedZone = CCCPBorderZone and CCCPBorderZone:IsCoordinateInZone(coord) - local inHeloZone = HeloBorderZone and HeloBorderZone:IsCoordinateInZone(coord) - - if inRedZone or inHeloZone then - local threatId = blueGroup:GetName() - local classification = classifyThreat(blueGroup) - local size = blueGroup:GetSize() - local heading = blueGroup:GetHeading() - local velocity = blueGroup:GetVelocity() - - -- Enhanced threat data with smart prioritization - newThreats[threatId] = { - id = threatId, - group = blueGroup, - coordinate = coord, - classification = classification, - size = size, - zone = inRedZone and "RED_BORDER" or "HELO_BORDER", - firstDetected = TADC.threats[threatId] and TADC.threats[threatId].firstDetected or currentTime, - lastSeen = currentTime, - heading = heading, - velocity = velocity, - priority = calculateThreatPriority(classification, coord, size, velocity, heading, blueGroup), - detectionMethod = "EWR", - -- Additional smart assessment data - typeName = blueGroup:GetTypeName(), - altitude = coord:GetLandHeight() + (blueGroup:GetCoordinate():GetY() or 0) - } - - -- Update statistics - if not TADC.threats[threatId] then - TADC.stats.threatsDetected = TADC.stats.threatsDetected + 1 - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "NEW EWR THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone) - end - end - end - end - end) - end - end - else - -- Fallback: Omniscient detection (original method) - TADC_Log("info", "🔍 Using OMNISCIENT detection mode") - TADC_Log("info", "🔍 Zone check - CCCPBorderZone: " .. tostring(CCCPBorderZone ~= nil) .. ", HeloBorderZone: " .. tostring(HeloBorderZone ~= nil)) - - local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() - - -- DEBUG: Log how many blue aircraft we found - local blueCount = BlueAircraft:Count() - TADC_Log("info", "🔍 OMNISCIENT SCAN: Found " .. blueCount .. " blue aircraft on map") - if blueCount == 0 then - TADC_Log("warning", "⚠ No blue aircraft found on map - check coalition filtering") - end - - BlueAircraft:ForEach(function(blueGroup) - if blueGroup and blueGroup:IsAlive() then - local blueCoord = blueGroup:GetCoordinate() - local threatId = blueGroup:GetName() - - -- DEBUG: Log each blue aircraft found - TADC_Log("info", "🔍 CHECKING BLUE AIRCRAFT: " .. threatId .. " (" .. blueGroup:GetTypeName() .. ") at " .. blueCoord:ToStringLLDMS()) - - -- Check if threat is in any patrol zone - local inRedZone = CCCPBorderZone and CCCPBorderZone:IsCoordinateInZone(blueCoord) - local inHeloZone = HeloBorderZone and HeloBorderZone:IsCoordinateInZone(blueCoord) - - -- DEBUG: Log zone check results - TADC_Log("info", "🔍 Zone Check - RED: " .. tostring(inRedZone) .. ", HELO: " .. tostring(inHeloZone)) - - if inRedZone or inHeloZone then - TADC_Log("info", "🎯 THREAT IN ZONE: " .. threatId .. " detected in " .. (inRedZone and "RED_BORDER" or "HELO_BORDER")) - - local classification = classifyThreat(blueGroup) - local size = blueGroup:GetSize() - local heading = blueGroup:GetHeading() - local velocity = blueGroup:GetVelocity() - - TADC_Log("info", "🎯 Details: " .. classification .. ", " .. size .. " aircraft, heading " .. heading .. "°, " .. velocity .. " kts") - - newThreats[threatId] = { - id = threatId, - group = blueGroup, - coordinate = blueCoord, - classification = classification, - size = size, - zone = inRedZone and "RED_BORDER" or "HELO_BORDER", - firstDetected = TADC.threats[threatId] and TADC.threats[threatId].firstDetected or currentTime, - lastSeen = currentTime, - heading = heading, - velocity = velocity, - priority = calculateThreatPriority(classification, blueCoord, size, velocity, heading, blueGroup), - detectionMethod = "OMNISCIENT", - -- Additional smart assessment data - typeName = blueGroup:GetTypeName(), - altitude = blueCoord:GetLandHeight() + (blueGroup:GetCoordinate():GetY() or 0) - } - - -- Update statistics - if not TADC.threats[threatId] then - TADC.stats.threatsDetected = TADC.stats.threatsDetected + 1 - TADC_Log("info", "✅ NEW THREAT REGISTERED: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone) - else - TADC_Log("info", "🔄 EXISTING THREAT UPDATED: " .. threatId) - end - else - TADC_Log("debug", "❌ Aircraft " .. threatId .. " not in patrol zones - ignored") - end - end - end) - end - - -- Remove old threats (not seen for threatTimeout seconds) - for threatId, threat in pairs(TADC.threats) do - if not newThreats[threatId] and (currentTime - threat.lastSeen) > GCI_Config.threatTimeout then - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "THREAT TIMEOUT: " .. threatId .. " - removing from threat picture") - end - end - end - - TADC.threats = newThreats - - -- Summary logging - local threatCount = 0 - for _ in pairs(newThreats) do threatCount = threatCount + 1 end - TADC_Log("info", "📊 Threat picture update complete: " .. threatCount .. " active threats") - - return newThreats -end - -local function findOptimalDefenders(threats, zone) - local assessment = assessThreatStrength(threats) - local availableSquadrons = {} - local totalAvailable = 0 - - -- Find available squadrons for this zone - local currentTime = timer.getTime() - for templateName, squadron in pairs(TADC.squadrons) do - -- Check availability, aircraft count, AND cooldown status - local cooldownRemaining = squadron.launchCooldown - (currentTime - squadron.lastLaunch) - if squadron.readinessLevel == "READY" and - squadron.availableAircraft > 0 and - cooldownRemaining <= 0 then - -- Check if squadron type matches zone - local canRespond = false - if zone == "RED_BORDER" and squadron.type == "FIGHTER" then - canRespond = true - elseif zone == "HELO_BORDER" and squadron.type == "HELICOPTER" then - canRespond = true - end - - if canRespond then - local airbaseObj = AIRBASE:FindByName(squadron.airbaseName) - if airbaseObj then - -- Calculate average distance to threats - local totalDistance = 0 - local threatCount = 0 - for _, threat in pairs(threats) do - if threat.zone == zone then - local distance = threat.coordinate:Get2DDistance(airbaseObj:GetCoordinate()) - totalDistance = totalDistance + distance - threatCount = threatCount + 1 - end - end - - if threatCount > 0 then - availableSquadrons[templateName] = { - squadron = squadron, - averageDistance = totalDistance / threatCount, - priority = squadron.type == "FIGHTER" and 2 or 1 - } - totalAvailable = totalAvailable + squadron.availableAircraft - end - end - end - end - end - - -- Sort by distance and priority - local sortedSquadrons = {} - for templateName, data in pairs(availableSquadrons) do - table.insert(sortedSquadrons, {templateName = templateName, data = data}) - end - - table.sort(sortedSquadrons, function(a, b) - if a.data.priority ~= b.data.priority then - return a.data.priority > b.data.priority - else - return a.data.averageDistance < b.data.averageDistance - end - end) - - return sortedSquadrons, assessment, totalAvailable -end - --- ================================================================================================ --- MISSION PLANNING AND LAUNCH COORDINATION --- ================================================================================================ - -local function calculateRequiredForce(threats, zone) - local zoneThreats = {} - for _, threat in pairs(threats) do - if threat.zone == zone then - table.insert(zoneThreats, threat) - end - end - - if #zoneThreats == 0 then - return 0, {} - end - - local assessment = assessThreatStrength(zoneThreats) - return assessment.requiredDefenders, zoneThreats -end - -local function assignThreatsToSquadrons(threats, zone) - TADC_Log("info", "🎯 ASSIGNING THREATS TO SQUADRONS for " .. zone) - - local zoneThreats = {} - for _, threat in pairs(threats) do - if threat.zone == zone then - table.insert(zoneThreats, threat) - end - end - - TADC_Log("info", "Found " .. #zoneThreats .. " threats in " .. zone) - - if #zoneThreats == 0 then - TADC_Log("info", "No threats in zone, returning empty assignments") - return {} - end - - -- Get enhanced threat assessment with smart prioritization - local sortedThreats, threatAssessments = assessThreatWithPrediction(zoneThreats) - - -- Smart threat assessment logging - if GCI_Config.debugLevel >= 1 and #sortedThreats > 0 then - TADC_Log("info", "=== SMART THREAT ASSESSMENT: " .. zone .. " ===") - for i, assessment in ipairs(sortedThreats) do - local threat = assessment.threat - local timeStr = assessment.timeToTarget and string.format("%.1fm", assessment.timeToTarget/60) or "N/A" - TADC_Log("info", string.format("%d. %s (%s x%d) Priority:%d TTT:%s Response:%s", - i, threat.id, threat.classification, threat.size or 1, - math.floor(assessment.priority), timeStr, assessment.recommendedResponse)) - end - end - - local availableSquadrons, assessment, totalAvailable = findOptimalDefenders(threats, zone) - - -- Debug: Show squadron selection results - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "Squadron Selection for " .. zone .. ":") - for i, squadronData in pairs(availableSquadrons) do - local squadron = squadronData.data.squadron - local currentTime = timer.getTime() - local cooldownRemaining = math.max(0, squadron.launchCooldown - (currentTime - squadron.lastLaunch)) - TADC_Log("info", " " .. i .. ". " .. squadron.displayName .. " - Available: " .. squadron.availableAircraft .. ", Cooldown: " .. math.ceil(cooldownRemaining) .. "s") - end - end - - -- Assign each unassigned threat to the best available squadron (using smart-prioritized order) - local newAssignments = {} - - -- Process threats in smart priority order (highest priority first) - for _, assessment in ipairs(sortedThreats) do - local threat = assessment.threat - local threatId = threat.id .. "_" .. threat.firstDetected - - -- Skip if threat is already assigned to an active squadron - local skipThreat = false - if TADC.threatAssignments[threatId] then - local assignedSquadron = TADC.threatAssignments[threatId] - local squadron = TADC.squadrons[assignedSquadron] - if squadron and squadron.readinessLevel == "BUSY" then - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", "Threat " .. threat.id .. " already assigned to " .. assignedSquadron) - end - skipThreat = true -- Skip this threat, it's being handled - else - -- Squadron is no longer busy, clear the assignment - TADC.threatAssignments[threatId] = nil - if TADC.squadronMissions[assignedSquadron] then - TADC.squadronMissions[assignedSquadron] = nil - end - end - end - - -- Only process threat if not skipped - if not skipThreat then - -- Enhanced squadron selection with smart threat matching - local bestSquadron = nil - local bestScore = -1 - - for _, squadronData in pairs(availableSquadrons) do - local squadron = squadronData.data.squadron - local templateName = squadronData.templateName - - -- Check if squadron is available (not already assigned to another threat) - if not TADC.squadronMissions[templateName] and squadron.availableAircraft > 0 then - - -- Find the original squadron config - local originalConfig = nil - for _, config in pairs(squadronConfigs) do - if config.templateName == templateName then - originalConfig = config - break - end - end - - if originalConfig then - -- Calculate enhanced squadron suitability score - local score = 0 - local distanceToThreat = squadronData.data.averageDistance or math.huge - - -- 1. DISTANCE FACTOR (40% of score) - Closer squadrons respond faster - local distanceScore = 40 * math.exp(-distanceToThreat / 75000) -- 75km ideal range - score = score + distanceScore - - -- 2. THREAT TYPE SPECIALIZATION (30% of score) - local typeBonus = 0 - if threat.classification == "HELICOPTER" and originalConfig.type == "HELICOPTER" then - typeBonus = 30 -- Perfect match for helo vs helo - elseif threat.classification == "BOMBER" and originalConfig.type == "FIGHTER" then - typeBonus = 25 -- Fighters excellent vs bombers - elseif threat.classification == "ATTACK" and originalConfig.type == "FIGHTER" then - typeBonus = 20 -- Fighters good vs attack aircraft - elseif threat.classification == "FIGHTER" and originalConfig.type == "FIGHTER" then - typeBonus = 15 -- Fighter vs fighter - elseif originalConfig.type == "FIGHTER" then - typeBonus = 10 -- Fighters can handle most threats - end - score = score + typeBonus - - -- 3. SQUADRON READINESS (20% of score) - local readinessBonus = squadron.availableAircraft * 3 -- More aircraft = better - if squadron.alertLevel == "GREEN" then - readinessBonus = readinessBonus + 10 - elseif squadron.alertLevel == "YELLOW" then - readinessBonus = readinessBonus + 5 - end - score = score + readinessBonus - - -- 4. RESPONSE URGENCY MATCHING (10% of score) - local urgencyBonus = 0 - if assessment.recommendedResponse == "EMERGENCY" then - urgencyBonus = 10 -- All squadrons get urgency bonus - score = score * 1.2 -- Emergency multiplier - elseif assessment.recommendedResponse == "URGENT" then - urgencyBonus = 7 - score = score * 1.1 -- Urgent multiplier - elseif assessment.recommendedResponse == "HIGH" then - urgencyBonus = 4 - end - score = score + urgencyBonus - - -- 5. PENALTY FACTORS - local penalties = 0 - local currentTime = timer.getTime() - local timeSinceLaunch = currentTime - squadron.lastLaunch - if timeSinceLaunch < squadron.launchCooldown * 1.5 then - penalties = penalties + 5 - end - if squadron.fatigue and squadron.fatigue > 50 then - penalties = penalties + (squadron.fatigue - 50) * 0.2 - end - score = score - penalties - - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", string.format(" %s vs %s: Score=%.1f [Dist:%.1f, Type:%d, Ready:%.1f, Urg:%d, Pen:%.1f]", - squadron.displayName, threat.id, score, distanceScore, typeBonus, readinessBonus, urgencyBonus, penalties)) - end - - if score > bestScore then - bestScore = score - bestSquadron = { - templateName = templateName, - squadron = squadron, - distance = distanceToThreat, - threat = threat, - threatId = threatId, - config = originalConfig, - matchScore = score, - assessment = assessment -- Include threat assessment - } - end - end - end - end - - if bestSquadron then - table.insert(newAssignments, bestSquadron) - -- Mark squadron as assigned to prevent double-booking - TADC.squadronMissions[bestSquadron.templateName] = bestSquadron.threatId - TADC.threatAssignments[bestSquadron.threatId] = bestSquadron.templateName - - local timeStr = bestSquadron.assessment and bestSquadron.assessment.timeToTarget and - string.format(" TTT:%.1fm", bestSquadron.assessment.timeToTarget/60) or "" - local responseStr = bestSquadron.assessment and bestSquadron.assessment.recommendedResponse or "STANDARD" - TADC_Log("info", string.format("✅ ASSIGNMENT CREATED: %s → %s (%s x%d) Score:%.1f Priority:%d%s %s", - bestSquadron.squadron.displayName, threat.id, threat.classification, - threat.size or 1, bestSquadron.matchScore or 0, - bestSquadron.assessment and math.floor(bestSquadron.assessment.priority) or threat.priority, - timeStr, responseStr)) - else - TADC_Log("warning", "❌ NO SQUADRON AVAILABLE for threat " .. threat.id .. " - checking why...") - -- Debug why no squadron was available - local availableCount = 0 - for templateName, squadron in pairs(TADC.squadrons) do - if squadron.readinessLevel == "READY" and squadron.availableAircraft >= 1 then - availableCount = availableCount + 1 - TADC_Log("info", " Available: " .. squadron.displayName) - else - TADC_Log("info", " Unavailable: " .. squadron.displayName .. " (" .. squadron.readinessLevel .. ", " .. squadron.availableAircraft .. " aircraft)") - end - end - TADC_Log("info", "Total available squadrons: " .. availableCount) - end - end - end - - TADC_Log("info", "📋 ASSIGNMENT SUMMARY: Created " .. #newAssignments .. " assignments for " .. #zoneThreats .. " threats in " .. zone) - return newAssignments -end - --- ================================================================================================ --- CAP LAUNCH FUNCTION (Must be defined before executeInterceptMission) --- ================================================================================================ - -local function launchCAP(config, aircraftCount, reason) - aircraftCount = aircraftCount or 1 - - TADC_Log("info", "=== LAUNCHING CAP ===") - TADC_Log("info", "Squadron: " .. config.displayName) - TADC_Log("info", "Airbase: " .. config.airbaseName) - TADC_Log("info", "Aircraft: " .. aircraftCount) - TADC_Log("info", "Reason: " .. reason) - - local success, errorMsg = pcall(function() - -- Find the airbase object - local airbaseObj = AIRBASE:FindByName(config.airbaseName) - if not airbaseObj then - TADC_Log("info", "✗ Could not find airbase: " .. config.airbaseName) - return - end - - TADC_Log("info", "✓ Airbase object found, attempting spawn...") - TADC_Log("info", "Template: " .. config.templateName) - TADC_Log("info", "Aircraft count: " .. config.aircraft) - TADC_Log("info", "Skill: " .. tostring(config.skill)) - - -- Check if template exists - local templateGroup = GROUP:FindByName(config.templateName) - if not templateGroup then - TADC_Log("info", "✗ CRITICAL: Template group not found: " .. config.templateName) - TADC_Log("info", "SOLUTION: In Mission Editor, ensure group '" .. config.templateName .. "' exists and is set to 'Late Activation'") - return - end - - -- Check template group properties - local coalition = templateGroup:GetCoalition() - local coalitionName = coalition == 1 and "Red" or (coalition == 2 and "Blue" or "Neutral") - TADC_Log("info", "✓ Template group found - Coalition: " .. coalitionName) - - if coalition ~= 1 then - TADC_Log("info", "✗ CRITICAL: Template group is not Red coalition (coalition=" .. coalition .. ")") - TADC_Log("info", "SOLUTION: In Mission Editor, set group '" .. config.templateName .. "' to Red coalition") - return - end - - -- Template groups should NOT be alive if Late Activation is working correctly - local isAlive = templateGroup:IsAlive() - TADC_Log("info", "Template Status - Alive: " .. tostring(isAlive) .. " (should be false for Late Activation)") - - if isAlive then - TADC_Log("info", "⚠ Warning: Template group is alive - Late Activation may not be set correctly") - TADC_Log("info", "This means the group has already spawned in the mission") - else - TADC_Log("info", "✓ Template group correctly set to Late Activation") - end - - -- Create SPAWN object with proper initialization - TADC_Log("info", "Creating SPAWN object...") - local spawner = SPAWN:New(config.templateName) - - - -- Try different spawn methods that actually work - local spawnedGroup = nil - - -- Enhanced spawn system with better error handling and retry logic - local airbaseCoord = airbaseObj:GetCoordinate() - local spawnAttempts = 0 - local maxAttempts = 3 - - local function attemptSpawn() - spawnAttempts = spawnAttempts + 1 - - -- Method 1: Air spawn with validation - if spawnAttempts == 1 then - local spawnCoord = airbaseCoord:Translate( - math.random(GCI_Config.spawnDistanceMin, GCI_Config.spawnDistanceMax), - math.random(0, 360) - ):SetAltitude(config.altitude * 0.3048) -- Convert feet to meters - - TADC_Log("info", "Attempt " .. spawnAttempts .. ": Air spawn at " .. config.altitude .. "ft near " .. config.airbaseName) - return spawner:SpawnFromCoordinate(spawnCoord, nil, SPAWN.Takeoff.Air) - - -- Method 2: Hot start from airbase - elseif spawnAttempts == 2 then - TADC_Log("info", "Attempt " .. spawnAttempts .. ": Hot start from airbase") - return spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Hot) - - -- Method 3: Cold start from airbase - elseif spawnAttempts == 3 then - TADC_Log("info", "Attempt " .. spawnAttempts .. ": Cold start from airbase") - return spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Cold) - end - - return nil - end - - -- Retry spawn with delays - while spawnAttempts < maxAttempts and not spawnedGroup do - spawnedGroup = attemptSpawn() - if not spawnedGroup and spawnAttempts < maxAttempts then - TADC_Log("info", "Spawn attempt " .. spawnAttempts .. " failed, retrying in 2 seconds...") - -- Note: In actual implementation, you'd want to use SCHEDULER for the delay - end - end - - if spawnedGroup then - TADC_Log("info", "✓ Aircraft spawned successfully: " .. config.displayName) - -- Note: Skip immediate altitude check as coordinates may not be ready yet - -- Altitude will be set properly in the scheduled CAP setup task - - -- Wait a moment then set up proper CAP mission - SCHEDULER:New(nil, function() - if spawnedGroup and spawnedGroup:IsAlive() then - TADC_Log("info", "Setting up CAP mission for " .. config.displayName) - - -- Set proper altitude and speed (with enhanced safety checks) - local currentCoord = nil - local coordSuccess = pcall(function() - currentCoord = spawnedGroup:GetCoordinate() - end) - - if coordSuccess and currentCoord then - local properAltCoord = currentCoord:SetAltitude(config.altitude) - spawnedGroup:RouteAirTo(properAltCoord, config.speed, "BARO") - TADC_Log("info", "✓ Set altitude to " .. config.altitude .. "ft") - else - TADC_Log("info", "⚠ Coordinate not ready yet, CAP task will handle altitude") - end - - -- Set up AGGRESSIVE AI options with error handling - local success, errorMsg = pcall(function() - -- ENGAGEMENT RULES - AGGRESSIVE - spawnedGroup:OptionROEOpenFire() -- Engage enemies immediately - spawnedGroup:OptionROTVertical() -- No altitude restrictions (for all aircraft) - - -- DETECTION AND TARGETING - AGGRESSIVE - spawnedGroup:OptionECM_Never() -- Never use ECM to stay hidden - -- spawnedGroup:OptionRadarUsing(AI.Option.Ground.val.RADAR_USING.FOR_SEARCH_IF_REQUIRED) -- Skip this - not for air units - - -- RTB CONDITIONS - AGGRESSIVE (stay longer) - spawnedGroup:OptionRTBBingoFuel() -- RTB when low fuel - spawnedGroup:OptionRTBAmmo(0.05) -- RTB when 5% ammo left (was 10%) - - -- COMBAT BEHAVIOR - AGGRESSIVE - spawnedGroup:OptionAAAttackRange(AI.Option.Air.val.AA_ATTACK_RANGE.MAX_RANGE) -- Use maximum weapon range - spawnedGroup:OptionMissileAttack(AI.Option.Air.val.MISSILE_ATTACK.MAX_RANGE) -- Fire missiles at max range - - -- FORMATION AND MANEUVERING - AGGRESSIVE - if config.type == "HELICOPTER" then - spawnedGroup:OptionFormation(AI.Option.Air.val.FORMATION.LINE_ABREAST) -- Spread out for helicopters - else - spawnedGroup:OptionFormation(AI.Option.Air.val.FORMATION.FINGER_FOUR) -- Combat formation for fighters - end - - TADC_Log("info", "✓ Aggressive AI options set for " .. config.displayName) - end) - - if not success then - TADC_Log("info", "⚠ Warning: Could not set all AI options: " .. tostring(errorMsg)) - end - - -- Create randomized patrol system to prevent clustering - local function setupRandomPatrol() - if spawnedGroup and spawnedGroup:IsAlive() and config.patrolZone then - -- Get a random point within the patrol zone - local patrolZoneCoord = config.patrolZone:GetCoordinate() - if patrolZoneCoord then - -- Generate random patrol point within zone boundaries - local maxRadius = math.min(GCI_Config.capOrbitRadius, 25000) -- Max 25km from zone center - local randomRadius = math.random(GCI_Config.minPatrolSeparation, maxRadius) - local randomBearing = math.random(0, 360) - - local patrolPoint = patrolZoneCoord:Translate(randomRadius, randomBearing) - patrolPoint = patrolPoint:SetAltitude(config.altitude * 0.3048) -- Convert to meters - - TADC_Log("info", "Setting new patrol area for " .. config.displayName .. " at " .. randomRadius .. "m/" .. randomBearing .. "°") - - -- Clear old tasks and set up AGGRESSIVE HUNTER-KILLER tasks - spawnedGroup:ClearTasks() - - -- PRIMARY TASK: AGGRESSIVE AREA SWEEP (Priority 1 - Most Important) - local sweepTask = { - id = 'EngageTargetsInZone', - params = { - targetTypes = {'Air'}, - priority = 1, -- HIGHEST PRIORITY - zone = { - point = {x = patrolPoint.x, y = patrolPoint.z}, - radius = GCI_Config.capEngagementRange, -- Large search area - }, - noTargetTypes = {}, -- Engage ALL air targets - value = 'All', -- Engage all found targets - } - } - spawnedGroup:PushTask(sweepTask, 1) - - -- SECONDARY TASK: COMBAT AIR PATROL with AGGRESSIVE SEARCH (Priority 2) - local aggressiveCAP = { - id = 'ComboTask', - params = { - tasks = { - -- Search Pattern - { - id = 'EngageTargets', - params = { - targetTypes = {'Air'}, - priority = 1, - maxDistEnabled = true, - maxDist = GCI_Config.capEngagementRange * 1.2, -- 20% larger search range - direction = 0, -- Search all directions - attackQtyLimit = 0, -- No limit on attacks - directionEnabled = false, -- Search all directions - altitudeEnabled = false, -- Search all altitudes - } - }, - -- Fallback Patrol (only if no targets) - { - id = 'Orbit', - params = { - pattern = config.type == "HELICOPTER" and 'Race-Track' or 'Circle', - point = {x = patrolPoint.x, y = patrolPoint.z}, - radius = config.type == "HELICOPTER" and 5000 or GCI_Config.patrolAreaRadius, - altitude = config.altitude * 0.3048, - speed = config.speed * 0.514444 * 1.1, -- 10% faster for aggressive patrol - } - } - } - } - } - spawnedGroup:PushTask(aggressiveCAP, 2) - - TADC_Log("info", "✓ " .. config.displayName .. " assigned to patrol area " .. randomRadius .. "m from zone center") - end - end - end - - -- Set up initial patrol area - local capSuccess, capError = pcall(function() - setupRandomPatrol() - - -- Schedule patrol area changes every AI_PATROL_TIME seconds - if TADC.activeCAPs[config.templateName] then - TADC.activeCAPs[config.templateName].patrolScheduler = SCHEDULER:New(nil, setupRandomPatrol, {}, GCI_Config.AI_PATROL_TIME, GCI_Config.AI_PATROL_TIME) - end - end) - - if not capSuccess then - TADC_Log("info", "⚠ Warning: Could not set CAP tasks: " .. tostring(capError)) - -- Fallback: just set basic engage task - spawnedGroup:OptionROEOpenFire() - end - - TADC_Log("info", "✓ CAP mission established at " .. config.altitude .. "ft altitude") - - end - end, {}, 5) -- 5 second delay to let aircraft stabilize - - -- Mark as active - TADC.activeCAPs[config.templateName] = { - group = spawnedGroup, - launchTime = timer.getTime(), - config = config - } - - -- Set up extended patrol timer (much longer than before) - local patrolDuration = math.max(config.patrolTime * 60, GCI_Config.minPatrolDuration) -- Minimum from config - SCHEDULER:New(nil, function() - if TADC.activeCAPs[config.templateName] then - TADC_Log("info", config.displayName .. " completing patrol mission - RTB") - local group = TADC.activeCAPs[config.templateName].group - if group and group:IsAlive() then - -- Clear current tasks - group:ClearTasks() - - -- Send back to base - local airbaseObj = AIRBASE:FindByName(config.airbaseName) - if airbaseObj then - group:RouteRTB(airbaseObj) - TADC_Log("info", "✓ " .. config.displayName .. " returning to " .. config.airbaseName) - end - - -- Clean up after RTB delay - SCHEDULER:New(nil, function() - if TADC.activeCAPs[config.templateName] then - local capData = TADC.activeCAPs[config.templateName] - local rtbGroup = capData.group - - -- Stop patrol scheduler - if capData.patrolScheduler then - capData.patrolScheduler:Stop() - end - - if rtbGroup and rtbGroup:IsAlive() then - rtbGroup:Destroy() - TADC_Log("info", "✓ " .. config.displayName .. " landed and available for next sortie") - end - TADC.activeCAPs[config.templateName] = nil - end - end, {}, GCI_Config.rtbDuration) -- RTB time from config - else - -- Stop patrol scheduler if CAP is being cleaned up early - if TADC.activeCAPs[config.templateName] and TADC.activeCAPs[config.templateName].patrolScheduler then - TADC.activeCAPs[config.templateName].patrolScheduler:Stop() - end - TADC.activeCAPs[config.templateName] = nil - end - end - end, {}, patrolDuration) - - else - TADC_Log("info", "✗ Failed to spawn " .. config.displayName) - end - end) - - if not success then - TADC_Log("info", "✗ Error launching CAP: " .. tostring(errorMsg)) - return false - else - TADC_Log("info", "✓ CAP launch completed successfully") - return true - end -end - --- ================================================================================================ --- AGGRESSIVE INTERCEPT FUNCTION - Direct threat vectoring --- ================================================================================================ - -launchInterceptMission = function(config, threat, reason) - TADC_Log("info", "=== LAUNCHING INTERCEPT MISSION ===") - TADC_Log("info", "Squadron: " .. config.displayName) - TADC_Log("info", "Target: " .. (threat and threat.id or "Unknown")) - TADC_Log("info", "Target Type: " .. (threat and threat.classification or "Unknown")) - TADC_Log("info", "Reason: " .. reason) - - local success, errorMsg = pcall(function() - -- Find the airbase object - local airbaseObj = AIRBASE:FindByName(config.airbaseName) - if not airbaseObj then - TADC_Log("error", "✗ Could not find airbase: " .. config.airbaseName) - error("Airbase not found: " .. config.airbaseName) - end - - -- Check if template exists - local templateGroup = GROUP:FindByName(config.templateName) - if not templateGroup then - TADC_Log("error", "✗ CRITICAL: Template group not found: " .. config.templateName) - error("Template group not found: " .. config.templateName) - end - - -- Create SPAWN object - local spawner = SPAWN:New(config.templateName) - - -- Spawn aircraft in air at proper altitude - local airbaseCoord = airbaseObj:GetCoordinate() - local spawnCoord = airbaseCoord:Translate(math.random(GCI_Config.spawnDistanceMin, GCI_Config.spawnDistanceMax), math.random(0, 360)) - spawnCoord = spawnCoord:SetAltitude(config.altitude) - - TADC_Log("info", "Spawning interceptor at " .. config.altitude .. "ft near " .. config.airbaseName) - local spawnedGroup = spawner:SpawnFromCoordinate(spawnCoord, nil, SPAWN.Takeoff.Air) - - if not spawnedGroup then - -- Fallback spawn methods - spawnedGroup = spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Hot) - if not spawnedGroup then - spawnedGroup = spawner:SpawnFromCoordinate(airbaseCoord) - end - end - - if spawnedGroup then - TADC_Log("info", "✓ Interceptor spawned successfully: " .. config.displayName) - - -- Wait a moment then set up AGGRESSIVE INTERCEPT mission - SCHEDULER:New(nil, function() - -- Enhanced safety checks - if not (spawnedGroup and spawnedGroup:IsAlive()) then - TADC_Log("warning", "⚠ Spawned group " .. config.displayName .. " is not alive, aborting intercept setup") - return - end - - if not (threat and threat.group and threat.group:IsAlive()) then - TADC_Log("warning", "⚠ Threat is no longer valid, aborting intercept setup for " .. config.displayName) - return - end - - -- Test if we can get coordinates before proceeding - local testCoord = nil - local coordSuccess = pcall(function() - testCoord = spawnedGroup:GetCoordinate() - end) - - if not coordSuccess or not testCoord then - TADC_Log("warning", "⚠ Cannot get coordinates for " .. config.displayName .. ", delaying intercept setup") - -- Try again in 3 more seconds - SCHEDULER:New(nil, function() - if spawnedGroup and spawnedGroup:IsAlive() then - TADC_Log("info", "Retrying intercept setup for " .. config.displayName) - -- TODO: Repeat the setup logic here if needed - end - end, {}, 3) - return - end - - TADC_Log("info", "Setting up AGGRESSIVE INTERCEPT mission for " .. config.displayName .. " vs " .. threat.id) - - -- Set MAXIMUM AGGRESSION AI options - local success, errorMsg = pcall(function() - spawnedGroup:OptionROEOpenFire() - spawnedGroup:OptionROTVertical() - spawnedGroup:OptionECM_Never() - spawnedGroup:OptionAAAttackRange(AI.Option.Air.val.AA_ATTACK_RANGE.MAX_RANGE) - spawnedGroup:OptionMissileAttack(AI.Option.Air.val.MISSILE_ATTACK.MAX_RANGE) - spawnedGroup:OptionFormation(AI.Option.Air.val.FORMATION.FINGER_FOUR) - spawnedGroup:OptionRTBBingoFuel() - spawnedGroup:OptionRTBAmmo(0.03) -- Stay until almost no ammo (3%) - - TADC_Log("info", "✓ Maximum aggression AI options set") - end) - - -- DIRECT THREAT VECTORING - This is the key difference! - local threatCoord = nil - if threat.coordinate then - threatCoord = threat.coordinate - elseif threat.group and threat.group:IsAlive() then - threatCoord = threat.group:GetCoordinate() - end - - if threatCoord then - TADC_Log("info", "VECTORING " .. config.displayName .. " directly to threat at " .. threatCoord:ToStringLLDMS()) - - -- Clear any existing tasks - spawnedGroup:ClearTasks() - - -- TASK 1: Direct intercept to threat location (HIGHEST PRIORITY) - local interceptCoord = threatCoord:SetAltitude(config.altitude * 0.3048) - - -- Additional safety check before routing - local interceptorCoord = spawnedGroup:GetCoordinate() - if interceptorCoord and interceptCoord then - spawnedGroup:RouteAirTo(interceptCoord, config.speed * 1.2, "BARO") -- 20% faster to intercept - else - TADC_Log("warning", "Cannot route " .. config.displayName .. " - interceptor coordinate invalid") - end - - -- TASK 2: Attack the specific threat group (Priority 1) - local attackTask = { - id = 'AttackGroup', - params = { - groupId = threat.group:GetID(), - weaponType = 'Auto', -- Use best available weapon - attackQtyLimit = 0, -- No attack limit - priority = 1 - } - } - spawnedGroup:PushTask(attackTask, 1) - - -- TASK 3: Engage all targets in area around threat (Priority 2) - local engageTask = { - id = 'EngageTargetsInZone', - params = { - targetTypes = {'Air'}, - priority = 2, - zone = { - point = {x = threatCoord.x, y = threatCoord.z}, - radius = 20000, -- 20km around threat - }, - noTargetTypes = {}, - value = 'All', - } - } - spawnedGroup:PushTask(engageTask, 2) - - -- TASK 4: Continuous threat hunting if initial target is destroyed (Priority 3) - local huntTask = { - id = 'EngageTargets', - params = { - targetTypes = {'Air'}, - priority = 3, - maxDistEnabled = true, - maxDist = GCI_Config.capEngagementRange, - attackQtyLimit = 0, - } - } - spawnedGroup:PushTask(huntTask, 3) - - TADC_Log("info", "✓ " .. config.displayName .. " vectored to intercept " .. threat.id .. " with aggressive hunter-killer tasks") - - -- Set up threat tracking updates every 30 seconds - local trackingScheduler = SCHEDULER:New(nil, function() - if spawnedGroup and spawnedGroup:IsAlive() and threat and threat.group and threat.group:IsAlive() then - local currentThreatCoord = threat.group:GetCoordinate() - if currentThreatCoord then - -- Update intercept vector to current threat position - local newInterceptCoord = currentThreatCoord:SetAltitude(config.altitude * 0.3048) - - -- Enhanced safety check before routing - local interceptorCoord = nil - local coordSuccess = pcall(function() - interceptorCoord = spawnedGroup:GetCoordinate() - end) - - if coordSuccess and interceptorCoord and newInterceptCoord then - spawnedGroup:RouteAirTo(newInterceptCoord, config.speed * 1.1, "BARO") - - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", "Updated vector: " .. config.displayName .. " → " .. threat.id) - end - else - TADC_Log("warning", "Cannot route " .. config.displayName .. " - invalid coordinates") - end - else - TADC_Log("warning", "Cannot get threat coordinate for " .. threat.id) - end - else - -- Stop tracking if threat or interceptor is dead - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "Stopping threat tracking for " .. config.displayName) - end - return false -- Stop scheduler - end - end, {}, 30, 30) -- Update every 30 seconds - - else - TADC_Log("info", "⚠ Could not get threat coordinate for vectoring") - -- Fallback to aggressive patrol - local patrolZoneCoord = config.patrolZone:GetCoordinate() - if patrolZoneCoord then - local patrolCoord = patrolZoneCoord:SetAltitude(config.altitude * 0.3048) - -- Enhanced safety check before routing - local interceptorCoord = nil - local coordSuccess = pcall(function() - interceptorCoord = spawnedGroup:GetCoordinate() - end) - - if coordSuccess and interceptorCoord then - spawnedGroup:RouteAirTo(patrolCoord, config.speed, "BARO") - TADC_Log("info", "✓ Fallback patrol routing successful for " .. config.displayName) - else - TADC_Log("warning", "Cannot route " .. config.displayName .. " to patrol - GetCoordinate failed") - end - else - TADC_Log("warning", "Cannot get patrol zone coordinate for " .. config.displayName) - end - end - end, {}, 5) -- Increased to 5 second delay to allow full group initialization - - -- Mark as active - TADC.activeCAPs[config.templateName] = { - group = spawnedGroup, - launchTime = timer.getTime(), - config = config, - isIntercept = true, -- Mark as intercept mission - targetThreat = threat - } - - -- Set up mission duration - local patrolDuration = math.max(config.patrolTime * 60, GCI_Config.minPatrolDuration) - SCHEDULER:New(nil, function() - if TADC.activeCAPs[config.templateName] then - TADC_Log("info", config.displayName .. " completing intercept mission - RTB") - local group = TADC.activeCAPs[config.templateName].group - if group and group:IsAlive() then - group:ClearTasks() - local airbaseObj = AIRBASE:FindByName(config.airbaseName) - if airbaseObj then - group:RouteRTB(airbaseObj) - end - - SCHEDULER:New(nil, function() - if TADC.activeCAPs[config.templateName] then - local rtbGroup = TADC.activeCAPs[config.templateName].group - if rtbGroup and rtbGroup:IsAlive() then - rtbGroup:Destroy() - end - TADC.activeCAPs[config.templateName] = nil - end - end, {}, GCI_Config.rtbDuration) - else - TADC.activeCAPs[config.templateName] = nil - end - end - end, {}, patrolDuration) - else - TADC_Log("info", "✗ Failed to spawn interceptor: " .. config.displayName) - end - end) - - if not success then - TADC_Log("info", "✗ Error launching intercept: " .. tostring(errorMsg)) - return false - else - TADC_Log("info", "✓ Intercept mission launched successfully") - return true - end -end - --- ================================================================================================ --- MISSION EXECUTION FUNCTION (Now can call launchCAP) --- ================================================================================================ - -local function executeThreatsAssignments(assignments) - if not assignments or #assignments == 0 then - return false - end - - local currentTime = timer.getTime() - - TADC_Log("info", "=== EXECUTING THREAT ASSIGNMENTS ===") - TADC_Log("info", "Processing " .. #assignments .. " threat assignments") - - local launchedFlights = {} - - for _, assignment in pairs(assignments) do - local squadron = assignment.squadron - local templateName = assignment.templateName - - -- Check squadron availability and cooldown - if squadron.readinessLevel == "READY" and - squadron.availableAircraft >= 1 and - (currentTime - squadron.lastLaunch) >= squadron.launchCooldown then - - local reason = "Intercept: " .. (assignment.threat and assignment.threat.id or "Unknown") .. " (" .. (assignment.threat and assignment.threat.classification or "Unknown") .. ")" - - -- Use AGGRESSIVE INTERCEPT MISSION instead of generic CAP - local success = false - if assignment.threat then - success = launchInterceptMission(assignment.config, assignment.threat, reason) - else - success = launchCAP(assignment.config, 1, reason) -- Fallback to CAP if no specific threat - end - - if success then - -- Update squadron status - squadron is now BUSY with this threat - squadron.availableAircraft = squadron.availableAircraft - 1 - squadron.airborneAircraft = squadron.airborneAircraft + 1 - squadron.lastLaunch = currentTime - squadron.sorties = squadron.sorties + 1 - -- TESTING: Don't mark squadron as BUSY - allow multiple launches - -- squadron.readinessLevel = "BUSY" -- Squadron is now handling this threat - - TADC.stats.interceptsLaunched = TADC.stats.interceptsLaunched + 1 - - -- Store mission details - TADC.missions[assignment.threatId] = { - threatId = assignment.threatId, - squadron = templateName, - threat = assignment.threat, - startTime = currentTime, - status = "ACTIVE" - } - - table.insert(launchedFlights, { - squadron = templateName, - threat = assignment.threat and assignment.threat.id or "Unknown", - launchTime = currentTime - }) - - TADC_Log("info", "✓ Launched: " .. squadron.displayName .. " → " .. (assignment.threat and assignment.threat.id or "Unknown")) - else - TADC_Log("info", "✗ Launch failed: " .. squadron.displayName) - end - else - local reason = "Unknown" - if squadron.readinessLevel ~= "READY" then - reason = "Not ready (" .. squadron.readinessLevel .. ")" - elseif squadron.availableAircraft < 1 then - reason = "Insufficient aircraft (" .. squadron.availableAircraft .. " available)" - elseif (currentTime - squadron.lastLaunch) < squadron.launchCooldown then - reason = "On cooldown (" .. math.ceil(squadron.launchCooldown - (currentTime - squadron.lastLaunch)) .. "s remaining)" - end - TADC_Log("info", "✗ Cannot launch " .. squadron.displayName .. ": " .. reason) - end - end - - -- Store mission for tracking - -- (Removed assignment to TADC.missions[missionId] due to undefined variables) - - return #launchedFlights > 0 -end - --- ================================================================================================ --- PERSISTENT CAP HELPER FUNCTIONS --- ================================================================================================ - -local function getPersistentCAPCount() - local count = 0 - for _, capData in pairs(TADC.persistentCAPs) do - if capData.group and capData.group:IsAlive() then - count = count + 1 - else - -- Clean up dead persistent CAPs - TADC.persistentCAPs[capData.templateName] = nil - end - end - return count -end - -local function launchPersistentCAP(templateName, reason) - -- Find the original squadron config - local config = nil - for _, squadronConfig in pairs(squadronConfigs) do - if squadronConfig.templateName == templateName then - config = squadronConfig - break - end - end - - if not config or not TADC.squadrons[templateName] then - return false - end - - local squadron = TADC.squadrons[templateName] - local currentTime = timer.getTime() - - -- Check if squadron is available (not on cooldown, has aircraft) - if squadron.readinessLevel ~= "READY" or - squadron.availableAircraft < 1 or - (currentTime - squadron.lastLaunch) < squadron.launchCooldown then - return false - end - - -- Launch the CAP - local success = launchCAP(config, 1, reason) - if success then - -- Track as persistent CAP (separate from regular intercept CAPs) - TADC.persistentCAPs[templateName] = { - templateName = templateName, - group = TADC.activeCAPs[templateName] and TADC.activeCAPs[templateName].group, - launchTime = currentTime, - isPersistent = true - } - - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "✓ Persistent CAP launched: " .. squadron.displayName) - end - - return true - end - - return false -end - -local function maintainPersistentCAP() - if not GCI_Config.enablePersistentCAP then - return - end - - local currentTime = timer.getTime() - - -- Only check every persistentCAPInterval seconds - if (currentTime - TADC.lastPersistentCheck) < GCI_Config.persistentCAPInterval then - return - end - - TADC.lastPersistentCheck = currentTime - - -- Calculate total airborne aircraft to respect maxSimultaneousCAP limit - local totalAirborne = 0 - for _, squadron in pairs(TADC.squadrons) do - totalAirborne = totalAirborne + squadron.airborneAircraft - end - - local currentPersistentCount = getPersistentCAPCount() - local needed = GCI_Config.persistentCAPCount - currentPersistentCount - - -- Respect maxSimultaneousCAP limit with reserve for threat response - local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve)) - local effectiveTarget = math.min(GCI_Config.persistentCAPCount, maxPersistentAllowed) - local availableSlots = maxPersistentAllowed - currentPersistentCount - - -- Recalculate needed based on effective limits - needed = math.min(needed, availableSlots) - needed = math.max(0, needed) - - if needed < (GCI_Config.persistentCAPCount - currentPersistentCount) and GCI_Config.debugLevel >= 1 then - TADC_Log("info", "⚠ Persistent CAP limited: Target=" .. GCI_Config.persistentCAPCount .. ", Effective=" .. effectiveTarget .. " (reserving " .. math.ceil(GCI_Config.maxSimultaneousCAP * GCI_Config.persistentCAPReserve) .. " slots for threats)") - end - - if needed > 0 then - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "=== PERSISTENT CAP MAINTENANCE ===") - TADC_Log("info", "Current: " .. currentPersistentCount .. ", Target: " .. GCI_Config.persistentCAPCount .. ", Need: " .. needed) - TADC_Log("info", "Total airborne: " .. totalAirborne .. "/" .. GCI_Config.maxSimultaneousCAP .. " (available slots: " .. availableSlots .. ")") - end - - -- Launch needed persistent CAPs from priority list - local launched = 0 - for _, templateName in pairs(GCI_Config.persistentCAPPriority) do - if launched >= needed then - break - end - - -- Skip if this squadron already has a persistent CAP - if not TADC.persistentCAPs[templateName] or - not TADC.persistentCAPs[templateName].group or - not TADC.persistentCAPs[templateName].group:IsAlive() then - - if launchPersistentCAP(templateName, "Persistent CAP maintenance") then - launched = launched + 1 - end - end - end - - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "✓ Persistent CAP maintenance complete: " .. launched .. " new patrols launched") - end - end -end - - - --- ================================================================================================ --- ENHANCED AI AWARENESS AND TARGET SHARING SYSTEM --- ================================================================================================ - -local function enhanceAIAwareness() - -- Update all active CAP flights with current threat information - for templateName, capData in pairs(TADC.activeCAPs) do - if capData.group and capData.group:IsAlive() then - local group = capData.group - local config = capData.config - - -- Find nearby threats to make AI more aware - local groupCoord = group:GetCoordinate() - if groupCoord then - local nearbyThreats = {} - - -- Collect threats within engagement range - for _, threat in pairs(TADC.threats) do - if threat.group and threat.group:IsAlive() then - local threatCoord = threat.coordinate or threat.group:GetCoordinate() - if threatCoord then - local distance = groupCoord:Get2DDistance(threatCoord) - local maxPursuitRange = GCI_Config.hyperAggressiveMode and GCI_Config.pursuitRange or (GCI_Config.capEngagementRange * 1.5) - if distance <= maxPursuitRange then - table.insert(nearbyThreats, { - threat = threat, - distance = distance, - coordinate = threatCoord - }) - end - end - end - end - - -- If threats are nearby, vector the aircraft towards the closest one - if #nearbyThreats > 0 then - -- Sort by distance (closest first) - table.sort(nearbyThreats, function(a, b) return a.distance < b.distance end) - - local closestThreat = nearbyThreats[1] - - -- Only update if this is a significant threat change - if not capData.lastTargetedThreat or - capData.lastTargetedThreat ~= closestThreat.threat.id or - (timer.getTime() - (capData.lastVectorUpdate or 0)) > GCI_Config.engagementUpdateInterval then - - if GCI_Config.debugLevel >= 2 then - TADC_Log("info", "Vectoring " .. config.displayName .. " to nearby threat: " .. closestThreat.threat.id .. " (" .. math.floor(closestThreat.distance/1000) .. "km)") - end - - -- Clear old tasks and add new aggressive intercept - group:ClearTasks() - - -- Route towards threat at higher speed - if closestThreat.coordinate then - local interceptCoord = closestThreat.coordinate:SetAltitude(config.altitude * 0.3048) - -- Safety check before routing - local groupCoord = group:GetCoordinate() - if groupCoord and interceptCoord then - group:RouteAirTo(interceptCoord, config.speed * 1.2, "BARO") - else - TADC_Log("warning", "Cannot route " .. config.displayName .. " - invalid coordinates for intercept") - end - else - TADC_Log("warning", "No coordinate available for threat " .. closestThreat.threat.id) - end - - -- Add aggressive attack task - local aggressiveAttack = { - id = 'AttackGroup', - params = { - groupId = closestThreat.threat.group:GetID(), - weaponType = 'Auto', - attackQtyLimit = 0, - priority = 1 - } - } - group:PushTask(aggressiveAttack, 1) - - -- Add area sweep task - local areaSweep = { - id = 'EngageTargetsInZone', - params = { - targetTypes = {'Air'}, - priority = 2, - zone = { - point = {x = closestThreat.coordinate.x, y = closestThreat.coordinate.z}, - radius = 15000, -- 15km area sweep - }, - noTargetTypes = {}, - value = 'All', - } - } - group:PushTask(areaSweep, 2) - - -- Update tracking info - capData.lastTargetedThreat = closestThreat.threat.id - capData.lastVectorUpdate = timer.getTime() - end - end - end - end - end -end - --- ================================================================================================ --- MAIN TADC CONTROL LOOP --- ================================================================================================ - --- SIMPLE GCI MAIN LOOP - Just detect threats and launch intercepts -local function simpleGCILoop() - local currentTime = timer.getTime() - - -- Count current airborne aircraft - local airborneCount = 0 - for _, squadron in pairs(TADC.squadrons) do - airborneCount = airborneCount + squadron.airborneAircraft - end - - -- Only proceed if we're under the airborne limit - if airborneCount >= GCI_Config.maxSimultaneousCAP then - TADC_Log("info", "Max aircraft limit reached (" .. airborneCount .. "/" .. GCI_Config.maxSimultaneousCAP .. ")") - return - end - - -- Detect threats using simple detection - local threats = simpleDetectThreats() - - -- For each threat, find closest airfield and launch intercept - for threatId, threat in pairs(threats) do - TADC_Log("info", "Processing threat: " .. threatId) - - -- Calculate how many defenders needed (1 to 1.5 ratio) - local defendersNeeded = math.ceil(threat.size * GCI_Config.threatRatio) - - -- Find closest available squadron - local bestSquadron = nil - local bestDistance = 999999 - - for templateName, squadron in pairs(TADC.squadrons) do - if squadron.readinessLevel == "READY" and squadron.availableAircraft >= defendersNeeded then - -- Check cooldown - local cooldown = currentTime - squadron.lastLaunch - if cooldown >= GCI_Config.squadronCooldown then - local squadronCoord = squadron.homebase and squadron.homebase:GetCoordinate() - if squadronCoord then - local distance = squadronCoord:Get2DDistance(threat.coordinate) - if distance < bestDistance then - bestDistance = distance - bestSquadron = squadron - bestSquadron.templateName = templateName - end - end - end - end - end - - -- Launch intercept if squadron found - if bestSquadron then - TADC_Log("info", "🚀 LAUNCHING: " .. bestSquadron.displayName .. " to intercept " .. threatId) - - -- Find the original squadron config for this squadron - local squadronConfig = nil - for _, config in pairs(squadronConfigs) do - if config.templateName == bestSquadron.templateName then - squadronConfig = config - break - end - end - - if squadronConfig then - launchInterceptMission(squadronConfig, threat, "GCI_INTERCEPT") - else - TADC_Log("error", "Could not find config for squadron: " .. bestSquadron.templateName) - end - else - TADC_Log("info", "⚠ No available squadrons for " .. threatId) - end - end - - -- Clean up completed missions and free squadrons - for missionId, mission in pairs(TADC.missions) do - local squadron = TADC.squadrons[mission.squadron] - if squadron then - -- Check if mission should be completed (threat destroyed, timed out, or squadron has no airborne aircraft) - local missionAge = currentTime - mission.startTime - local shouldComplete = false - - -- Mission timeout (30 minutes) - if missionAge > 1800 then - shouldComplete = true - end - - -- Squadron has returned (no airborne aircraft) - if squadron.airborneAircraft == 0 and missionAge > 300 then -- Give 5 min minimum mission time - shouldComplete = true - end - - -- Threat no longer exists - local threatExists = false - for _, threat in pairs(threats) do - if (threat.id .. "_" .. threat.firstDetected) == missionId then - threatExists = true - break - end - end - if not threatExists and missionAge > 60 then -- 1 minute grace period - shouldComplete = true - end - - if shouldComplete then - -- Free the squadron for new missions - squadron.readinessLevel = "READY" - TADC.squadronMissions[mission.squadron] = nil - TADC.threatAssignments[missionId] = nil - TADC.missions[missionId] = nil - - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "Mission completed: " .. mission.squadron .. " freed from " .. (mission.threat and mission.threat.id or "unknown threat")) - end - end - else - -- Squadron doesn't exist anymore, clean up - TADC.missions[missionId] = nil - TADC.threatAssignments[missionId] = nil - end - end - - -- Periodic status report - if GCI_Config.debugLevel >= 1 and (currentTime % GCI_Config.statusReportInterval) < 30 then -- Status reports from config - local totalAirborne = 0 - local totalAvailable = 0 - for _, squadron in pairs(TADC.squadrons) do - totalAirborne = totalAirborne + squadron.airborneAircraft - totalAvailable = totalAvailable + squadron.availableAircraft - end - - TADC_Log("info", "=== TADC STATUS REPORT ===") - TADC_Log("info", "Threats: " .. threatCount .. " active") - TADC_Log("info", "Aircraft: " .. totalAirborne .. " airborne, " .. totalAvailable .. " available") - TADC_Log("info", "Statistics: " .. TADC.stats.threatsDetected .. " threats detected, " .. TADC.stats.interceptsLaunched .. " intercepts launched") - - -- Persistent CAP Status - if GCI_Config.enablePersistentCAP then - local persistentCount = getPersistentCAPCount() - local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve)) - local threatReserve = GCI_Config.maxSimultaneousCAP - maxPersistentAllowed - TADC_Log("info", "Persistent CAP: " .. persistentCount .. "/" .. GCI_Config.persistentCAPCount .. " target (" .. maxPersistentAllowed .. " max, " .. threatReserve .. " reserved for threats)") - end - end - - -- Enhanced AI Awareness and Target Sharing - enhanceAIAwareness() - - -- Persistent CAP Management - if GCI_Config.enablePersistentCAP then - maintainPersistentCAP() - end -end - --- ================================================================================================ --- MAIN TADC LOOP FUNCTION (COMPREHENSIVE VERSION) --- ================================================================================================ - -local function mainTADCLoop() - local currentTime = timer.getTime() - local startTime = currentTime - - -- Performance tracking - TADC.performance.loopCount = TADC.performance.loopCount + 1 - - -- Update system statistics - TADC.stats.systemLoadTime = currentTime - - -- 1. IMMEDIATE RESPONSE CHECK (like RU_INTERCEPT backup) - immediateInterceptCheck() - - -- 2. COMPREHENSIVE THREAT DETECTION - local threats = simpleDetectThreats() - local threatCount = 0 - for _ in pairs(threats) do threatCount = threatCount + 1 end - - if threatCount > 0 then - TADC_Log("info", "🎯 MAIN LOOP: Detected " .. threatCount .. " active threats") - TADC.stats.threatsDetected = TADC.stats.threatsDetected + threatCount - - -- Debug: Show which threats were detected - for threatId, threat in pairs(threats) do - TADC_Log("info", " Threat: " .. threatId .. " (" .. threat.classification .. ") in " .. threat.zone) - end - else - TADC_Log("info", "🎯 MAIN LOOP: No threats detected") - end - - -- 3. PROCESS THREATS BY ZONE - local zones = {"RED_BORDER", "HELO_BORDER"} - - for _, zone in pairs(zones) do - -- Get threats in this zone - local zoneThreats = {} - for threatId, threat in pairs(threats) do - if threat.zone == zone then - table.insert(zoneThreats, threat) - end - end - - if #zoneThreats > 0 then - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "Processing " .. #zoneThreats .. " threats in " .. zone) - end - - -- Assign threats to squadrons using smart algorithm - local assignments = assignThreatsToSquadrons(zoneThreats, zone) - - -- Execute the assignments - if #assignments > 0 then - TADC_Log("info", "📋 EXECUTING " .. #assignments .. " ASSIGNMENTS FOR " .. zone) - TADC.stats.interceptsLaunched = TADC.stats.interceptsLaunched + #assignments - executeThreatsAssignments(assignments) - else - TADC_Log("warning", "⚠ NO ASSIGNMENTS GENERATED for " .. #zoneThreats .. " threats in " .. zone) - -- Debug squadron availability - local readySquadrons = 0 - local totalSquadrons = 0 - for templateName, squadron in pairs(TADC.squadrons) do - totalSquadrons = totalSquadrons + 1 - if squadron.readinessLevel == "READY" and squadron.availableAircraft >= 1 then - readySquadrons = readySquadrons + 1 - TADC_Log("info", "✓ READY: " .. squadron.displayName .. " (" .. squadron.availableAircraft .. " aircraft)") - else - TADC_Log("info", "✗ NOT READY: " .. squadron.displayName .. " - " .. squadron.readinessLevel .. " (" .. squadron.availableAircraft .. " aircraft)") - end - end - TADC_Log("info", "Squadron Status: " .. readySquadrons .. "/" .. totalSquadrons .. " ready") - end - end - end - - -- 4. SQUADRON STATUS MANAGEMENT - -- Clean up completed missions and free squadrons - for missionId, mission in pairs(TADC.missions) do - local squadron = TADC.squadrons[mission.squadron] - if squadron then - local missionAge = currentTime - mission.startTime - local shouldComplete = false - - -- Mission completion conditions - if missionAge > 1800 then -- 30 minutes timeout - shouldComplete = true - elseif squadron.airborneAircraft == 0 and missionAge > 300 then -- 5 min minimum - shouldComplete = true - end - - -- Check if threat still exists - local threatExists = false - for _, threat in pairs(threats) do - if (threat.id .. "_" .. threat.firstDetected) == missionId then - threatExists = true - break - end - end - if not threatExists and missionAge > 60 then - shouldComplete = true - end - - if shouldComplete then - squadron.readinessLevel = "READY" - TADC.squadronMissions[mission.squadron] = nil - TADC.threatAssignments[missionId] = nil - TADC.missions[missionId] = nil - - if GCI_Config.debugLevel >= 1 then - TADC_Log("info", "Mission completed: " .. mission.squadron .. " freed") - end - end - else - TADC.missions[missionId] = nil - TADC.threatAssignments[missionId] = nil - end - end - - -- 5. ENHANCED AI AWARENESS AND TARGET SHARING - enhanceAIAwareness() - - -- 6. PERSISTENT CAP MANAGEMENT - if GCI_Config.enablePersistentCAP then - maintainPersistentCAP() - end - - -- 7. PERIODIC STATUS REPORTING - if GCI_Config.debugLevel >= 1 and (currentTime % GCI_Config.statusReportInterval) < GCI_Config.mainLoopInterval then - local totalAirborne = 0 - local totalAvailable = 0 - for _, squadron in pairs(TADC.squadrons) do - totalAirborne = totalAirborne + squadron.airborneAircraft - totalAvailable = totalAvailable + squadron.availableAircraft - end - - TADC_Log("info", "=== TADC STATUS REPORT ===") - TADC_Log("info", "Threats: " .. threatCount .. " active") - TADC_Log("info", "Aircraft: " .. totalAirborne .. " airborne, " .. totalAvailable .. " available") - TADC_Log("info", "Statistics: " .. TADC.stats.threatsDetected .. " threats detected, " .. TADC.stats.interceptsLaunched .. " intercepts launched") - - if GCI_Config.enablePersistentCAP then - local persistentCount = getPersistentCAPCount() - TADC_Log("info", "Persistent CAP: " .. persistentCount .. "/" .. GCI_Config.persistentCAPCount .. " target") - end - end - - -- 8. PERFORMANCE TRACKING - local loopTime = timer.getTime() - startTime - TADC.performance.lastLoopTime = loopTime - TADC.performance.avgLoopTime = (TADC.performance.avgLoopTime * (TADC.performance.loopCount - 1) + loopTime) / TADC.performance.loopCount - TADC.performance.maxLoopTime = math.max(TADC.performance.maxLoopTime, loopTime) - - if loopTime > 1.0 and GCI_Config.debugLevel >= 1 then - TADC_Log("warning", "TADC loop took " .. string.format("%.2f", loopTime) .. "s (performance warning)") - end -end - --- ================================================================================================ --- PERSISTENT CAP MANAGEMENT SYSTEM --- ================================================================================================ - -local function setupTADC() - TADC_Log("info", "=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===") - - -- Validate configuration before starting - if not validateConfiguration() then - TADC_Log("info", "✗ TADC configuration validation failed") - return - end - - TADC_Log("info", "✓ Configuration loaded and validated:") - TADC_Log("info", " - Threat Ratio: " .. GCI_Config.threatRatio .. ":1") - TADC_Log("info", " - Max Simultaneous CAP: " .. GCI_Config.maxSimultaneousCAP) - TADC_Log("info", " - Reserve Percentage: " .. (GCI_Config.reservePercent * 100) .. "%") - TADC_Log("info", " - Supply Mode: " .. GCI_Config.supplyMode) - TADC_Log("info", " - Response Delay: " .. GCI_Config.responseDelay .. " seconds") - - -- Persistent CAP Configuration - if GCI_Config.enablePersistentCAP then - local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve)) - local threatReserve = math.ceil(GCI_Config.maxSimultaneousCAP * GCI_Config.persistentCAPReserve) - TADC_Log("info", " - Persistent CAP: ENABLED (" .. GCI_Config.persistentCAPCount .. " target, " .. maxPersistentAllowed .. " max allowed)") - TADC_Log("info", " - Threat Response Reserve: " .. threatReserve .. " aircraft slots") - TADC_Log("info", " - Persistent CAP Check Interval: " .. GCI_Config.persistentCAPInterval .. " seconds") - else - TADC_Log("info", " - Persistent CAP: DISABLED") - end - - -- CAP Behavior Configuration - TADC_Log("info", " - CAP Orbit Radius: " .. (GCI_Config.capOrbitRadius / 1000) .. "km") - TADC_Log("info", " - CAP Engagement Range: " .. (GCI_Config.capEngagementRange / 1000) .. "km") - TADC_Log("info", " - Zone Constraint: " .. (GCI_Config.capZoneConstraint and "ENABLED" or "DISABLED")) - - -- Start main control loop - SCHEDULER:New(nil, mainTADCLoop, {}, GCI_Config.mainLoopDelay, GCI_Config.mainLoopInterval) -- Main loop timing from config - - TADC_Log("info", "✓ TADC main control loop started") - TADC_Log("info", "✓ Tactical Air Defense Controller operational!") -end - --- Initialize the TADC system -SCHEDULER:New(nil, function() - setupTADC() - - -- Launch initial persistent CAP flights if enabled - if GCI_Config.enablePersistentCAP then - TADC_Log("info", "=== LAUNCHING INITIAL PERSISTENT CAP ===") - TADC.lastPersistentCheck = 0 -- Force immediate check - maintainPersistentCAP() - - -- Schedule another check in 30 seconds to ensure CAP gets airborne - SCHEDULER:New(nil, function() - TADC_Log("info", "=== PERSISTENT CAP FOLLOW-UP CHECK ===") - TADC.lastPersistentCheck = 0 -- Force another immediate check - maintainPersistentCAP() - end, {}, 30) - end - - -- Legacy: Optional initial standing patrols (if configured) - if GCI_Config.initialStandingPatrols then - local initialPatrols = { - "FIGHTER_SWEEP_RED_Severomorsk-1", -- Main base always has standing patrol - "HELO_SWEEP_RED_Afrikanda" -- Helo patrol coverage - } - - for _, templateName in pairs(initialPatrols) do - if TADC.squadrons[templateName] then - -- Find the original squadron config - local config = nil - for _, squadronConfig in pairs(squadronConfigs) do - if squadronConfig.templateName == templateName then - config = squadronConfig - break - end - end - - if config then - launchCAP(config, 1, "Initial standing patrol") - end - end - end - end - - -- Initialize strategic targets for smart prioritization - initializeStrategicTargets() - - TADC_Log("info", "=== TADC INITIALIZATION COMPLETE ===") - TADC_Log("info", "✓ Smart threat prioritization system with multi-factor analysis") - TADC_Log("info", "✓ Predictive threat assessment and response") - TADC_Log("info", "✓ Intelligent threat assessment and response") - TADC_Log("info", "✓ Multi-squadron coordinated intercepts") - TADC_Log("info", "✓ Dynamic force sizing based on threat strength") - TADC_Log("info", "✓ Resource management with reserve forces") - TADC_Log("info", "✓ EWR network integration with " .. (RedEWR:Count()) .. " detection groups") - TADC_Log("info", "✓ Strategic target protection with distance-based prioritization") - TADC_Log("info", "✓ Enhanced squadron-threat matching algorithm") - TADC_Log("info", "✓ Tactical Air Defense Controller operational!") - -end, {}, 5) - --- ================================================================================================ --- SYSTEM STARTUP AND INITIALIZATION --- ================================================================================================ - --- Initialize strategic target coordinates -initializeStrategicTargets() - --- Start the TADC system with proper delays -SCHEDULER:New(nil, function() - TADC_Log("info", "=== STARTING TADC SYSTEM ===") - setupTADC() - - -- Launch initial standing patrols if configured - if GCI_Config.initialStandingPatrols then - TADC_Log("info", "Launching initial standing patrols...") - - -- Wait a bit more then launch initial CAPs - SCHEDULER:New(nil, function() - local launched = 0 - local maxInitialCAP = math.min(2, GCI_Config.persistentCAPCount) -- Start with 2 max - - for _, templateName in pairs(GCI_Config.persistentCAPPriority) do - if launched >= maxInitialCAP then break end - - if launchPersistentCAP(templateName, "Initial standing patrol") then - launched = launched + 1 - -- Stagger launches by 30 seconds - if launched < maxInitialCAP then - SCHEDULER:New(nil, function() end, {}, 30) - end - end - end - - if launched > 0 then - TADC_Log("info", "✓ Initial standing patrols launched: " .. launched .. " flights") - else - TADC_Log("info", "⚠ Could not launch initial standing patrols - will retry during maintenance cycle") - end - end, {}, GCI_Config.capSetupDelay + 15) -- Extra delay for initial patrols - end - - -- Start the main TADC control loop - SCHEDULER:New(nil, function() - TADC_Log("info", "✓ TADC Main Control Loop starting...") - - -- Schedule the main loop to run every mainLoopInterval seconds - SCHEDULER:New(nil, mainTADCLoop, {}, 0, GCI_Config.mainLoopInterval) - - TADC_Log("info", "✓ TADC System fully operational!") - TADC_Log("info", "✓ Monitoring for threats every " .. GCI_Config.mainLoopInterval .. " seconds") - - end, {}, GCI_Config.mainLoopDelay + GCI_Config.capSetupDelay) - -end, {}, 3) -- Small initial delay to let MOOSE fully initialize diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua index 4e8e1ee..aea608f 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua @@ -404,8 +404,8 @@ local function dispatchCargo(squadron, coalitionKey) local destCoalition = destAirbase:GetCoalition() if destCoalition ~= coalitionSide then - log("WARNING: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(destCoalition) .. "). This will cause RAT to fail with 'Airbase doesn't exist' error. Skipping dispatch.") - announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (airbase captured by enemy)!") + 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 @@ -423,10 +423,8 @@ local function dispatchCargo(squadron, coalitionKey) local originCoalition = originAirbase:GetCoalition() if originCoalition ~= coalitionSide then - log("WARNING: Origin airbase '" .. origin .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(originCoalition) .. "). This will cause RAT to fail. Skipping dispatch.") - announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (origin airbase captured by enemy)!") - -- Mark mission as failed and cleanup immediately - mission.status = "failed" + 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 @@ -581,18 +579,24 @@ end -------------------------------------------------------------------------- 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 - 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) + -- 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 diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua index 6a29065..3f903b6 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua @@ -319,11 +319,15 @@ end -- Squadron resource summary generator local function getSquadronResourceSummary(coalitionSide) - local function getStatus(remaining, max) + local function getStatus(remaining, max, state) + if state == "captured" then return "[CAPTURED]" end + if state == "destroyed" then return "[DESTROYED]" end + if state ~= "operational" then return "[OFFLINE]" end + local percent = (remaining / max) * 100 - if percent <= 10 then return "[CRITICAL]" end - if percent <= 25 then return "[LOW]" end - return "OK" + if percent <= 10 then return "[CRITICAL]" end + if percent <= 25 then return "[LOW]" end + return "OK" end local lines = {} @@ -336,19 +340,21 @@ local function getSquadronResourceSummary(coalitionSide) for _, squadron in pairs(RED_SQUADRON_CONFIG) do local remaining = squadronAircraftCounts.red[squadron.templateName] or 0 local max = squadron.aircraft or 0 - local status = getStatus(remaining, max) + local state = squadron.state or "operational" + local status = getStatus(remaining, max, state) table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status)) end elseif coalitionSide == coalition.side.BLUE then for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do local remaining = squadronAircraftCounts.blue[squadron.templateName] or 0 local max = squadron.aircraft or 0 - local status = getStatus(remaining, max) + local state = squadron.state or "operational" + local status = getStatus(remaining, max, state) table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status)) end end - table.insert(lines, "\n- [LOW]: Below 25%\n- [CRITICAL]: Below 10%\n- OK: Above 25%") + table.insert(lines, "\n- [CAPTURED]: Airbase captured by enemy\n- [LOW]: Below 25%\n- [CRITICAL]: Below 10%\n- OK: Above 25%") return table.concat(lines, "\n") end @@ -1404,8 +1410,20 @@ local function findBestSquadron(threatCoord, threatSize, coalitionSide) local squadronAvailable = true local unavailableReason = "" + -- Check squadron state first + if squadron.state and squadron.state ~= "operational" then + squadronAvailable = false + if squadron.state == "captured" then + unavailableReason = "airbase captured by enemy" + elseif squadron.state == "destroyed" then + unavailableReason = "airbase destroyed" + else + unavailableReason = "squadron not operational (state: " .. tostring(squadron.state) .. ")" + end + end + -- Check cooldown - if squadronCooldowns[coalitionKey][squadron.templateName] then + if squadronAvailable and squadronCooldowns[coalitionKey][squadron.templateName] then local cooldownEnd = squadronCooldowns[coalitionKey][squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1808,12 +1826,36 @@ local function checkAirbaseStatus() if TADC_SETTINGS.enableRed then log("=== RED COALITION STATUS ===") for _, squadron in pairs(RED_SQUADRON_CONFIG) do - local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED) - - -- Add aircraft count to status + local airbase = AIRBASE:FindByName(squadron.airbaseName) local aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0 local maxAircraft = squadron.aircraft - local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Determine status based on squadron state + local statusPrefix = "✗" + local statusText = "" + local usable = false + + if squadron.state == "operational" then + statusPrefix = "✓" + statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + usable = true + elseif squadron.state == "captured" then + -- Determine who captured it + local capturedBy = "enemy" + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.BLUE then + capturedBy = "Blue" + elseif airbaseCoalition == coalition.side.NEUTRAL then + capturedBy = "neutral forces" + end + end + statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + elseif squadron.state == "destroyed" then + statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + else + statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + end -- Add zone information if configured local zoneStatus = "" @@ -1825,9 +1867,9 @@ local function checkAirbaseStatus() zoneStatus = " Zones: " .. table.concat(zones, " ") end - -- Check if squadron is on cooldown + -- Check if squadron is on cooldown (only show for operational squadrons) local cooldownStatus = "" - if squadronCooldowns.red[squadron.templateName] then + if squadron.state == "operational" and squadronCooldowns.red[squadron.templateName] then local cooldownEnd = squadronCooldowns.red[squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1835,14 +1877,13 @@ local function checkAirbaseStatus() end end - local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + local fullStatus = statusText .. zoneStatus .. cooldownStatus if usable and cooldownStatus == "" and aircraftCount > 0 then redUsableCount = redUsableCount + 1 - log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) - else - log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) end + + log(statusPrefix .. " " .. squadron.airbaseName .. " - " .. fullStatus) end log("RED Status: " .. redUsableCount .. "/" .. #RED_SQUADRON_CONFIG .. " airbases operational") end @@ -1851,12 +1892,36 @@ local function checkAirbaseStatus() if TADC_SETTINGS.enableBlue then log("=== BLUE COALITION STATUS ===") for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do - local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE) - - -- Add aircraft count to status + local airbase = AIRBASE:FindByName(squadron.airbaseName) local aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0 local maxAircraft = squadron.aircraft - local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Determine status based on squadron state + local statusPrefix = "✗" + local statusText = "" + local usable = false + + if squadron.state == "operational" then + statusPrefix = "✓" + statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + usable = true + elseif squadron.state == "captured" then + -- Determine who captured it + local capturedBy = "enemy" + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.RED then + capturedBy = "Red" + elseif airbaseCoalition == coalition.side.NEUTRAL then + capturedBy = "neutral forces" + end + end + statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + elseif squadron.state == "destroyed" then + statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + else + statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + end -- Add zone information if configured local zoneStatus = "" @@ -1868,9 +1933,9 @@ local function checkAirbaseStatus() zoneStatus = " Zones: " .. table.concat(zones, " ") end - -- Check if squadron is on cooldown + -- Check if squadron is on cooldown (only show for operational squadrons) local cooldownStatus = "" - if squadronCooldowns.blue[squadron.templateName] then + if squadron.state == "operational" and squadronCooldowns.blue[squadron.templateName] then local cooldownEnd = squadronCooldowns.blue[squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1878,14 +1943,14 @@ local function checkAirbaseStatus() end end - local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + local fullStatus = statusText .. zoneStatus .. cooldownStatus if usable and cooldownStatus == "" and aircraftCount > 0 then blueUsableCount = blueUsableCount + 1 - log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) - else - log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) end + + log(statusPrefix .. " " .. squadron.airbaseName .. " - " .. fullStatus) + end end log("BLUE Status: " .. blueUsableCount .. "/" .. #BLUE_SQUADRON_CONFIG .. " airbases operational") end @@ -1911,6 +1976,63 @@ local function cleanupOldDeliveries() end end +-- Update squadron states based on airbase coalition control +local function updateSquadronStates() + -- Update RED squadrons + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.RED then + -- Only update to operational if not already operational (avoid spam) + if squadron.state ~= "operational" then + squadron.state = "operational" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational") + end + else + -- Airbase captured + if squadron.state ~= "captured" then + squadron.state = "captured" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy") + end + end + else + -- Airbase destroyed or not found + if squadron.state ~= "destroyed" then + squadron.state = "destroyed" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found") + end + end + end + + -- Update BLUE squadrons + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.BLUE then + -- Only update to operational if not already operational (avoid spam) + if squadron.state ~= "operational" then + squadron.state = "operational" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational") + end + else + -- Airbase captured + if squadron.state ~= "captured" then + squadron.state = "captured" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy") + end + end + else + -- Airbase destroyed or not found + if squadron.state ~= "destroyed" then + squadron.state = "destroyed" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found") + end + end + end +end + -- System initialization local function initializeSystem() log("Universal Dual-Coalition TADC starting...") @@ -1962,6 +2084,15 @@ local function initializeSystem() return false end + -- Initialize squadron states + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + squadron.state = "operational" + end + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + squadron.state = "operational" + end + log("Squadron states initialized") + -- Log enabled coalitions local enabledCoalitions = {} if TADC_SETTINGS.enableRed then @@ -2037,6 +2168,7 @@ local function initializeSystem() 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, updateSquadronStates, {}, 15, 30) -- Update squadron states every 30 seconds SCHEDULER:New(nil, cleanupOldDeliveries, {}, 60, 3600) -- Cleanup old delivery records every hour -- Start periodic squadron summary broadcast diff --git a/Moose_TADC/Moose_TADC_CargoDispatcher.lua b/Moose_TADC/Moose_TADC_CargoDispatcher.lua index d6b374a..e2577a8 100644 --- a/Moose_TADC/Moose_TADC_CargoDispatcher.lua +++ b/Moose_TADC/Moose_TADC_CargoDispatcher.lua @@ -404,8 +404,8 @@ local function dispatchCargo(squadron, coalitionKey) local destCoalition = destAirbase:GetCoalition() if destCoalition ~= coalitionSide then - log("WARNING: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(destCoalition) .. "). This will cause RAT to fail with 'Airbase doesn't exist' error. Skipping dispatch.") - announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (airbase captured by enemy)!") + 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 @@ -423,10 +423,8 @@ local function dispatchCargo(squadron, coalitionKey) local originCoalition = originAirbase:GetCoalition() if originCoalition ~= coalitionSide then - log("WARNING: Origin airbase '" .. origin .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(originCoalition) .. "). This will cause RAT to fail. Skipping dispatch.") - announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (origin airbase captured by enemy)!") - -- Mark mission as failed and cleanup immediately - mission.status = "failed" + 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 @@ -581,18 +579,24 @@ end -------------------------------------------------------------------------- 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 - 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) + -- 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 diff --git a/Moose_TADC/Moose_TADC_Load2nd.lua b/Moose_TADC/Moose_TADC_Load2nd.lua index 6a29065..3f903b6 100644 --- a/Moose_TADC/Moose_TADC_Load2nd.lua +++ b/Moose_TADC/Moose_TADC_Load2nd.lua @@ -319,11 +319,15 @@ end -- Squadron resource summary generator local function getSquadronResourceSummary(coalitionSide) - local function getStatus(remaining, max) + local function getStatus(remaining, max, state) + if state == "captured" then return "[CAPTURED]" end + if state == "destroyed" then return "[DESTROYED]" end + if state ~= "operational" then return "[OFFLINE]" end + local percent = (remaining / max) * 100 - if percent <= 10 then return "[CRITICAL]" end - if percent <= 25 then return "[LOW]" end - return "OK" + if percent <= 10 then return "[CRITICAL]" end + if percent <= 25 then return "[LOW]" end + return "OK" end local lines = {} @@ -336,19 +340,21 @@ local function getSquadronResourceSummary(coalitionSide) for _, squadron in pairs(RED_SQUADRON_CONFIG) do local remaining = squadronAircraftCounts.red[squadron.templateName] or 0 local max = squadron.aircraft or 0 - local status = getStatus(remaining, max) + local state = squadron.state or "operational" + local status = getStatus(remaining, max, state) table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status)) end elseif coalitionSide == coalition.side.BLUE then for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do local remaining = squadronAircraftCounts.blue[squadron.templateName] or 0 local max = squadron.aircraft or 0 - local status = getStatus(remaining, max) + local state = squadron.state or "operational" + local status = getStatus(remaining, max, state) table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status)) end end - table.insert(lines, "\n- [LOW]: Below 25%\n- [CRITICAL]: Below 10%\n- OK: Above 25%") + table.insert(lines, "\n- [CAPTURED]: Airbase captured by enemy\n- [LOW]: Below 25%\n- [CRITICAL]: Below 10%\n- OK: Above 25%") return table.concat(lines, "\n") end @@ -1404,8 +1410,20 @@ local function findBestSquadron(threatCoord, threatSize, coalitionSide) local squadronAvailable = true local unavailableReason = "" + -- Check squadron state first + if squadron.state and squadron.state ~= "operational" then + squadronAvailable = false + if squadron.state == "captured" then + unavailableReason = "airbase captured by enemy" + elseif squadron.state == "destroyed" then + unavailableReason = "airbase destroyed" + else + unavailableReason = "squadron not operational (state: " .. tostring(squadron.state) .. ")" + end + end + -- Check cooldown - if squadronCooldowns[coalitionKey][squadron.templateName] then + if squadronAvailable and squadronCooldowns[coalitionKey][squadron.templateName] then local cooldownEnd = squadronCooldowns[coalitionKey][squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1808,12 +1826,36 @@ local function checkAirbaseStatus() if TADC_SETTINGS.enableRed then log("=== RED COALITION STATUS ===") for _, squadron in pairs(RED_SQUADRON_CONFIG) do - local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED) - - -- Add aircraft count to status + local airbase = AIRBASE:FindByName(squadron.airbaseName) local aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0 local maxAircraft = squadron.aircraft - local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Determine status based on squadron state + local statusPrefix = "✗" + local statusText = "" + local usable = false + + if squadron.state == "operational" then + statusPrefix = "✓" + statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + usable = true + elseif squadron.state == "captured" then + -- Determine who captured it + local capturedBy = "enemy" + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.BLUE then + capturedBy = "Blue" + elseif airbaseCoalition == coalition.side.NEUTRAL then + capturedBy = "neutral forces" + end + end + statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + elseif squadron.state == "destroyed" then + statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + else + statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + end -- Add zone information if configured local zoneStatus = "" @@ -1825,9 +1867,9 @@ local function checkAirbaseStatus() zoneStatus = " Zones: " .. table.concat(zones, " ") end - -- Check if squadron is on cooldown + -- Check if squadron is on cooldown (only show for operational squadrons) local cooldownStatus = "" - if squadronCooldowns.red[squadron.templateName] then + if squadron.state == "operational" and squadronCooldowns.red[squadron.templateName] then local cooldownEnd = squadronCooldowns.red[squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1835,14 +1877,13 @@ local function checkAirbaseStatus() end end - local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + local fullStatus = statusText .. zoneStatus .. cooldownStatus if usable and cooldownStatus == "" and aircraftCount > 0 then redUsableCount = redUsableCount + 1 - log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) - else - log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) end + + log(statusPrefix .. " " .. squadron.airbaseName .. " - " .. fullStatus) end log("RED Status: " .. redUsableCount .. "/" .. #RED_SQUADRON_CONFIG .. " airbases operational") end @@ -1851,12 +1892,36 @@ local function checkAirbaseStatus() if TADC_SETTINGS.enableBlue then log("=== BLUE COALITION STATUS ===") for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do - local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE) - - -- Add aircraft count to status + local airbase = AIRBASE:FindByName(squadron.airbaseName) local aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0 local maxAircraft = squadron.aircraft - local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Determine status based on squadron state + local statusPrefix = "✗" + local statusText = "" + local usable = false + + if squadron.state == "operational" then + statusPrefix = "✓" + statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + usable = true + elseif squadron.state == "captured" then + -- Determine who captured it + local capturedBy = "enemy" + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.RED then + capturedBy = "Red" + elseif airbaseCoalition == coalition.side.NEUTRAL then + capturedBy = "neutral forces" + end + end + statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + elseif squadron.state == "destroyed" then + statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + else + statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft" + end -- Add zone information if configured local zoneStatus = "" @@ -1868,9 +1933,9 @@ local function checkAirbaseStatus() zoneStatus = " Zones: " .. table.concat(zones, " ") end - -- Check if squadron is on cooldown + -- Check if squadron is on cooldown (only show for operational squadrons) local cooldownStatus = "" - if squadronCooldowns.blue[squadron.templateName] then + if squadron.state == "operational" and squadronCooldowns.blue[squadron.templateName] then local cooldownEnd = squadronCooldowns.blue[squadron.templateName] if currentTime < cooldownEnd then local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) @@ -1878,14 +1943,14 @@ local function checkAirbaseStatus() end end - local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + local fullStatus = statusText .. zoneStatus .. cooldownStatus if usable and cooldownStatus == "" and aircraftCount > 0 then blueUsableCount = blueUsableCount + 1 - log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) - else - log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) end + + log(statusPrefix .. " " .. squadron.airbaseName .. " - " .. fullStatus) + end end log("BLUE Status: " .. blueUsableCount .. "/" .. #BLUE_SQUADRON_CONFIG .. " airbases operational") end @@ -1911,6 +1976,63 @@ local function cleanupOldDeliveries() end end +-- Update squadron states based on airbase coalition control +local function updateSquadronStates() + -- Update RED squadrons + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.RED then + -- Only update to operational if not already operational (avoid spam) + if squadron.state ~= "operational" then + squadron.state = "operational" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational") + end + else + -- Airbase captured + if squadron.state ~= "captured" then + squadron.state = "captured" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy") + end + end + else + -- Airbase destroyed or not found + if squadron.state ~= "destroyed" then + squadron.state = "destroyed" + log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found") + end + end + end + + -- Update BLUE squadrons + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:IsAlive() then + local airbaseCoalition = airbase:GetCoalition() + if airbaseCoalition == coalition.side.BLUE then + -- Only update to operational if not already operational (avoid spam) + if squadron.state ~= "operational" then + squadron.state = "operational" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational") + end + else + -- Airbase captured + if squadron.state ~= "captured" then + squadron.state = "captured" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy") + end + end + else + -- Airbase destroyed or not found + if squadron.state ~= "destroyed" then + squadron.state = "destroyed" + log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found") + end + end + end +end + -- System initialization local function initializeSystem() log("Universal Dual-Coalition TADC starting...") @@ -1962,6 +2084,15 @@ local function initializeSystem() return false end + -- Initialize squadron states + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + squadron.state = "operational" + end + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + squadron.state = "operational" + end + log("Squadron states initialized") + -- Log enabled coalitions local enabledCoalitions = {} if TADC_SETTINGS.enableRed then @@ -2037,6 +2168,7 @@ local function initializeSystem() 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, updateSquadronStates, {}, 15, 30) -- Update squadron states every 30 seconds SCHEDULER:New(nil, cleanupOldDeliveries, {}, 60, 3600) -- Cleanup old delivery records every hour -- Start periodic squadron summary broadcast diff --git a/Moose_TADC/TADC_Example.miz b/Moose_TADC/TADC_Example.miz index 73dbd3a..5790ba7 100644 Binary files a/Moose_TADC/TADC_Example.miz and b/Moose_TADC/TADC_Example.miz differ