From 84e2e9ffaeb27aa9240a7e6bcd7bc6f231fdbb8c Mon Sep 17 00:00:00 2001 From: iTracerFacer <134304944+iTracerFacer@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:34:37 -0500 Subject: [PATCH] Added config validation messages to the log and game for missing squadron file loaded. --- Moose_TADC/Moose_TADC.lua | 1668 +++++++++++++++++++++++++++++ Moose_TADC/Moose_TADC_Load2nd.lua | 30 +- Moose_TADC/TADC_Example.miz | Bin 823621 -> 823695 bytes 3 files changed, 1689 insertions(+), 9 deletions(-) create mode 100644 Moose_TADC/Moose_TADC.lua diff --git a/Moose_TADC/Moose_TADC.lua b/Moose_TADC/Moose_TADC.lua new file mode 100644 index 0000000..78a7d10 --- /dev/null +++ b/Moose_TADC/Moose_TADC.lua @@ -0,0 +1,1668 @@ +--[[ +═══════════════════════════════════════════════════════════════════════════════ + UNIVERSAL TADC + Dual-Coalition Tactical Air Defense Controller + Advanced Zone-Based System +═══════════════════════════════════════════════════════════════════════════════ + +DESCRIPTION: +This script provides a sophisticated automated air defense system for BOTH RED and +BLUE coalitions operating independently. Features advanced zone-based area of +responsibility (AOR) management, allowing squadrons to respond differently based +on threat location and priority levels. Perfect for complex scenarios requiring +realistic air defense behavior and tactical depth. + +CORE FEATURES: +• Dual-coalition support with completely independent operation +• Advanced zone-based area of responsibility system (Primary/Secondary/Tertiary) +• Automatic threat detection with intelligent interceptor allocation +• Multi-squadron management with individual cooldowns and aircraft tracking +• Dynamic cargo aircraft replenishment system +• Configurable intercept ratios with zone-specific response modifiers +• Smart interceptor routing, engagement, and RTB (Return to Base) behavior +• Real-time airbase status monitoring (operational/captured/destroyed) +• Comprehensive configuration validation and error reporting +• Asymmetric warfare support with coalition-specific capabilities +• Emergency cleanup systems and safety nets for mission stability + +ADVANCED ZONE SYSTEM: +Each squadron can be configured with up to three zone types: +• PRIMARY ZONE: Main area of responsibility (full response ratio) +• SECONDARY ZONE: Support area (reduced response, optional low-priority filtering) +• TERTIARY ZONE: Emergency/fallback area (enhanced response when base threatened) +• Squadrons will respond based on threat location relative to their zones +• Zone-specific response modifiers can be configured for each squadron +• Zones may overlap between squadrons for layered defense. + +ADVANCED ZONE SETUP: +• Create zones in the mission editor (MOOSE polygons, circles, etc.) +• Assign zone names to squadrons in the configuration (exact match required) +• Leave zones as nil for global threat response (no zone restrictions) +• Each zone is defined by placing a helicopter group with waypoints outlining the area +• The script will create polygon zones from the helicopter waypoints automatically + +Zone response behaviors include: +• Distance-based engagement limits (max range from airbase) +• Priority thresholds for threat classification (major vs minor threats) +• Fallback conditions (auto-switch to tertiary when squadron weakened) +• Response ratio multipliers per zone type +• Low-priority threat filtering in secondary zones + +REPLENISHMENT SYSTEM: +• Automated cargo aircraft detection system that monitors for transport aircraft + landings to replenish squadron aircraft counts (fixed wing only): +• Detects cargo aircraft by name patterns (CARGO, TRANSPORT, C130, C-130, AN26, AN-26) +• Monitors landing status based on velocity and proximity to friendly airbases +• Replenishes squadron aircraft up to maximum capacity per airbase +• Prevents duplicate processing of the same cargo delivery +• Coalition-specific replenishment amounts configurable independently +• Supports sustained operations over extended mission duration + +*** This system does not spawn or manage cargo aircraft - it only detects when +your existing cargo aircraft complete deliveries. Create and route your own +transport missions to maintain squadron strength. *** + +INTERCEPT RATIO SYSTEM: +Sophisticated threat response calculation with zone-based modifiers: +• Base intercept ratio (e.g., 0.8 = 8 interceptors per 10 threats) +• Zone-specific multipliers (primary: 1.0, secondary: 0.6, tertiary: 1.4) +• Threat size considerations (larger formations get proportional response) +• Squadron selection based on zone priority and proximity +• Aircraft availability and cooldown status factored into decisions + +SETUP INSTRUCTIONS: +1. Load MOOSE framework in mission before this script +2. Configure Squadrons: Create fighter aircraft GROUP templates for both coalitions in mission editor +3. Configure RED squadrons in RED_SQUADRON_CONFIG section +4. Configure BLUE squadrons in BLUE_SQUADRON_CONFIG section +5. Optionally create zones in mission editor for area-of-responsibility using helicopter groups with waypoints. +6. Set coalition behavior parameters in TADC_SETTINGS +7. Configure cargo patterns in ADVANCED_SETTINGS if using replenishment +8. Add this script as "DO SCRIPT" trigger at mission start (after MOOSE loaded) +9. Create and manage cargo aircraft missions for replenishment (optional) + +CONFIGURATION VALIDATION: +Built-in validation system checks for: +• Template existence and proper naming +• Airbase name accuracy and coalition control +• Zone existence in mission editor +• Parameter ranges and logical consistency +• Coalition enablement and squadron availability +• Prevents common configuration errors before mission starts + +TACTICAL SCENARIOS SUPPORTED: +• Balanced air warfare with equal capabilities and symmetric response +• Asymmetric scenarios with different coalition strengths and capabilities +• Layered air defense with overlapping squadron zones +• Border/perimeter defense with primary and fallback positions +• Training missions for AI vs AI air combat observation +• Dynamic frontline battles with shifting territorial control +• Long-duration missions with cargo resupply operations +• Emergency response scenarios with threat priority management + +LOGGING AND MONITORING: +• Real-time threat detection and interceptor launch notifications +• Squadron status reports including aircraft counts and cooldown timers +• Airbase operational status with capture/destruction detection +• Cargo delivery tracking and replenishment confirmations +• Zone-based engagement decisions with detailed reasoning +• Configuration validation results and error reporting +• Performance monitoring with emergency cleanup notifications + +REQUIREMENTS: +• MOOSE framework (https://github.com/FlightControl-Master/MOOSE) +• Fighter aircraft GROUP templates (not UNIT templates) for each coalition +• Airbases must exist in mission and be under correct coalition control +• Zone objects in mission editor (if using zone-based features) +• Proper template naming matching squadron configuration + +AUTHOR: +• Based off MOOSE framework by FlightControl-Master +• Developed and customized by Mission Designer "F99th-TracerFacer" + +VERSION: 1.0 +═══════════════════════════════════════════════════════════════════════════════ +]] + +--[[ +═══════════════════════════════════════════════════════════════════════════════ + MAIN SETTINGS +═══════════════════════════════════════════════════════════════════════════════ +]] + +-- Core TADC behavior settings - applies to BOTH coalitions unless overridden +local TADC_SETTINGS = { + -- Enable/Disable coalitions + enableRed = true, -- Set to false to disable RED TADC + enableBlue = true, -- Set to false to disable BLUE TADC + + -- Timing settings (applies to both coalitions) + checkInterval = 30, -- How often to scan for threats (seconds) + monitorInterval = 30, -- How often to check interceptor status (seconds) + statusReportInterval = 120, -- How often to report airbase status (seconds) + squadronSummaryInterval = 600, -- How often to broadcast squadron summary (seconds) + cargoCheckInterval = 15, -- How often to check for cargo deliveries (seconds) + + -- RED Coalition Settings + red = { + maxActiveCAP = 24, -- Maximum RED fighters airborne at once + squadronCooldown = 300, -- RED cooldown after squadron launch (seconds) + interceptRatio = 0.8, -- RED interceptors per threat aircraft + cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery + emergencyCleanupTime = 7200, -- RED force cleanup time (seconds) + rtbFlightBuffer = 300, -- RED extra landing time before cleanup (seconds) + }, + + -- BLUE Coalition Settings + blue = { + maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once + squadronCooldown = 300, -- BLUE cooldown after squadron launch (seconds) + interceptRatio = 0.8, -- BLUE interceptors per threat aircraft + cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery + emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds) + rtbFlightBuffer = 300, -- BLUE extra landing time before cleanup (seconds) + }, +} + +-- Load squadron configs from external file +-- RED_SQUADRON_CONFIG and BLUE_SQUADRON_CONFIG are now global, loaded by the trigger + +--[[ +INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft: + +Threat Size: 1 2 4 8 12 16 (aircraft) +==================================================================== +interceptRatio 0.2: 1 1 1 2 3 4 (conservative) +interceptRatio 0.5: 1 1 2 4 6 8 (light response) +interceptRatio 0.8: 1 2 4 7 10 13 (balanced) <- DEFAULT +interceptRatio 1.0: 1 2 4 8 12 16 (1:1 parity) +interceptRatio 1.2: 2 3 5 10 15 20 (slight advantage) +interceptRatio 1.4: 2 3 6 12 17 23 (good advantage) +interceptRatio 1.6: 2 4 7 13 20 26 (strong response) +interceptRatio 1.8: 2 4 8 15 22 29 (overwhelming) +interceptRatio 2.0: 2 4 8 16 24 32 (overkill) + +TACTICAL EFFECTS: +• 0.2-0.5: Minimal response, may be overwhelmed by large formations +• 0.8-1.0: Realistic parity, creates balanced dogfights +• 1.2-1.4: Coalition advantage, challenging for enemy +• 1.6-1.8: Strong defense, difficult penetration missions +• 1.9-2.0: Nearly impenetrable, may exhaust squadrons quickly + +SQUADRON IMPACT: +• Low ratios (0.2-0.8): Squadrons last longer, sustained defense +• High ratios (1.6-2.0): Rapid squadron depletion, coverage gaps +• Sweet spot (1.0-1.4): Balanced response with good coverage duration + +ASYMMETRIC SCENARIOS: +• Set RED ratio 1.2, BLUE ratio 0.8 = RED advantage +• Set RED ratio 0.6, BLUE ratio 1.4 = BLUE advantage +• Different maxActiveCAP values create capacity imbalances +]] + + +--[[ +═══════════════════════════════════════════════════════════════════════════════ + ADVANCED SETTINGS +═══════════════════════════════════════════════════════════════════════════════ + +These settings control more detailed behavior. Most users won't need to change these. +]] + +local ADVANCED_SETTINGS = { + -- Cargo aircraft detection patterns (aircraft with these names will replenish squadrons (Currently only fixed wing aircraft supported)) + cargoPatterns = {"CARGO", "TRANSPORT", "C130", "C-130", "AN26", "AN-26"}, + + -- Distance from airbase to consider cargo "landed" (meters) + cargoLandingDistance = 3000, + + -- Velocity below which aircraft is considered "landed" (km/h) + cargoLandedVelocity = 5, + + -- RTB settings + rtbAltitude = 6000, -- Return to base altitude (feet) + rtbSpeed = 430, -- Return to base speed (knots) + + -- Logging settings + enableDetailedLogging = true, -- Set to false to reduce log spam + logPrefix = "[Universal TADC]", -- Prefix for all log messages +} + +--[[ +═══════════════════════════════════════════════════════════════════════════════ + SYSTEM CODE + (DO NOT MODIFY BELOW THIS LINE) +═══════════════════════════════════════════════════════════════════════════════ +]] + +-- Internal tracking variables - separate for each coalition +local activeInterceptors = { + red = {}, + blue = {} +} +local lastLaunchTime = { + red = {}, + blue = {} +} +local assignedThreats = { + red = {}, + blue = {} +} +local squadronCooldowns = { + red = {}, + blue = {} +} +local squadronAircraftCounts = { + red = {}, + blue = {} +} + +-- Performance optimization: Cache SET_GROUP objects to avoid repeated creation +local cachedSets = { + redCargo = nil, + blueCargo = nil, + redAircraft = nil, + blueAircraft = nil +} + +-- Initialize squadron aircraft counts for both coalitions +for _, squadron in pairs(RED_SQUADRON_CONFIG) do + if squadron.aircraft and squadron.templateName then + squadronAircraftCounts.red[squadron.templateName] = squadron.aircraft + end +end + +for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + if squadron.aircraft and squadron.templateName then + squadronAircraftCounts.blue[squadron.templateName] = squadron.aircraft + end +end + +-- Logging function +local function log(message, detailed) + if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then + env.info(ADVANCED_SETTINGS.logPrefix .. " " .. message) + end +end + +-- Squadron resource summary generator + +local function getSquadronResourceSummary(coalitionSide) + local function getStatus(remaining, max) + local percent = (remaining / max) * 100 + if percent <= 10 then return "[CRITICAL]" end + if percent <= 25 then return "[LOW]" end + return "OK" + end + + local lines = {} + table.insert(lines, "-=[ Tactical Air Defense Controller ]=-\n") + table.insert(lines, "Squadron Resource Summary:\n") + table.insert(lines, "| Squadron | Aircraft Remaining | Status |") + table.insert(lines, "|--------------|--------------------|-------------|") + + if coalitionSide == coalition.side.RED then + 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) + 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) + 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%") + return table.concat(lines, "\n") +end + +-- Broadcast squadron summary to all players +local function broadcastSquadronSummary() + if TADC_SETTINGS.enableRed then + local summaryRed = getSquadronResourceSummary(coalition.side.RED) + MESSAGE:New(summaryRed, 20):ToCoalition(coalition.side.RED) + end + if TADC_SETTINGS.enableBlue then + local summaryBlue = getSquadronResourceSummary(coalition.side.BLUE) + MESSAGE:New(summaryBlue, 20):ToCoalition(coalition.side.BLUE) + end +end + +-- Coalition-specific settings helper +local function getCoalitionSettings(coalitionSide) + if coalitionSide == coalition.side.RED then + return TADC_SETTINGS.red, "RED" + elseif coalitionSide == coalition.side.BLUE then + return TADC_SETTINGS.blue, "BLUE" + else + return nil, "UNKNOWN" + end +end + +-- Get squadron config for coalition +local function getSquadronConfig(coalitionSide) + if coalitionSide == coalition.side.RED then + return RED_SQUADRON_CONFIG + elseif coalitionSide == coalition.side.BLUE then + return BLUE_SQUADRON_CONFIG + else + return {} + end +end + +-- Check if coordinate is within a zone +local function isInZone(coordinate, zoneName) + if not zoneName or zoneName == "" then + return false + end + + -- Try to find the zone + local zone = ZONE:FindByName(zoneName) + if zone then + return zone:IsCoordinateInZone(coordinate) + else + -- Try to create polygon zone from helicopter group waypoints if not found + local group = GROUP:FindByName(zoneName) + if group then + -- Create polygon zone using the group's waypoints as vertices + zone = ZONE_POLYGON:NewFromGroupName(zoneName, zoneName) + if zone then + log("Created polygon zone '" .. zoneName .. "' from helicopter waypoints") + return zone:IsCoordinateInZone(coordinate) + else + log("Warning: Could not create polygon zone from group '" .. zoneName .. "' - check waypoints") + end + else + log("Warning: No group named '" .. zoneName .. "' found for zone creation") + end + + log("Warning: Zone '" .. zoneName .. "' not found in mission and could not create from helicopter group", true) + return false + end +end + +-- Get default zone configuration +local function getDefaultZoneConfig() + return { + primaryResponse = 1.0, + secondaryResponse = 0.6, + tertiaryResponse = 1.4, + maxRange = 200, + enableFallback = false, + priorityThreshold = 4, + ignoreLowPriority = false, + } +end + +-- Check if squadron should respond to fallback conditions +local function checkFallbackConditions(squadron, coalitionSide) + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + + -- Check if airbase is under attack (simplified - check if base has low aircraft) + local currentAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0 + local maxAircraft = squadron.aircraft + local aircraftRatio = currentAircraft / maxAircraft + + -- Trigger fallback if squadron is below 50% strength or base is threatened + if aircraftRatio < 0.5 then + return true + end + + -- Could add more complex conditions here (base under attack, etc.) + return false +end + +-- Get threat zone priority and response ratio for squadron +local function getThreatZonePriority(threatCoord, squadron, coalitionSide) + local zoneConfig = squadron.zoneConfig or getDefaultZoneConfig() + + -- Check distance from airbase first + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase then + local airbaseCoord = airbase:GetCoordinate() + local distance = airbaseCoord:Get2DDistance(threatCoord) / 1852 -- Convert meters to nautical miles + + if distance > zoneConfig.maxRange then + return "none", 0, "out of range (" .. math.floor(distance) .. "nm > " .. zoneConfig.maxRange .. "nm)" + end + end + + -- Check tertiary zone first (highest priority if fallback enabled) + if squadron.tertiaryZone and zoneConfig.enableFallback then + if checkFallbackConditions(squadron, coalitionSide) then + if isInZone(threatCoord, squadron.tertiaryZone) then + return "tertiary", zoneConfig.tertiaryResponse, "fallback zone (enhanced response)" + end + end + end + + -- Check primary zone + if squadron.primaryZone and isInZone(threatCoord, squadron.primaryZone) then + return "primary", zoneConfig.primaryResponse, "primary AOR" + end + + -- Check secondary zone + if squadron.secondaryZone and isInZone(threatCoord, squadron.secondaryZone) then + return "secondary", zoneConfig.secondaryResponse, "secondary AOR" + end + + -- Check tertiary zone (normal priority) + if squadron.tertiaryZone and isInZone(threatCoord, squadron.tertiaryZone) then + return "tertiary", zoneConfig.tertiaryResponse, "tertiary zone" + end + + -- If no zones are defined, use global response + if not squadron.primaryZone and not squadron.secondaryZone and not squadron.tertiaryZone then + return "global", 1.0, "global response (no zones defined)" + end + + -- Outside all defined zones + return "none", 0, "outside defined zones" +end + +-- Startup validation +local function validateConfiguration() + local errors = {} + + -- Check that global squadron configs exist + if type(RED_SQUADRON_CONFIG) ~= "table" then + table.insert(errors, "RED_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs.lua is loaded before this script.") + end + if type(BLUE_SQUADRON_CONFIG) ~= "table" then + table.insert(errors, "BLUE_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs.lua is loaded before this script.") + end + + -- Check coalition enablement + if not TADC_SETTINGS.enableRed and not TADC_SETTINGS.enableBlue then + table.insert(errors, "Both coalitions disabled - enable at least one in TADC_SETTINGS") + end + + -- Validate RED squadrons if enabled + if TADC_SETTINGS.enableRed then + if #RED_SQUADRON_CONFIG == 0 then + table.insert(errors, "No RED squadrons configured but RED TADC is enabled") + else + for i, squadron in pairs(RED_SQUADRON_CONFIG) do + local prefix = "RED Squadron " .. i .. ": " + + if not squadron.templateName or squadron.templateName == "" or + squadron.templateName == "RED_CAP_SQUADRON_1" or + squadron.templateName == "RED_CAP_SQUADRON_2" then + table.insert(errors, prefix .. "templateName not configured or using default example") + end + + if not squadron.displayName or squadron.displayName == "" then + table.insert(errors, prefix .. "displayName not configured") + end + + if not squadron.airbaseName or squadron.airbaseName == "" or + squadron.airbaseName:find("YOUR_RED_AIRBASE") then + table.insert(errors, prefix .. "airbaseName not configured or using default example") + end + + if not squadron.aircraft or squadron.aircraft <= 0 then + table.insert(errors, prefix .. "aircraft count not configured or invalid") + end + + -- Validate zone configuration if zones are specified + if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then + if squadron.zoneConfig then + local zc = squadron.zoneConfig + if zc.primaryResponse and (zc.primaryResponse < 0 or zc.primaryResponse > 5) then + table.insert(errors, prefix .. "primaryResponse ratio out of range (0-5)") + end + if zc.secondaryResponse and (zc.secondaryResponse < 0 or zc.secondaryResponse > 5) then + table.insert(errors, prefix .. "secondaryResponse ratio out of range (0-5)") + end + if zc.tertiaryResponse and (zc.tertiaryResponse < 0 or zc.tertiaryResponse > 5) then + table.insert(errors, prefix .. "tertiaryResponse ratio out of range (0-5)") + end + if zc.maxRange and (zc.maxRange < 10 or zc.maxRange > 1000) then + table.insert(errors, prefix .. "maxRange out of range (10-1000 nm)") + end + end + + -- Check if specified zones exist in mission + local zones = {} + if squadron.primaryZone then table.insert(zones, squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, squadron.tertiaryZone) end + + for _, zoneName in ipairs(zones) do + local zoneObj = ZONE:FindByName(zoneName) + if not zoneObj then + -- Check if there's a helicopter unit/group with this name for zone creation + local unit = UNIT:FindByName(zoneName) + local group = GROUP:FindByName(zoneName) + if not unit and not group then + table.insert(errors, prefix .. "zone '" .. zoneName .. "' not found in mission (no zone or helicopter unit named '" .. zoneName .. "')") + end + end + end + end + end + end + end + + -- Validate BLUE squadrons if enabled + if TADC_SETTINGS.enableBlue then + if #BLUE_SQUADRON_CONFIG == 0 then + table.insert(errors, "No BLUE squadrons configured but BLUE TADC is enabled") + else + for i, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local prefix = "BLUE Squadron " .. i .. ": " + + if not squadron.templateName or squadron.templateName == "" or + squadron.templateName == "BLUE_CAP_SQUADRON_1" or + squadron.templateName == "BLUE_CAP_SQUADRON_2" then + table.insert(errors, prefix .. "templateName not configured or using default example") + end + + if not squadron.displayName or squadron.displayName == "" then + table.insert(errors, prefix .. "displayName not configured") + end + + if not squadron.airbaseName or squadron.airbaseName == "" or + squadron.airbaseName:find("YOUR_BLUE_AIRBASE") then + table.insert(errors, prefix .. "airbaseName not configured or using default example") + end + + if not squadron.aircraft or squadron.aircraft <= 0 then + table.insert(errors, prefix .. "aircraft count not configured or invalid") + end + + -- Validate zone configuration if zones are specified + if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then + if squadron.zoneConfig then + local zc = squadron.zoneConfig + if zc.primaryResponse and (zc.primaryResponse < 0 or zc.primaryResponse > 5) then + table.insert(errors, prefix .. "primaryResponse ratio out of range (0-5)") + end + if zc.secondaryResponse and (zc.secondaryResponse < 0 or zc.secondaryResponse > 5) then + table.insert(errors, prefix .. "secondaryResponse ratio out of range (0-5)") + end + if zc.tertiaryResponse and (zc.tertiaryResponse < 0 or zc.tertiaryResponse > 5) then + table.insert(errors, prefix .. "tertiaryResponse ratio out of range (0-5)") + end + if zc.maxRange and (zc.maxRange < 10 or zc.maxRange > 1000) then + table.insert(errors, prefix .. "maxRange out of range (10-1000 nm)") + end + end + + -- Check if specified zones exist in mission + local zones = {} + if squadron.primaryZone then table.insert(zones, squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, squadron.tertiaryZone) end + + for _, zoneName in ipairs(zones) do + local zoneObj = ZONE:FindByName(zoneName) + if not zoneObj then + -- Check if there's a helicopter unit/group with this name for zone creation + local unit = UNIT:FindByName(zoneName) + local group = GROUP:FindByName(zoneName) + if not unit and not group then + table.insert(errors, prefix .. "zone '" .. zoneName .. "' not found in mission (no zone or helicopter unit named '" .. zoneName .. "')") + end + end + end + end + end + end + end + + -- Report errors + if #errors > 0 then + log("CONFIGURATION ERRORS DETECTED:") + MESSAGE:New("CONFIGURATION ERRORS DETECTED:", 30):ToAll() + for _, error in pairs(errors) do + log(" ✗ " .. error) + MESSAGE:New("CONFIG ERROR: " .. error, 30):ToAll() + end + log("Please fix configuration before using Universal TADC!") + MESSAGE:New("Please fix configuration before using Universal TADC!", 30):ToAll() + return false + else + log("Configuration validation passed ✓") + MESSAGE:New("Universal TADC configuration passed ✓", 10):ToAll() + return true + end +end + +-- Monitor cargo aircraft landings for squadron replenishment +local function monitorCargoReplenishment() + -- Process RED cargo aircraft + if TADC_SETTINGS.enableRed then + -- Use cached set for performance, create if needed + if not cachedSets.redCargo then + cachedSets.redCargo = SET_GROUP:New():FilterCoalitions("red"):FilterCategoryAirplane():FilterStart() + end + local redCargo = cachedSets.redCargo + + redCargo:ForEach(function(cargoGroup) + if cargoGroup and cargoGroup:IsAlive() then + local cargoName = cargoGroup:GetName():upper() + local isCargoAircraft = false + + -- Check if aircraft name matches cargo patterns + for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do + if string.find(cargoName, pattern) then + isCargoAircraft = true + break + end + end + + if isCargoAircraft then + local cargoCoord = cargoGroup:GetCoordinate() + local cargoVelocity = cargoGroup:GetVelocityKMH() + + -- Consider aircraft "landed" if velocity is very low + if cargoVelocity < ADVANCED_SETTINGS.cargoLandedVelocity then + -- Check which RED airbase it's near + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:GetCoalition() == coalition.side.RED then + local airbaseCoord = airbase:GetCoordinate() + local distance = cargoCoord:Get2DDistance(airbaseCoord) + + -- If within configured distance of airbase, consider it a delivery + if distance < ADVANCED_SETTINGS.cargoLandingDistance then + -- Initialize processed deliveries table + if not _G.processedDeliveries then + _G.processedDeliveries = {} + end + + -- Create unique delivery key including timestamp to prevent race conditions + local deliveryKey = cargoName .. "_RED_" .. squadron.airbaseName .. "_" .. cargoGroup:GetID() + + if not _G.processedDeliveries[deliveryKey] then + -- Mark delivery as processed immediately to prevent race conditions + _G.processedDeliveries[deliveryKey] = timer.getTime() + -- Process replenishment + local currentCount = squadronAircraftCounts.red[squadron.templateName] or 0 + local maxCount = squadron.aircraft + local newCount = math.min(currentCount + TADC_SETTINGS.red.cargoReplenishmentAmount, maxCount) + local actualAdded = newCount - currentCount + + if actualAdded > 0 then + squadronAircraftCounts.red[squadron.templateName] = newCount + local msg = "RED CARGO DELIVERY: " .. cargoName .. " delivered " .. actualAdded .. + " aircraft to " .. squadron.displayName .. + " (" .. newCount .. "/" .. maxCount .. ")" + log(msg) + MESSAGE:New(msg, 20):ToAll() + else + local msg = "RED CARGO DELIVERY: " .. squadron.displayName .. " already at max capacity" + log(msg, true) + MESSAGE:New(msg, 15):ToAll() + end + end + end + end + end + end + end + end + end) + end + + -- Process BLUE cargo aircraft + if TADC_SETTINGS.enableBlue then + -- Use cached set for performance, create if needed + if not cachedSets.blueCargo then + cachedSets.blueCargo = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() + end + local blueCargo = cachedSets.blueCargo + + blueCargo:ForEach(function(cargoGroup) + if cargoGroup and cargoGroup:IsAlive() then + local cargoName = cargoGroup:GetName():upper() + local isCargoAircraft = false + + -- Check if aircraft name matches cargo patterns + for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do + if string.find(cargoName, pattern) then + isCargoAircraft = true + break + end + end + + if isCargoAircraft then + local cargoCoord = cargoGroup:GetCoordinate() + local cargoVelocity = cargoGroup:GetVelocityKMH() + + -- Consider aircraft "landed" if velocity is very low + if cargoVelocity < ADVANCED_SETTINGS.cargoLandedVelocity then + -- Check which BLUE airbase it's near + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:GetCoalition() == coalition.side.BLUE then + local airbaseCoord = airbase:GetCoordinate() + local distance = cargoCoord:Get2DDistance(airbaseCoord) + + -- If within configured distance of airbase, consider it a delivery + if distance < ADVANCED_SETTINGS.cargoLandingDistance then + -- Initialize processed deliveries table + if not _G.processedDeliveries then + _G.processedDeliveries = {} + end + + -- Create unique delivery key including timestamp to prevent race conditions + local deliveryKey = cargoName .. "_BLUE_" .. squadron.airbaseName .. "_" .. cargoGroup:GetID() + + if not _G.processedDeliveries[deliveryKey] then + -- Mark delivery as processed immediately to prevent race conditions + _G.processedDeliveries[deliveryKey] = timer.getTime() + -- Process replenishment + local currentCount = squadronAircraftCounts.blue[squadron.templateName] or 0 + local maxCount = squadron.aircraft + local newCount = math.min(currentCount + TADC_SETTINGS.blue.cargoReplenishmentAmount, maxCount) + local actualAdded = newCount - currentCount + + if actualAdded > 0 then + squadronAircraftCounts.blue[squadron.templateName] = newCount + local msg = "BLUE CARGO DELIVERY: " .. cargoName .. " delivered " .. actualAdded .. + " aircraft to " .. squadron.displayName .. + " (" .. newCount .. "/" .. maxCount .. ")" + log(msg) + MESSAGE:New(msg, 20):ToAll() + else + local msg = "BLUE CARGO DELIVERY: " .. squadron.displayName .. " already at max capacity" + log(msg, true) + MESSAGE:New(msg, 15):ToAll() + end + end + end + end + end + end + end + end + end) + end +end + +-- Send interceptor back to base +local function sendInterceptorHome(interceptor, coalitionSide) + if not interceptor or not interceptor:IsAlive() then + return + end + + -- Find nearest friendly airbase + local interceptorCoord = interceptor:GetCoordinate() + if not interceptorCoord then + log("ERROR: Could not get interceptor coordinates for RTB", true) + return + end + local nearestAirbase = nil + local shortestDistance = math.huge + local squadronConfig = getSquadronConfig(coalitionSide) + + -- Check all squadron airbases to find the nearest one that's still friendly + for _, squadron in pairs(squadronConfig) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:GetCoalition() == coalitionSide and airbase:IsAlive() then + local airbaseCoord = airbase:GetCoordinate() + local distance = interceptorCoord:Get2DDistance(airbaseCoord) + if distance < shortestDistance then + shortestDistance = distance + nearestAirbase = airbase + end + end + end + + if nearestAirbase then + local airbaseCoord = nearestAirbase:GetCoordinate() + local rtbAltitude = ADVANCED_SETTINGS.rtbAltitude * 0.3048 -- Convert feet to meters + local rtbCoord = airbaseCoord:SetAltitude(rtbAltitude) + + -- Clear current tasks and route home + interceptor:ClearTasks() + interceptor:RouteAirTo(rtbCoord, ADVANCED_SETTINGS.rtbSpeed * 0.5144, "BARO") -- Convert knots to m/s + + local _, coalitionName = getCoalitionSettings(coalitionSide) + log("Sending " .. coalitionName .. " " .. interceptor:GetName() .. " back to " .. nearestAirbase:GetName(), true) + + -- Schedule cleanup after they should have landed + local coalitionSettings = getCoalitionSettings(coalitionSide) + local flightTime = math.ceil(shortestDistance / (ADVANCED_SETTINGS.rtbSpeed * 0.5144)) + coalitionSettings.rtbFlightBuffer + + SCHEDULER:New(nil, function() + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + if activeInterceptors[coalitionKey][interceptor:GetName()] then + activeInterceptors[coalitionKey][interceptor:GetName()] = nil + log("Cleaned up " .. coalitionName .. " " .. interceptor:GetName() .. " after RTB", true) + end + end, {}, flightTime) + else + local _, coalitionName = getCoalitionSettings(coalitionSide) + log("No friendly airbase found for " .. coalitionName .. " " .. interceptor:GetName() .. ", will clean up normally") + end +end + +-- Check if airbase is still usable +local function isAirbaseUsable(airbaseName, expectedCoalition) + local airbase = AIRBASE:FindByName(airbaseName) + if not airbase then + return false, "not found" + elseif airbase:GetCoalition() ~= expectedCoalition then + local capturedBy = "Unknown" + if airbase:GetCoalition() == coalition.side.RED then + capturedBy = "Red" + elseif airbase:GetCoalition() == coalition.side.BLUE then + capturedBy = "Blue" + else + capturedBy = "Neutral" + end + return false, "captured by " .. capturedBy + elseif not airbase:IsAlive() then + return false, "destroyed" + else + return true, "operational" + end +end + +-- Count active fighters for coalition +local function countActiveFighters(coalitionSide) + local count = 0 + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + + for _, interceptorData in pairs(activeInterceptors[coalitionKey]) do + if interceptorData and interceptorData.group and interceptorData.group:IsAlive() then + count = count + interceptorData.group:GetSize() + end + end + return count +end + +-- Find best squadron to launch for coalition using zone-based priorities +local function findBestSquadron(threatCoord, threatSize, coalitionSide) + local bestSquadron = nil + local bestPriority = "none" + local bestResponseRatio = 0 + local shortestDistance = math.huge + local currentTime = timer.getTime() + local squadronConfig = getSquadronConfig(coalitionSide) + local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide) + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + local zonePriorityOrder = {"tertiary", "primary", "secondary", "global"} + + -- First pass: find squadrons that can respond to this threat + local availableSquadrons = {} + + for _, squadron in pairs(squadronConfig) do + -- Check basic availability + local squadronAvailable = true + local unavailableReason = "" + + -- Check cooldown + if squadronCooldowns[coalitionKey][squadron.templateName] then + local cooldownEnd = squadronCooldowns[coalitionKey][squadron.templateName] + if currentTime < cooldownEnd then + local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) + squadronAvailable = false + unavailableReason = "on cooldown for " .. timeLeft .. "m" + else + -- Cooldown expired, remove it + squadronCooldowns[coalitionKey][squadron.templateName] = nil + log(coalitionName .. " Squadron " .. squadron.displayName .. " cooldown expired, available for launch", true) + end + end + + -- Check aircraft availability + if squadronAvailable then + local availableAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0 + if availableAircraft <= 0 then + squadronAvailable = false + unavailableReason = "no aircraft available (" .. availableAircraft .. "/" .. squadron.aircraft .. ")" + end + end + + -- Check airbase status + if squadronAvailable then + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if not airbase then + squadronAvailable = false + unavailableReason = "airbase not found" + elseif airbase:GetCoalition() ~= coalitionSide then + squadronAvailable = false + unavailableReason = "airbase no longer under " .. coalitionName .. " control" + elseif not airbase:IsAlive() then + squadronAvailable = false + unavailableReason = "airbase destroyed" + end + end + + -- Check template exists (Note: Templates are validated during SPAWN:New() call) + -- Template validation is handled by MOOSE SPAWN class during actual spawning + + if squadronAvailable then + -- Get zone priority and response ratio + local zonePriority, responseRatio, zoneDescription = getThreatZonePriority(threatCoord, squadron, coalitionSide) + + -- Check if threat meets priority threshold for secondary zones + local zoneConfig = squadron.zoneConfig or getDefaultZoneConfig() + if zonePriority == "secondary" and zoneConfig.ignoreLowPriority then + if threatSize < zoneConfig.priorityThreshold then + log(coalitionName .. " " .. squadron.displayName .. " ignoring low-priority threat in secondary zone (" .. + threatSize .. " < " .. zoneConfig.priorityThreshold .. ")", true) + responseRatio = 0 + zonePriority = "none" + end + end + + if responseRatio > 0 then + local airbase = AIRBASE:FindByName(squadron.airbaseName) + local airbaseCoord = airbase:GetCoordinate() + local distance = airbaseCoord:Get2DDistance(threatCoord) + + table.insert(availableSquadrons, { + squadron = squadron, + zonePriority = zonePriority, + responseRatio = responseRatio, + distance = distance, + zoneDescription = zoneDescription + }) + + log(coalitionName .. " " .. squadron.displayName .. " can respond: " .. zoneDescription .. + " (ratio: " .. responseRatio .. ", distance: " .. math.floor(distance/1852) .. "nm)", true) + else + log(coalitionName .. " " .. squadron.displayName .. " will not respond: " .. zoneDescription, true) + end + else + log(coalitionName .. " " .. squadron.displayName .. " unavailable: " .. unavailableReason, true) + end + end + + -- Second pass: select best squadron by priority and distance + if #availableSquadrons > 0 then + -- Sort by zone priority (higher priority first), then by distance (closer first) + table.sort(availableSquadrons, function(a, b) + -- Get priority indices + local aPriorityIndex = 5 + local bPriorityIndex = 5 + for i, priority in ipairs(zonePriorityOrder) do + if a.zonePriority == priority then aPriorityIndex = i end + if b.zonePriority == priority then bPriorityIndex = i end + end + + -- First sort by priority (lower index = higher priority) + if aPriorityIndex ~= bPriorityIndex then + return aPriorityIndex < bPriorityIndex + end + + -- Then sort by distance (closer is better) + return a.distance < b.distance + end) + + local selected = availableSquadrons[1] + log("Selected " .. coalitionName .. " " .. selected.squadron.displayName .. " for response: " .. + selected.zoneDescription .. " (distance: " .. math.floor(selected.distance/1852) .. "nm)") + + return selected.squadron, selected.responseRatio, selected.zoneDescription + end + + log("No " .. coalitionName .. " squadron available for threat at coordinates") + return nil, 0, "no available squadrons" +end + +-- Launch interceptor for coalition +local function launchInterceptor(threatGroup, coalitionSide) + if not threatGroup or not threatGroup:IsAlive() then + return + end + + local threatCoord = threatGroup:GetCoordinate() + local threatName = threatGroup:GetName() + local threatSize = threatGroup:GetSize() + local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide) + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + + -- Check if threat already has interceptors assigned + if assignedThreats[coalitionKey][threatName] then + local assignedInterceptors = assignedThreats[coalitionKey][threatName] + local aliveCount = 0 + + -- Check if assigned interceptors are still alive + if type(assignedInterceptors) == "table" then + for _, interceptor in pairs(assignedInterceptors) do + if interceptor and interceptor:IsAlive() then + aliveCount = aliveCount + 1 + end + end + else + -- Handle legacy single interceptor assignment + if assignedInterceptors and assignedInterceptors:IsAlive() then + aliveCount = 1 + end + end + + if aliveCount > 0 then + return -- Still being intercepted + else + -- All interceptors are dead, clear the assignment + assignedThreats[coalitionKey][threatName] = nil + end + end + + -- Find best squadron using zone-based priority system first + local squadron, zoneResponseRatio, zoneDescription = findBestSquadron(threatCoord, threatSize, coalitionSide) + + if not squadron then + log("No " .. coalitionName .. " squadron available") + return + end + + -- Calculate how many interceptors to launch using zone-modified ratio + local finalInterceptRatio = coalitionSettings.interceptRatio * zoneResponseRatio + local interceptorsNeeded = math.max(1, math.ceil(threatSize * finalInterceptRatio)) + + -- Check if we have capacity + if countActiveFighters(coalitionSide) + interceptorsNeeded > coalitionSettings.maxActiveCAP then + interceptorsNeeded = coalitionSettings.maxActiveCAP - countActiveFighters(coalitionSide) + if interceptorsNeeded <= 0 then + log(coalitionName .. " max fighters airborne, skipping launch") + return + end + end + if not squadron then + log("No " .. coalitionName .. " squadron available") + return + end + + -- Limit interceptors to available aircraft + local availableAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0 + interceptorsNeeded = math.min(interceptorsNeeded, availableAircraft) + + if interceptorsNeeded <= 0 then + log(coalitionName .. " Squadron " .. squadron.displayName .. " has no aircraft to launch") + return + end + + -- Launch multiple interceptors to match threat + local spawn = SPAWN:New(squadron.templateName) + if not spawn then + log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName) + return + end + + local interceptors = {} + + for i = 1, interceptorsNeeded do + local interceptor = spawn:Spawn() + + if interceptor then + table.insert(interceptors, interceptor) + + -- Wait a moment for initialization + SCHEDULER:New(nil, function() + if interceptor and interceptor:IsAlive() then + -- Set aggressive AI + interceptor:OptionROEOpenFire() + interceptor:OptionROTVertical() + + -- Route to threat + local currentThreatCoord = threatGroup:GetCoordinate() + if currentThreatCoord then + local interceptCoord = currentThreatCoord:SetAltitude(squadron.altitude * 0.3048) -- Convert feet to meters + interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO") -- Convert knots to m/s + + -- Attack the threat + local attackTask = { + id = 'AttackGroup', + params = { + groupId = threatGroup:GetID(), + weaponType = 'Auto', + attackQtyLimit = 0, + priority = 1 + } + } + interceptor:PushTask(attackTask, 1) + end + end + end, {}, 3) + + -- Track the interceptor with squadron info + activeInterceptors[coalitionKey][interceptor:GetName()] = { + group = interceptor, + squadron = squadron.templateName, + displayName = squadron.displayName + } + + -- Emergency cleanup (safety net) + SCHEDULER:New(nil, function() + if activeInterceptors[coalitionKey][interceptor:GetName()] then + log("Emergency cleanup of " .. coalitionName .. " " .. interceptor:GetName() .. " (should have RTB'd)") + activeInterceptors[coalitionKey][interceptor:GetName()] = nil + end + end, {}, coalitionSettings.emergencyCleanupTime) + end + end + + -- Log the launch and track assignment + if #interceptors > 0 then + -- Decrement squadron aircraft count + local currentCount = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0 + squadronAircraftCounts[coalitionKey][squadron.templateName] = math.max(0, currentCount - #interceptors) + local remainingCount = squadronAircraftCounts[coalitionKey][squadron.templateName] + + log("Launched " .. #interceptors .. " x " .. coalitionName .. " " .. squadron.displayName .. " to intercept " .. + threatSize .. " x " .. threatName .. " (" .. zoneDescription .. ", ratio: " .. string.format("%.1f", finalInterceptRatio) .. + ", remaining: " .. remainingCount .. "/" .. squadron.aircraft .. ")") + assignedThreats[coalitionKey][threatName] = interceptors + lastLaunchTime[coalitionKey][threatName] = timer.getTime() + + -- Apply cooldown immediately when squadron launches + local currentTime = timer.getTime() + squadronCooldowns[coalitionKey][squadron.templateName] = currentTime + coalitionSettings.squadronCooldown + local cooldownMinutes = coalitionSettings.squadronCooldown / 60 + log(coalitionName .. " Squadron " .. squadron.displayName .. " LAUNCHED! Applying " .. cooldownMinutes .. " minute cooldown") + end +end + +-- Main threat detection loop for coalition +local function detectThreatsForCoalition(coalitionSide) + local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide) + local enemyCoalition = (coalitionSide == coalition.side.RED) and "blue" or "red" + local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" + + log("Scanning for " .. coalitionName .. " threats...", true) + + -- Clean up dead threats from tracking + local currentThreats = {} + + -- Find all enemy aircraft using cached set for performance + local cacheKey = enemyCoalition .. "Aircraft" + if not cachedSets[cacheKey] then + cachedSets[cacheKey] = SET_GROUP:New():FilterCoalitions(enemyCoalition):FilterCategoryAirplane():FilterStart() + end + local enemyAircraft = cachedSets[cacheKey] + local threatCount = 0 + + enemyAircraft:ForEach(function(enemyGroup) + if enemyGroup and enemyGroup:IsAlive() then + threatCount = threatCount + 1 + currentThreats[enemyGroup:GetName()] = true + log("Found " .. coalitionName .. " threat: " .. enemyGroup:GetName() .. " (" .. enemyGroup:GetTypeName() .. ")", true) + + -- Launch interceptor for this threat + launchInterceptor(enemyGroup, coalitionSide) + end + end) + + -- Clean up assignments for threats that no longer exist and send interceptors home + for threatName, assignedInterceptors in pairs(assignedThreats[coalitionKey]) do + if not currentThreats[threatName] then + log("Threat " .. threatName .. " eliminated, sending " .. coalitionName .. " interceptors home...") + + -- Send assigned interceptors back to base + if type(assignedInterceptors) == "table" then + for _, interceptor in pairs(assignedInterceptors) do + if interceptor and interceptor:IsAlive() then + sendInterceptorHome(interceptor, coalitionSide) + end + end + else + -- Handle legacy single interceptor assignment + if assignedInterceptors and assignedInterceptors:IsAlive() then + sendInterceptorHome(assignedInterceptors, coalitionSide) + end + end + + assignedThreats[coalitionKey][threatName] = nil + end + end + + -- Count assigned threats + local assignedCount = 0 + for _ in pairs(assignedThreats[coalitionKey]) do assignedCount = assignedCount + 1 end + + log(coalitionName .. " scan complete: " .. threatCount .. " threats, " .. countActiveFighters(coalitionSide) .. " active fighters, " .. + assignedCount .. " assigned") +end + +-- Main threat detection loop - calls both coalitions +local function detectThreats() + if TADC_SETTINGS.enableRed then + detectThreatsForCoalition(coalition.side.RED) + end + + if TADC_SETTINGS.enableBlue then + detectThreatsForCoalition(coalition.side.BLUE) + end +end + +-- Monitor interceptor groups for cleanup when destroyed +local function monitorInterceptors() + -- Check RED interceptors + if TADC_SETTINGS.enableRed then + for interceptorName, interceptorData in pairs(activeInterceptors.red) do + if interceptorData and interceptorData.group then + if not interceptorData.group:IsAlive() then + -- Interceptor group is destroyed - just clean up tracking + local displayName = interceptorData.displayName + log("RED Interceptor from " .. displayName .. " destroyed: " .. interceptorName, true) + + -- Remove from active tracking + activeInterceptors.red[interceptorName] = nil + end + end + end + end + + -- Check BLUE interceptors + if TADC_SETTINGS.enableBlue then + for interceptorName, interceptorData in pairs(activeInterceptors.blue) do + if interceptorData and interceptorData.group then + if not interceptorData.group:IsAlive() then + -- Interceptor group is destroyed - just clean up tracking + local displayName = interceptorData.displayName + log("BLUE Interceptor from " .. displayName .. " destroyed: " .. interceptorName, true) + + -- Remove from active tracking + activeInterceptors.blue[interceptorName] = nil + end + end + end + end +end + +-- Periodic airbase status check +local function checkAirbaseStatus() + log("=== AIRBASE STATUS REPORT ===") + + local redUsableCount = 0 + local blueUsableCount = 0 + local currentTime = timer.getTime() + + -- Check RED airbases + 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 aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0 + local maxAircraft = squadron.aircraft + local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Add zone information if configured + local zoneStatus = "" + if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then + local zones = {} + if squadron.primaryZone then table.insert(zones, "P:" .. squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, "S:" .. squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, "T:" .. squadron.tertiaryZone) end + zoneStatus = " Zones: " .. table.concat(zones, " ") + end + + -- Check if squadron is on cooldown + local cooldownStatus = "" + if squadronCooldowns.red[squadron.templateName] then + local cooldownEnd = squadronCooldowns.red[squadron.templateName] + if currentTime < cooldownEnd then + local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) + cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" + end + end + + local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + + if usable and cooldownStatus == "" and aircraftCount > 0 then + redUsableCount = redUsableCount + 1 + log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) + else + log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) + end + end + log("RED Status: " .. redUsableCount .. "/" .. #RED_SQUADRON_CONFIG .. " airbases operational") + end + + -- Check BLUE airbases + 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 aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0 + local maxAircraft = squadron.aircraft + local aircraftStatus = " Aircraft: " .. aircraftCount .. "/" .. maxAircraft + + -- Add zone information if configured + local zoneStatus = "" + if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then + local zones = {} + if squadron.primaryZone then table.insert(zones, "P:" .. squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, "S:" .. squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, "T:" .. squadron.tertiaryZone) end + zoneStatus = " Zones: " .. table.concat(zones, " ") + end + + -- Check if squadron is on cooldown + local cooldownStatus = "" + if squadronCooldowns.blue[squadron.templateName] then + local cooldownEnd = squadronCooldowns.blue[squadron.templateName] + if currentTime < cooldownEnd then + local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) + cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" + end + end + + local fullStatus = status .. aircraftStatus .. zoneStatus .. cooldownStatus + + if usable and cooldownStatus == "" and aircraftCount > 0 then + blueUsableCount = blueUsableCount + 1 + log("✓ " .. squadron.airbaseName .. " - " .. fullStatus) + else + log("✗ " .. squadron.airbaseName .. " - " .. fullStatus) + end + end + log("BLUE Status: " .. blueUsableCount .. "/" .. #BLUE_SQUADRON_CONFIG .. " airbases operational") + end +end + +-- Cleanup old delivery records to prevent memory buildup +local function cleanupOldDeliveries() + if _G.processedDeliveries then + local currentTime = timer.getTime() + local cleanupAge = 3600 -- Remove delivery records older than 1 hour + local removedCount = 0 + + for deliveryKey, timestamp in pairs(_G.processedDeliveries) do + if currentTime - timestamp > cleanupAge then + _G.processedDeliveries[deliveryKey] = nil + removedCount = removedCount + 1 + end + end + + if removedCount > 0 then + log("Cleaned up " .. removedCount .. " old cargo delivery records", true) + end + end +end + +-- System initialization +local function initializeSystem() + log("Universal Dual-Coalition TADC starting...") + + -- Create zones from late-activated helicopter units (MOOSE method) + -- This allows using helicopters named "RED_BORDER", "BLUE_BORDER" etc. as zone markers + -- Uses the helicopter's waypoints as polygon vertices (standard MOOSE method) + local function createZoneFromUnit(unitName) + -- Try to find as a group first (this is the standard MOOSE way) + local group = GROUP:FindByName(unitName) + if group then + -- Create polygon zone using the group's waypoints as vertices + local zone = ZONE_POLYGON:NewFromGroupName(unitName, unitName) + if zone then + log("Created polygon zone '" .. unitName .. "' from helicopter waypoints") + return zone + else + log("Warning: Could not create polygon zone from group '" .. unitName .. "' - check waypoints") + end + else + log("Warning: No group named '" .. unitName .. "' found for zone creation") + end + return nil + end + + -- Try to create zones for all configured zone names + local zoneNames = {} + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + if squadron.primaryZone then table.insert(zoneNames, squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zoneNames, squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zoneNames, squadron.tertiaryZone) end + end + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + if squadron.primaryZone then table.insert(zoneNames, squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zoneNames, squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zoneNames, squadron.tertiaryZone) end + end + + -- Create zones from helicopters + for _, zoneName in ipairs(zoneNames) do + if not ZONE:FindByName(zoneName) then + createZoneFromUnit(zoneName) + end + end + + -- Validate configuration + if not validateConfiguration() then + log("System startup aborted due to configuration errors!") + return false + end + + -- Log enabled coalitions + local enabledCoalitions = {} + if TADC_SETTINGS.enableRed then + table.insert(enabledCoalitions, "RED (" .. #RED_SQUADRON_CONFIG .. " squadrons)") + end + if TADC_SETTINGS.enableBlue then + table.insert(enabledCoalitions, "BLUE (" .. #BLUE_SQUADRON_CONFIG .. " squadrons)") + end + log("Enabled coalitions: " .. table.concat(enabledCoalitions, ", ")) + + -- Log initial squadron aircraft counts + if TADC_SETTINGS.enableRed then + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local count = squadronAircraftCounts.red[squadron.templateName] + log("Initial RED: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft") + end + end + + if TADC_SETTINGS.enableBlue then + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local count = squadronAircraftCounts.blue[squadron.templateName] + log("Initial BLUE: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft") + end + end + + -- Start schedulers + SCHEDULER:New(nil, detectThreats, {}, 5, TADC_SETTINGS.checkInterval) + SCHEDULER:New(nil, monitorInterceptors, {}, 10, TADC_SETTINGS.monitorInterval) + SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, TADC_SETTINGS.statusReportInterval) + SCHEDULER:New(nil, monitorCargoReplenishment, {}, 15, TADC_SETTINGS.cargoCheckInterval) + SCHEDULER:New(nil, cleanupOldDeliveries, {}, 60, 3600) -- Cleanup old delivery records every hour + + -- Start periodic squadron summary broadcast + SCHEDULER:New(nil, broadcastSquadronSummary, {}, 10, TADC_SETTINGS.squadronSummaryInterval) + + log("Universal Dual-Coalition TADC operational!") + log("RED Replenishment: " .. TADC_SETTINGS.red.cargoReplenishmentAmount .. " aircraft per cargo delivery") + log("BLUE Replenishment: " .. TADC_SETTINGS.blue.cargoReplenishmentAmount .. " aircraft per cargo delivery") + + return true +end + + +initializeSystem() + +-- Add F10 menu command for squadron summary +local menuRoot = MENU_MISSION:New("TADC Utilities") + +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Squadron Resource Summary", menuRoot, function() + local summary = getSquadronResourceSummary(coalition.side.RED) + MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Squadron Resource Summary", menuRoot, function() + local summary = getSquadronResourceSummary(coalition.side.BLUE) + MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE) +end) + +-- 1. Show Airbase Status Report +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Status Report", menuRoot, function() + local report = "=== RED Airbase Status ===\n" + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED) + local aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0 + local maxAircraft = squadron.aircraft + local cooldown = squadronCooldowns.red[squadron.templateName] + local cooldownStatus = "" + if cooldown then + local timeLeft = math.ceil((cooldown - timer.getTime()) / 60) + if timeLeft > 0 then cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" end + end + report = report .. string.format("%s: %s | Aircraft: %d/%d%s\n", squadron.displayName, status, aircraftCount, maxAircraft, cooldownStatus) + end + MESSAGE:New(report, 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Status Report", menuRoot, function() + local report = "=== BLUE Airbase Status ===\n" + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE) + local aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0 + local maxAircraft = squadron.aircraft + local cooldown = squadronCooldowns.blue[squadron.templateName] + local cooldownStatus = "" + if cooldown then + local timeLeft = math.ceil((cooldown - timer.getTime()) / 60) + if timeLeft > 0 then cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" end + end + report = report .. string.format("%s: %s | Aircraft: %d/%d%s\n", squadron.displayName, status, aircraftCount, maxAircraft, cooldownStatus) + end + MESSAGE:New(report, 20):ToCoalition(coalition.side.BLUE) +end) + +-- 2. Show Active Interceptors +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Active Interceptors", menuRoot, function() + local lines = {"Active RED Interceptors:"} + for name, data in pairs(activeInterceptors.red) do + if data and data.group and data.group:IsAlive() then + table.insert(lines, string.format("%s (Squadron: %s, Threat: %s)", name, data.displayName or data.squadron, assignedThreats.red[name] or "N/A")) + end + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Active Interceptors", menuRoot, function() + local lines = {"Active BLUE Interceptors:"} + for name, data in pairs(activeInterceptors.blue) do + if data and data.group and data.group:IsAlive() then + table.insert(lines, string.format("%s (Squadron: %s, Threat: %s)", name, data.displayName or data.squadron, assignedThreats.blue[name] or "N/A")) + end + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE) +end) + +-- 3. Show Threat Summary +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Threat Summary", menuRoot, function() + local lines = {"Detected BLUE Threats:"} + if cachedSets.blueAircraft then + cachedSets.blueAircraft:ForEach(function(group) + if group and group:IsAlive() then + table.insert(lines, string.format("%s (Size: %d)", group:GetName(), group:GetSize())) + end + end) + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRoot, function() + local lines = {"Detected RED Threats:"} + if cachedSets.redAircraft then + cachedSets.redAircraft:ForEach(function(group) + if group and group:IsAlive() then + table.insert(lines, string.format("%s (Size: %d)", group:GetName(), group:GetSize())) + end + end) + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE) +end) + +-- 4. Request Immediate Squadron Summary Broadcast +MENU_COALITION_COMMAND:New(coalition.side.RED, "Broadcast Squadron Summary Now", menuRoot, function() + local summary = getSquadronResourceSummary(coalition.side.RED) + MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Broadcast Squadron Summary Now", menuRoot, function() + local summary = getSquadronResourceSummary(coalition.side.BLUE) + MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE) +end) + +-- 5. Show Cargo Delivery Log +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Cargo Delivery Log", menuRoot, function() + local lines = {"Recent RED Cargo Deliveries:"} + if _G.processedDeliveries then + for key, timestamp in pairs(_G.processedDeliveries) do + if string.find(key, "RED") then + table.insert(lines, string.format("%s at %d", key, timestamp)) + end + end + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Cargo Delivery Log", menuRoot, function() + local lines = {"Recent BLUE Cargo Deliveries:"} + if _G.processedDeliveries then + for key, timestamp in pairs(_G.processedDeliveries) do + if string.find(key, "BLUE") then + table.insert(lines, string.format("%s at %d", key, timestamp)) + end + end + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE) +end) + +-- 6. Show Zone Coverage Map +MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Zone Coverage Map", menuRoot, function() + local lines = {"RED Zone Coverage:"} + for _, squadron in pairs(RED_SQUADRON_CONFIG) do + local zones = {} + if squadron.primaryZone then table.insert(zones, "Primary: " .. squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, "Secondary: " .. squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, "Tertiary: " .. squadron.tertiaryZone) end + table.insert(lines, string.format("%s: %s", squadron.displayName, table.concat(zones, ", "))) + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED) +end) + +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Zone Coverage Map", menuRoot, function() + local lines = {"BLUE Zone Coverage:"} + for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do + local zones = {} + if squadron.primaryZone then table.insert(zones, "Primary: " .. squadron.primaryZone) end + if squadron.secondaryZone then table.insert(zones, "Secondary: " .. squadron.secondaryZone) end + if squadron.tertiaryZone then table.insert(zones, "Tertiary: " .. squadron.tertiaryZone) end + table.insert(lines, string.format("%s: %s", squadron.displayName, table.concat(zones, ", "))) + end + MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE) +end) + +-- 7. Request Emergency Cleanup (admin/global) +MENU_MISSION_COMMAND:New("Emergency Cleanup Interceptors", menuRoot, function() + local cleaned = 0 + for _, interceptors in pairs(activeInterceptors.red) do + if interceptors and interceptors.group and not interceptors.group:IsAlive() then + interceptors.group = nil + cleaned = cleaned + 1 + end + end + for _, interceptors in pairs(activeInterceptors.blue) do + if interceptors and interceptors.group and not interceptors.group:IsAlive() then + interceptors.group = nil + cleaned = cleaned + 1 + end + end + MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToAll() +end) + +-- 9. Show System Uptime/Status +local systemStartTime = timer.getTime() +MENU_MISSION_COMMAND:New("Show TADC System Status", menuRoot, function() + local uptime = math.floor((timer.getTime() - systemStartTime) / 60) + local status = string.format("TADC System Uptime: %d minutes\nCheck Interval: %ds\nMonitor Interval: %ds\nStatus Report Interval: %ds\nSquadron Summary Interval: %ds\nCargo Check Interval: %ds", uptime, TADC_SETTINGS.checkInterval, TADC_SETTINGS.monitorInterval, TADC_SETTINGS.statusReportInterval, TADC_SETTINGS.squadronSummaryInterval, TADC_SETTINGS.cargoCheckInterval) + MESSAGE:New(status, 20):ToAll() +end) + + + diff --git a/Moose_TADC/Moose_TADC_Load2nd.lua b/Moose_TADC/Moose_TADC_Load2nd.lua index 3507b2c..dfe2b52 100644 --- a/Moose_TADC/Moose_TADC_Load2nd.lua +++ b/Moose_TADC/Moose_TADC_Load2nd.lua @@ -164,8 +164,6 @@ local TADC_SETTINGS = { }, } --- Load squadron configs from external file --- RED_SQUADRON_CONFIG and BLUE_SQUADRON_CONFIG are now global, loaded by the trigger --[[ INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft: @@ -235,6 +233,8 @@ local ADVANCED_SETTINGS = { ═══════════════════════════════════════════════════════════════════════════════ ]] + + -- Internal tracking variables - separate for each coalition local activeInterceptors = { red = {}, @@ -257,6 +257,13 @@ local squadronAircraftCounts = { blue = {} } +-- Logging function +local function log(message, detailed) + if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then + env.info(ADVANCED_SETTINGS.logPrefix .. " " .. message) + end +end + -- Performance optimization: Cache SET_GROUP objects to avoid repeated creation local cachedSets = { redCargo = nil, @@ -265,7 +272,17 @@ local cachedSets = { blueAircraft = nil } --- Initialize squadron aircraft counts for both coalitions +if type(RED_SQUADRON_CONFIG) ~= "table" then + local msg = "CONFIG ERROR: RED_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs_Load1st.lua is loaded before this script." + log(msg, true) + MESSAGE:New(msg, 30):ToAll() +end +if type(BLUE_SQUADRON_CONFIG) ~= "table" then + local msg = "CONFIG ERROR: BLUE_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs_Load1st.lua is loaded before this script." + log(msg, true) + MESSAGE:New(msg, 30):ToAll() +end + for _, squadron in pairs(RED_SQUADRON_CONFIG) do if squadron.aircraft and squadron.templateName then squadronAircraftCounts.red[squadron.templateName] = squadron.aircraft @@ -278,12 +295,7 @@ for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do end end --- Logging function -local function log(message, detailed) - if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then - env.info(ADVANCED_SETTINGS.logPrefix .. " " .. message) - end -end + -- Squadron resource summary generator diff --git a/Moose_TADC/TADC_Example.miz b/Moose_TADC/TADC_Example.miz index 1e17c916e768034d38ad45543406ced8910e59c5..b99ac0c199c326d6f48a04b4009ffe7a41c3d9aa 100644 GIT binary patch delta 28882 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⁡FZtxl#o(ji0Rw=CMiY)%477MGwonUKvPo2S;^K> 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 delta 28751 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

srVmv8+oWojkN;80fBLG*x(GOE zh7%}HxOJ7Jm*mRV-NwctXt;gXG#rh27J*rp&3y4?ZNp(~7t7N>&`d^FF`m4J`N#vw zlbfMdC4MuqF-qfBYp>dC8T;ieTc>U(p+vyoHG{&5r@R`L0cNxhukJ@!QVKZfke@f-V&&(efK*5J#uxn8LlI9}ZDeTqTrZqCSRjk<=ZWTU% zl@Dm}AAMGA;b@HDb+5tK!O}-Xv<`j;ABrk=*2jh$(D~9%k3kqjyU QPnU0z3>pTGjtl?*0E^hRmjD0&