diff --git a/DCS_Kola/Operation_Polar_Shield/CTLD.lua b/DCS_Kola/Operation_Polar_Shield/CTLD.lua index 06103bd..75a9473 100644 --- a/DCS_Kola/Operation_Polar_Shield/CTLD.lua +++ b/DCS_Kola/Operation_Polar_Shield/CTLD.lua @@ -511,22 +511,16 @@ ctld.pickupZones = { { "London FARP Supply", "blue", -1, "yes", 0 }, { "Dallas FARP Supply", "blue", -1, "yes", 0 }, { "Paris FARP Supply", "blue", -1, "yes", 0 }, - { "pickzone6", "none", -1, "yes", 0 }, - { "pickzone7", "none", -1, "yes", 0 }, - { "pickzone8", "none", -1, "yes", 0 }, - { "pickzone9", "none", 5, "yes", 1 }, -- limits pickup zone 9 to 5 groups of soldiers or vehicles, only red can pick up - { "pickzone10", "none", 10, "yes", 2 }, -- limits pickup zone 10 to 10 groups of soldiers or vehicles, only blue can pick up - - { "pickzone11", "blue", 20, "no", 2 }, -- limits pickup zone 11 to 20 groups of soldiers or vehicles, only blue can pick up. Zone starts inactive! - { "pickzone12", "red", 20, "no", 1 }, -- limits pickup zone 11 to 20 groups of soldiers or vehicles, only blue can pick up. Zone starts inactive! - { "pickzone13", "none", -1, "yes", 0 }, - { "pickzone14", "none", -1, "yes", 0 }, - { "pickzone15", "none", -1, "yes", 0 }, - { "pickzone16", "none", -1, "yes", 0 }, - { "pickzone17", "none", -1, "yes", 0 }, - { "pickzone18", "none", -1, "yes", 0 }, - { "pickzone19", "none", 5, "yes", 0 }, - { "pickzone20", "none", 10, "yes", 0, 1000 }, -- optional extra flag number to store the current number of groups available in + { "Kilpyavr Supply", "none", -1, "yes", 0 }, + { "Severomorsk-1 Supply", "none", -1, "yes", 0 }, + { "Severomorsk-3 Supply", "none", -1, "yes", 0 }, + { "Murmansk Supply", "none", -1, "yes", 0 }, + { "Olenya Supply", "none", -1, "yes", 0 }, + { "Monchegorsk Supply", "none", -1, "yes", 0 }, + { "Afrikanda Supply", "none", -1, "yes", 0 }, + { "Koshka Supply", "none", -1, "yes", 0 }, + { "Alakurtti Supply", "blue", -1, "yes", 0}, + { "CVN-72 Abraham Lincoln", "none", -1, "yes", 0, 1001 }, -- instead of a Zone Name you can also use the UNIT NAME of a ship } @@ -651,16 +645,21 @@ ctld.extractableGroups = { -- Use any of the predefined names or set your own ones -- When a logistic unit is destroyed, you will no longer be able to spawn crates ctld.logisticUnits = { - "logistic1", - "logistic2", - "logistic3", - "logistic4", - "logistic5", - "logistic6", - "logistic7", - "logistic8", - "logistic9", - "logistic10", + "Luostari Supply", + "Ivalo Supply", + "London FARP Supply", + "Paris FARP Supply", + "Dallas FARP Supply", + "Kilpyavr Supply", + "Severomorsk-1 Supply", + "Severomorsk-3 Supply", + "Koshka Supply", + "Murmansk Supply", + "Olenya Supply", + "Monchegorsk Supply", + "Afrikanda Supply", + "Alakurtti Supply" + } -- ************** UNITS ABLE TO TRANSPORT VEHICLES ****************** diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.2.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.2.miz index 91a2a1a..80e033a 100644 Binary files a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.2.miz and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.2.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.3.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.3.miz new file mode 100644 index 0000000..a234cea Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.3.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.4.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.4.miz new file mode 100644 index 0000000..81507eb Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.4.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua index 864e6df..de6e51d 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua @@ -24,112 +24,335 @@ end -- Red Airbases (from TADC configuration) -env.info("[DEBUG] Initializing Capture Zone: Kilpyavr") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Kilpyavr") CaptureZone_Kilpyavr = ZONE:New( "Capture Kilpyavr" ) ZoneCapture_Kilpyavr = ZONE_CAPTURE_COALITION:New( CaptureZone_Kilpyavr, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Kilpyavr:__Guard( 1 ) ZoneCapture_Kilpyavr:Start( 30, 30 ) -env.info("[DEBUG] Kilpyavr zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Kilpyavr zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Severomorsk-1") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Severomorsk-1") CaptureZone_Severomorsk_1 = ZONE:New( "Capture Severomorsk-1" ) ZoneCapture_Severomorsk_1 = ZONE_CAPTURE_COALITION:New( CaptureZone_Severomorsk_1, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Severomorsk_1:__Guard( 1 ) ZoneCapture_Severomorsk_1:Start( 30, 30 ) -env.info("[DEBUG] Severomorsk-1 zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Severomorsk-1 zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Severomorsk-3") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Severomorsk-3") CaptureZone_Severomorsk_3 = ZONE:New( "Capture Severomorsk-3" ) ZoneCapture_Severomorsk_3 = ZONE_CAPTURE_COALITION:New( CaptureZone_Severomorsk_3, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Severomorsk_3:__Guard( 1 ) ZoneCapture_Severomorsk_3:Start( 30, 30 ) -env.info("[DEBUG] Severomorsk-3 zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Severomorsk-3 zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Murmansk International") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Murmansk International") CaptureZone_Murmansk_International = ZONE:New( "Capture Murmansk International" ) ZoneCapture_Murmansk_International = ZONE_CAPTURE_COALITION:New( CaptureZone_Murmansk_International, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Murmansk_International:__Guard( 1 ) ZoneCapture_Murmansk_International:Start( 30, 30 ) -env.info("[DEBUG] Murmansk International zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Murmansk International zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Monchegorsk") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Monchegorsk") CaptureZone_Monchegorsk = ZONE:New( "Capture Monchegorsk" ) ZoneCapture_Monchegorsk = ZONE_CAPTURE_COALITION:New( CaptureZone_Monchegorsk, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Monchegorsk:__Guard( 1 ) ZoneCapture_Monchegorsk:Start( 30, 30 ) -env.info("[DEBUG] Monchegorsk zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Monchegorsk zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Olenya") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Olenya") CaptureZone_Olenya = ZONE:New( "Capture Olenya" ) ZoneCapture_Olenya = ZONE_CAPTURE_COALITION:New( CaptureZone_Olenya, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Olenya:__Guard( 1 ) ZoneCapture_Olenya:Start( 30, 30 ) -env.info("[DEBUG] Olenya zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Olenya zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: Afrikanda") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: Afrikanda") CaptureZone_Afrikanda = ZONE:New( "Capture Afrikanda" ) ZoneCapture_Afrikanda = ZONE_CAPTURE_COALITION:New( CaptureZone_Afrikanda, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_Afrikanda:__Guard( 1 ) ZoneCapture_Afrikanda:Start( 30, 30 ) -env.info("[DEBUG] Afrikanda zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] Afrikanda zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: The Mountain") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: The Mountain") CaptureZone_The_Mountain = ZONE:New( "Capture The Mountain" ) ZoneCapture_The_Mountain = ZONE_CAPTURE_COALITION:New( CaptureZone_The_Mountain, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_The_Mountain:__Guard( 1 ) ZoneCapture_The_Mountain:Start( 30, 30 ) -env.info("[DEBUG] The Mountain zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] The Mountain zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: The River") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: The River") CaptureZone_The_River = ZONE:New( "Capture The River" ) ZoneCapture_The_River = ZONE_CAPTURE_COALITION:New( CaptureZone_The_River, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_The_River:__Guard( 1 ) ZoneCapture_The_River:Start( 30, 30 ) -env.info("[DEBUG] The River zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] The River zone initialization complete") -env.info("[DEBUG] Initializing Capture Zone: The Gulf") +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: The Gulf") CaptureZone_The_Gulf = ZONE:New( "Capture The Gulf" ) ZoneCapture_The_Gulf = ZONE_CAPTURE_COALITION:New( CaptureZone_The_Gulf, coalition.side.RED ) -- SetMarkReadOnly method not available in this MOOSE version - feature disabled ZoneCapture_The_Gulf:__Guard( 1 ) ZoneCapture_The_Gulf:Start( 30, 30 ) -env.info("[DEBUG] The Gulf zone initialization complete") +env.info("[CAPTURE Module] [DEBUG] The Gulf zone initialization complete") + +env.info("[CAPTURE Module] [DEBUG] Initializing Capture Zone: The Lakes") +CaptureZone_The_Lakes = ZONE:New( "Capture The Lakes" ) +ZoneCapture_The_Lakes = ZONE_CAPTURE_COALITION:New( CaptureZone_The_Lakes, coalition.side.RED ) +-- SetMarkReadOnly method not available in this MOOSE version - feature disabled +ZoneCapture_The_Lakes:__Guard( 1 ) +ZoneCapture_The_Lakes:Start( 30, 30 ) +env.info("[CAPTURE Module] [DEBUG] The Lakes zone initialization complete") +-- Helper functions for tactical information +local function GetZoneForceStrengths(ZoneCapture) + local zone = ZoneCapture:GetZone() + if not zone then return {red = 0, blue = 0, neutral = 0} end + + local redCount = 0 + local blueCount = 0 + local neutralCount = 0 + + -- Simple approach: scan all units and check if they're in the zone + local coord = zone:GetCoordinate() + local radius = zone:GetRadius() or 1000 + + local allUnits = SET_UNIT:New():FilterStart() + allUnits:ForEachUnit(function(unit) + if unit and unit:IsAlive() then + local unitCoord = unit:GetCoordinate() + if unitCoord and coord:Get2DDistance(unitCoord) <= radius then + local unitCoalition = unit:GetCoalition() + if unitCoalition == coalition.side.RED then + redCount = redCount + 1 + elseif unitCoalition == coalition.side.BLUE then + blueCount = blueCount + 1 + elseif unitCoalition == coalition.side.NEUTRAL then + neutralCount = neutralCount + 1 + end + end + end + end) + + env.info(string.format("[CAPTURE Module] [TACTICAL] Zone %s scan result: R:%d B:%d N:%d", + ZoneCapture:GetZoneName(), redCount, blueCount, neutralCount)) + + return { + red = redCount, + blue = blueCount, + neutral = neutralCount + } +end + +local function GetRedUnitMGRSCoords(ZoneCapture) + local zone = ZoneCapture:GetZone() + if not zone then return {} end + + local coords = {} + local units = nil + + -- Try multiple methods to get units (same as GetZoneForceStrengths) + local success1 = pcall(function() + units = zone:GetScannedUnits() + end) + + if not success1 or not units then + local success2 = pcall(function() + local unitSet = SET_UNIT:New():FilterZones({zone}):FilterStart() + units = {} + unitSet:ForEachUnit(function(unit) + if unit then + units[unit:GetName()] = unit + end + end) + end) + end + + if not units then + local success3 = pcall(function() + units = {} + local unitSet = SET_UNIT:New():FilterZones({zone}):FilterStart() + unitSet:ForEachUnit(function(unit) + if unit and unit:IsInZone(zone) then + units[unit:GetName()] = unit + end + end) + end) + end + + -- Extract RED unit coordinates + if units then + for unitName, unit in pairs(units) do + -- Enhanced nil checking and safe method calls + if unit and type(unit) == "table" and unit.IsAlive then + local isAlive = false + local success_alive = pcall(function() + isAlive = unit:IsAlive() + end) + + if success_alive and isAlive then + local coalition_side = nil + local success_coalition = pcall(function() + coalition_side = unit:GetCoalition() + end) + + if success_coalition and coalition_side == coalition.side.RED then + local coord = unit:GetCoordinate() + if coord then + local success, mgrs = pcall(function() + return coord:ToStringMGRS(5) -- 5-digit precision + end) + + if success and mgrs then + table.insert(coords, { + name = unit:GetName(), + type = unit:GetTypeName(), + mgrs = mgrs + }) + end + end + end + end + end + end + end + + env.info(string.format("[CAPTURE Module] [TACTICAL] Found %d RED units with coordinates in %s", + #coords, ZoneCapture:GetZoneName())) + + return coords +end + +local function CreateTacticalInfoMarker(ZoneCapture) + local zone = ZoneCapture:GetZone() + if not zone then return end + + local forces = GetZoneForceStrengths(ZoneCapture) + local zoneName = ZoneCapture:GetZoneName() + + -- Build tactical info text + local tacticalText = string.format("TACTICAL: %s\nForces: R:%d B:%d", + zoneName, forces.red, forces.blue) + + if forces.neutral > 0 then + tacticalText = tacticalText .. string.format(" C:%d", forces.neutral) + end + + -- Add MGRS coordinates if RED forces <= 10 + if forces.red > 0 and forces.red <= 10 then + local redCoords = GetRedUnitMGRSCoords(ZoneCapture) + if #redCoords > 0 then + tacticalText = tacticalText .. "\n\nRED UNITS:" + for i, unit in ipairs(redCoords) do + if i <= 6 then -- Limit to 6 entries to avoid text overflow + -- Shorten unit type names to fit better + local shortType = unit.type:gsub("^%w+%-", ""):gsub("%s.*", "") + tacticalText = tacticalText .. string.format("\n%s: %s", shortType, unit.mgrs) + end + end + if #redCoords > 6 then + tacticalText = tacticalText .. string.format("\n+%d more", #redCoords - 6) + end + end + end + + -- Create tactical marker offset from zone center + local coord = zone:GetCoordinate() + if coord then + -- Offset the tactical marker slightly northeast of the main zone marker + local offsetCoord = coord:Translate(200, 45) -- 200m northeast + + -- Remove any existing tactical marker first + if ZoneCapture.TacticalMarkerID then + env.info(string.format("[CAPTURE Module] [TACTICAL] Removing old marker ID %d for %s", ZoneCapture.TacticalMarkerID, zoneName)) + -- Try multiple removal methods + local success1 = pcall(function() + offsetCoord:RemoveMark(ZoneCapture.TacticalMarkerID) + end) + if not success1 then + local success2 = pcall(function() + trigger.action.removeMark(ZoneCapture.TacticalMarkerID) + end) + if not success2 then + -- Try using coordinate removal + pcall(function() + coord:RemoveMark(ZoneCapture.TacticalMarkerID) + end) + end + end + ZoneCapture.TacticalMarkerID = nil + end + + -- Create new tactical marker for BLUE coalition only + local success, markerID = pcall(function() + return offsetCoord:MarkToCoalition(tacticalText, coalition.side.BLUE) + end) + + if success and markerID then + ZoneCapture.TacticalMarkerID = markerID + + -- Try to make the marker read-only (if available in this MOOSE version) + pcall(function() + offsetCoord:SetMarkReadOnly(markerID, true) + end) + + env.info(string.format("[CAPTURE Module] [TACTICAL] Created read-only marker for %s with %d RED, %d BLUE units", zoneName, forces.red, forces.blue)) + else + env.info(string.format("[CAPTURE Module] [TACTICAL] Failed to create marker for %s", zoneName)) + end + end +end + -- Event handler functions - define them separately for each zone local function OnEnterGuarded(ZoneCapture, From, Event, To) if From ~= To then local Coalition = ZoneCapture:GetCoalition() if Coalition == coalition.side.BLUE then ZoneCapture:Smoke( SMOKECOLOR.Blue ) + -- Update zone visual markers to BLUE + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue zone boundary US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) else ZoneCapture:Smoke( SMOKECOLOR.Red ) + -- Update zone visual markers to RED + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red zone boundary RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) end + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) end end local function OnEnterEmpty(ZoneCapture) ZoneCapture:Smoke( SMOKECOLOR.Green ) + -- Update zone visual markers to GREEN (neutral) + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green zone boundary US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) end local function OnEnterAttacked(ZoneCapture) ZoneCapture:Smoke( SMOKECOLOR.White ) + -- Update zone visual markers to ORANGE (contested) + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {1, 0.5, 0}, 0.5, {1, 0.5, 0}, 0.2, 2, true) -- Orange zone boundary for contested local Coalition = ZoneCapture:GetCoalition() if Coalition == coalition.side.BLUE then US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) @@ -138,8 +361,25 @@ local function OnEnterAttacked(ZoneCapture) RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) end + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) end +-- Apply event handlers to all zone capture objects +local zoneCaptureObjects = { + ZoneCapture_Kilpyavr, + ZoneCapture_Severomorsk_1, + ZoneCapture_Severomorsk_3, + ZoneCapture_Murmansk_International, + ZoneCapture_Monchegorsk, + ZoneCapture_Olenya, + ZoneCapture_Afrikanda, + ZoneCapture_The_Mountain, + ZoneCapture_The_River, + ZoneCapture_The_Gulf, + ZoneCapture_The_Lakes +} + -- Victory condition monitoring local function CheckVictoryCondition() local blueZonesCount = 0 @@ -151,11 +391,11 @@ local function CheckVictoryCondition() end end - env.info(string.format("[VICTORY CHECK] Blue owns %d/%d zones", blueZonesCount, totalZones)) + env.info(string.format("[CAPTURE Module] [VICTORY CHECK] Blue owns %d/%d zones", blueZonesCount, totalZones)) if blueZonesCount >= totalZones then -- All zones captured by BLUE - trigger victory condition - env.info("[VICTORY] All zones captured by BLUE! Triggering victory sequence...") + env.info("[CAPTURE Module] [VICTORY] All zones captured by BLUE! Triggering victory sequence...") -- Victory messages US_CC:MessageTypeToCoalition( @@ -185,7 +425,7 @@ local function CheckVictoryCondition() -- Schedule mission end after 60 seconds SCHEDULER:New( nil, function() - env.info("[VICTORY] Ending mission due to complete zone capture by BLUE") + env.info("[CAPTURE Module] [VICTORY] Ending mission due to complete zone capture by BLUE") -- You can trigger specific end-mission logic here -- For example: trigger.action.setUserFlag("MissionComplete", 1) -- Or call specific mission ending functions @@ -211,9 +451,15 @@ end local function OnEnterCaptured(ZoneCapture) local Coalition = ZoneCapture:GetCoalition() if Coalition == coalition.side.BLUE then + -- Update zone visual markers to BLUE for captured + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue zone boundary RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) else + -- Update zone visual markers to RED for captured + ZoneCapture:UndrawZone() + ZoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red zone boundary US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information ) end @@ -221,28 +467,18 @@ local function OnEnterCaptured(ZoneCapture) ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", 200 ) ZoneCapture:__Guard( 30 ) + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) + -- Check victory condition after any zone capture CheckVictoryCondition() end --- Apply event handlers to all zone capture objects -local zoneCaptureObjects = { - ZoneCapture_Kilpyavr, - ZoneCapture_Severomorsk_1, - ZoneCapture_Severomorsk_3, - ZoneCapture_Murmansk_International, - ZoneCapture_Monchegorsk, - ZoneCapture_Olenya, - ZoneCapture_Afrikanda, - ZoneCapture_The_Mountain, - ZoneCapture_The_River, - ZoneCapture_The_Gulf -} - -- Set up event handlers for each zone with proper MOOSE methods and debugging local zoneNames = { "Kilpyavr", "Severomorsk-1", "Severomorsk-3", "Murmansk International", - "Monchegorsk", "Olenya", "Afrikanda", "The Mountain", "The River", "The Gulf" + "Monchegorsk", "Olenya", "Afrikanda", "The Mountain", "The River", "The Gulf", + "The Lakes" } for i, zoneCapture in ipairs(zoneCaptureObjects) do @@ -258,54 +494,54 @@ for i, zoneCapture in ipairs(zoneCaptureObjects) do -- Debug: Check if the underlying zone exists local success, zone = pcall(function() return zoneCapture:GetZone() end) if success and zone then - env.info("✓ Zone 'Capture " .. zoneName .. "' successfully created and linked") + env.info("[CAPTURE Module] ✓ Zone 'Capture " .. zoneName .. "' successfully created and linked") - -- Try to make zone borders visible with different approach + -- Initialize zone borders with initial RED color (all zones start as RED coalition) local drawSuccess, drawError = pcall(function() - zone:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) + zone:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red initial boundary end) if not drawSuccess then - env.info("⚠ Zone 'Capture " .. zoneName .. "' border drawing failed: " .. tostring(drawError)) + env.info("[CAPTURE Module] ⚠ Zone 'Capture " .. zoneName .. "' border drawing failed: " .. tostring(drawError)) -- Alternative: Try simpler zone marking pcall(function() zone:SmokeZone(SMOKECOLOR.Red, 30) end) else - env.info("✓ Zone 'Capture " .. zoneName .. "' border drawn successfully") + env.info("[CAPTURE Module] ✓ Zone 'Capture " .. zoneName .. "' border drawn successfully with RED initial color") end else - env.info("✗ ERROR: Zone 'Capture " .. zoneName .. "' not found in mission editor!") - env.info(" Make sure you have a trigger zone named exactly: 'Capture " .. zoneName .. "'") + env.info("[CAPTURE Module] ✗ ERROR: Zone 'Capture " .. zoneName .. "' not found in mission editor!") + env.info("[CAPTURE Module] Make sure you have a trigger zone named exactly: 'Capture " .. zoneName .. "'") end else - env.info("✗ ERROR: Zone capture object " .. i .. " (" .. (zoneNames[i] or "Unknown") .. ") is nil!") + env.info("[CAPTURE Module] ✗ ERROR: Zone capture object " .. i .. " (" .. (zoneNames[i] or "Unknown") .. ") is nil!") end end -- Additional specific check for Olenya -env.info("=== OLENYA SPECIFIC DEBUG ===") +env.info("[CAPTURE Module] === OLENYA SPECIFIC DEBUG ===") if ZoneCapture_Olenya then - env.info("✓ ZoneCapture_Olenya object exists") + env.info("[CAPTURE Module] ✓ ZoneCapture_Olenya object exists") local success, result = pcall(function() return ZoneCapture_Olenya:GetZoneName() end) if success then - env.info("✓ Zone name: " .. tostring(result)) + env.info("[CAPTURE Module] ✓ Zone name: " .. tostring(result)) else - env.info("✗ Could not get zone name: " .. tostring(result)) + env.info("[CAPTURE Module] ✗ Could not get zone name: " .. tostring(result)) end local success2, zone = pcall(function() return ZoneCapture_Olenya:GetZone() end) if success2 and zone then - env.info("✓ Underlying zone object exists") + env.info("[CAPTURE Module] ✓ Underlying zone object exists") local coord = zone:GetCoordinate() if coord then - env.info("✓ Zone coordinate: " .. coord:ToStringLLDMS()) + env.info("[CAPTURE Module] ✓ Zone coordinate: " .. coord:ToStringLLDMS()) end else - env.info("✗ Underlying zone object missing: " .. tostring(zone)) + env.info("[CAPTURE Module] ✗ Underlying zone object missing: " .. tostring(zone)) end else - env.info("✗ ZoneCapture_Olenya object is nil!") + env.info("[CAPTURE Module] ✗ ZoneCapture_Olenya object is nil!") end -- ========================================== @@ -330,15 +566,32 @@ local function GetZoneOwnershipStatus() local zoneCoalition = zoneCapture:GetCoalition() local zoneName = zoneNames[i] or ("Zone " .. i) + -- Get the current state of the zone + local currentState = zoneCapture:GetCurrentState() + local stateString = "" + + -- Determine status based on coalition and state if zoneCoalition == coalitionTable.side.BLUE then status.blue = status.blue + 1 - status.zones[zoneName] = "BLUE" + if currentState == "Attacked" then + status.zones[zoneName] = "BLUE (Under Attack)" + else + status.zones[zoneName] = "BLUE" + end elseif zoneCoalition == coalitionTable.side.RED then status.red = status.red + 1 - status.zones[zoneName] = "RED" + if currentState == "Attacked" then + status.zones[zoneName] = "RED (Under Attack)" + else + status.zones[zoneName] = "RED" + end else status.neutral = status.neutral + 1 - status.zones[zoneName] = "NEUTRAL" + if currentState == "Attacked" then + status.zones[zoneName] = "NEUTRAL (Under Attack)" + else + status.zones[zoneName] = "NEUTRAL" + end end end end @@ -372,7 +625,7 @@ local function BroadcastZoneStatus() US_CC:MessageTypeToCoalition( fullMessage, MESSAGE.Type.Information, 15 ) - env.info("[ZONE STATUS] " .. reportMessage:gsub("\n", " | ")) + env.info("[CAPTURE Module] [ZONE STATUS] " .. reportMessage:gsub("\n", " | ")) return status end @@ -398,6 +651,73 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function() end, {}, 10, 300 ) -- Start after 10 seconds, repeat every 300 seconds (5 minutes) +-- Periodic zone color verification system (every 2 minutes) +local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function() + env.info("[CAPTURE Module] [ZONE COLORS] Running periodic zone color verification...") + + -- Verify each zone's visual marker matches its coalition + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneCoalition = zoneCapture:GetCoalition() + local zoneName = zoneNames[i] or ("Zone " .. i) + + -- Force redraw the zone with correct color (helps prevent desync issues) + zoneCapture:UndrawZone() + + if zoneCoalition == coalition.side.BLUE then + zoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue + elseif zoneCoalition == coalition.side.RED then + zoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red + else + zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green (neutral) + end + end + end + +end, {}, 60, 120 ) -- Start after 60 seconds, repeat every 120 seconds (2 minutes) + +-- Periodic tactical marker update system (every 1 minute) +local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function() + env.info("[CAPTURE Module] [TACTICAL] Running periodic tactical marker update...") + + -- Update tactical markers for all zones + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + CreateTacticalInfoMarker(zoneCapture) + end + end + +end, {}, 30, 60 ) -- Start after 30 seconds, repeat every 60 seconds (1 minute) + +-- Function to refresh all zone colors based on current ownership +local function RefreshAllZoneColors() + env.info("[CAPTURE Module] [ZONE COLORS] Refreshing all zone visual markers...") + + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local coalition = zoneCapture:GetCoalition() + local zoneName = zoneNames[i] or ("Zone " .. i) + + -- Clear existing drawings + zoneCapture:UndrawZone() + + -- Redraw with correct color based on current coalition + if coalition == coalition.side.BLUE then + zoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue + env.info(string.format("[CAPTURE Module] [ZONE COLORS] %s: Set to BLUE", zoneName)) + elseif coalition == coalition.side.RED then + zoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red + env.info(string.format("[CAPTURE Module] [ZONE COLORS] %s: Set to RED", zoneName)) + else + zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green (neutral) + env.info(string.format("[CAPTURE Module] [ZONE COLORS] %s: Set to NEUTRAL/GREEN", zoneName)) + end + end + end + + US_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, 5) +end + -- Manual zone status command for players (F10 radio menu) local function SetupZoneStatusCommands() -- Add F10 radio menu commands for zone status @@ -426,21 +746,24 @@ local function SetupZoneStatusCommands() MESSAGE.Type.Information, 10 ) end ) + + -- Add command to refresh zone colors (troubleshooting tool) + MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Refresh Zone Colors", USMenu, RefreshAllZoneColors ) end end -- Initialize zone status monitoring SCHEDULER:New( nil, function() - env.info("[VICTORY SYSTEM] Initializing zone monitoring system...") + env.info("[CAPTURE Module] [VICTORY SYSTEM] Initializing zone monitoring system...") SetupZoneStatusCommands() -- Initial status report SCHEDULER:New( nil, function() - env.info("[VICTORY SYSTEM] Broadcasting initial zone status...") + env.info("[CAPTURE Module] [VICTORY SYSTEM] Broadcasting initial zone status...") BroadcastZoneStatus() end, {}, 30 ) -- Initial report after 30 seconds end, {}, 5 ) -- Initialize after 5 seconds -env.info("[VICTORY SYSTEM] Zone capture victory monitoring system loaded successfully!") +env.info("[CAPTURE Module] [VICTORY SYSTEM] Zone capture victory monitoring system loaded successfully!") diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua index 0f555af..972d6f6 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua @@ -39,7 +39,7 @@ end local redHQ = GROUP:FindByName("REDHQ") if redHQ then RU_CC = COMMANDCENTER:New(redHQ, "Russia HQ") - RU_Mission = MISSION:New(RU_CC, "Operation Polar Shield", "Primary", "Destroy the City of Ushuaia and its supporting FARPS", coalition.side.RED) + RU_Mission = MISSION:New(RU_CC, "Operation Polar Shield", "Primary", "Hold what we have, take what we don't.", coalition.side.RED) --RU_Score = SCORING:New("Operation Polar Shield") --RU_Mission:AddScoring(RU_Score) RU_Mission:Start() diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua index 85528d8..0b5cf29 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua @@ -24,67 +24,72 @@ -- TACTICAL AIR DEFENSE CONTROLLER (TADC) CONFIGURATION -- ================================================================================================ --- GCI Configuration - Define at top level for global access +-- ================================================================================================ +-- 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 = { - -- Threat Response Parameters - threatRatio = 1, -- Send 1x defenders per attacker - maxSimultaneousCAP = 12, -- Maximum total airborne aircraft - reservePercent = 0.25, -- Keep 25% of forces in reserve - responseDelay = 23, -- Seconds to assess threat before scrambling + -- Basic Response Parameters + threatRatio = 1.5, -- Send 1.5x defenders per attacker + maxSimultaneousCAP = 12, -- Maximum total airborne aircraft at once - -- Supply Management - supplyMode = "FINITE", -- "FINITE" or "INFINITE" aircraft spawning - defaultSquadronSize = 25, -- Aircraft per squadron if not specified + -- EWR Detection + useEWRDetection = true, -- Use EWR radar for detection + ewrDetectionRadius = 50000, -- EWR detection radius (50km) - -- Detection Parameters - useEWRDetection = true, -- Use realistic EWR-based detection (true) or omniscient detection (false) - ewrDetectionRadius = 30000, -- EWR detection radius in meters (30km) - ewrUpdateInterval = 10, -- EWR detection update interval in seconds - threatTimeout = 300, -- Remove threats not seen for 5 minutes - minThreatDistance = 50000, -- Minimum 50km to consider threat + -- 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 - -- Force Sizing Rules - fighterVsFighter = 1.0, -- Multiplier for fighter threats (reduced from 1.5) - fighterVsBomber = 1.2, -- Multiplier for bomber threats (reduced from 2.0) - fighterVsHelicopter = 0.8, -- Multiplier for helicopter threats + -- 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 Behavior Parameters - capOrbitRadius = 30000, -- CAP orbit radius in meters (30km) - capEngagementRange = 35000, -- Maximum engagement range in meters (35km) - capZoneConstraint = true, -- Keep CAP flights within their patrol zones + -- 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 - -- AI Patrol Parameters - AI_PATROL_TIME = 300, -- Time aircraft patrol one area before moving (5 minutes) - patrolAreaRadius = 15000, -- Radius of individual patrol areas in meters (15km) - minPatrolSeparation = 25000, -- Minimum distance between patrol areas (25km) + -- Spawn Parameters + spawnDistanceMin = 5000, -- Minimum spawn distance from airbase (5km) + spawnDistanceMax = 15000, -- Maximum spawn distance from airbase (15km) - -- Squadron Operational Parameters - squadronCooldown = 1800, -- Seconds between squadron launches (30 minutes - increased from 15) - maxAircraftPerMission = 1, -- Maximum aircraft per squadron per mission (reduced from 2) + -- 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) - -- Spawn Positioning - spawnDistanceMin = 1000, -- Minimum spawn distance from airbase (meters) - spawnDistanceMax = 3000, -- Maximum spawn distance from airbase (meters) - takeoffDistance = 5000, -- Distance for ground spawn takeoff positioning (meters) + -- Status and Monitoring + statusReportInterval = 300, -- Status report every 5 minutes + engagementUpdateInterval = 30, -- Update engagements every 30 seconds - -- Mission Timing - minPatrolDuration = 1800, -- Minimum patrol duration in seconds (30 minutes) - rtbDuration = 600, -- Time allowed for RTB in seconds (10 minutes) - missionCleanupTime = 1800, -- Time before old missions are cleaned up (30 minutes) - statusReportInterval = 300, -- Interval between status reports (5 minutes) + -- Combat Effectiveness Ratios + fighterVsFighter = 1.0, -- Fighter vs Fighter effectiveness + fighterVsBomber = 1.5, -- Fighter vs Bomber effectiveness + fighterVsHelicopter = 2.0, -- Fighter vs Helicopter effectiveness - -- System Timing - capSetupDelay = 5, -- Delay before setting up CAP tasks (seconds) - mainLoopDelay = 5, -- Initial delay before starting main loop (seconds) - mainLoopInterval = 30, -- Main loop execution interval (seconds) + -- Threat Management + threatTimeout = 300, -- Remove threats after 5 minutes of no contact - -- Combat Aggression Parameters - hyperAggressiveMode = true, -- Enable maximum aggression settings - pursuitRange = 50000, -- How far aircraft will chase targets (50km) - engagementUpdateInterval = 30, -- How often to update target vectors (seconds) - - -- Debug Options - debugLevel = 2, -- 0=Silent, 1=Basic, 2=Verbose + -- Testing and Debug + forceOmniscientDetection = false, -- Force omniscient detection for testing + debugLevel = 2, -- Verbose logging -- Persistent CAP Configuration enablePersistentCAP = true, -- Enable continuous standing patrols @@ -105,7 +110,7 @@ local GCI_Config = { }, -- Optional Features - initialStandingPatrols = false -- Launch standing patrols on startup (legacy) + initialStandingPatrols = true -- Launch standing patrols on startup (ENABLED FOR TESTING) } @@ -168,9 +173,9 @@ local function validateConfiguration() end if #errors > 0 then - env.error("TADC Configuration Errors:") + TADC_Log("error", "TADC Configuration Errors:") for _, error in pairs(errors) do - env.error(" - " .. error) + TADC_Log("error", " - " .. error) end return false end @@ -188,17 +193,17 @@ local heloBorderGroup = GROUP:FindByName("HELO BORDER") if redBorderGroup then CCCPBorderZone = ZONE_POLYGON:New("RED BORDER", redBorderGroup) - env.info("RED BORDER zone created successfully") + TADC_Log("info", "RED BORDER zone created successfully") else - env.info("ERROR: RED BORDER group not found!") + TADC_Log("error", "RED BORDER group not found!") return end if heloBorderGroup then HeloBorderZone = ZONE_POLYGON:New("HELO BORDER", heloBorderGroup) - env.info("HELO BORDER zone created successfully") + TADC_Log("info", "HELO BORDER zone created successfully") else - env.info("ERROR: HELO BORDER group not found!") + TADC_Log("error", "HELO BORDER group not found!") return end @@ -293,11 +298,11 @@ local squadronConfigs = { } -- Check which squadron templates exist in the mission -env.info("=== CHECKING SQUADRON TEMPLATES ===") +TADC_Log("info", "=== CHECKING SQUADRON TEMPLATES ===") local availableSquadrons = {} -- First, let's verify what airbases are actually available -env.info("=== VERIFYING AIRBASE NAMES ===") +TADC_Log("info", "=== VERIFYING AIRBASE NAMES ===") local testAirbaseNames = { "Kilpyavr", "Severomorsk-1", "Severomorsk-3", "Murmansk International", "Monchegorsk", "Olenya", "Afrikanda" @@ -305,9 +310,9 @@ local testAirbaseNames = { for _, airbaseName in pairs(testAirbaseNames) do local airbaseObj = AIRBASE:FindByName(airbaseName) if airbaseObj then - env.info("✓ Airbase found: " .. airbaseName) + TADC_Log("info", "✓ Airbase found: " .. airbaseName) else - env.info("✗ Airbase NOT found: " .. airbaseName) + TADC_Log("info", "✗ Airbase NOT found: " .. airbaseName) end end @@ -335,6 +340,7 @@ local function initializeSquadron(config) 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 @@ -351,33 +357,33 @@ local function initializeSquadron(config) } end -env.info("=== INITIALIZING SQUADRON DATABASE ===") +TADC_Log("info", "=== INITIALIZING SQUADRON DATABASE ===") for _, config in pairs(squadronConfigs) do local template = GROUP:FindByName(config.templateName) if template then - env.info("✓ Found squadron template: " .. config.templateName) + 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 - env.info(" ✓ Airbase verified: " .. config.airbaseName .. " (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 - env.info(" ✓ Squadron initialized: " .. squadron.availableAircraft .. " aircraft available") + TADC_Log("info", " ✓ Squadron initialized: " .. squadron.availableAircraft .. " aircraft available") else - env.info(" ✗ Airbase " .. config.airbaseName .. " not Red coalition - squadron disabled") + TADC_Log("info", " ✗ Airbase " .. config.airbaseName .. " not Red coalition - squadron disabled") end else - env.info(" ✗ Airbase NOT found: " .. config.airbaseName .. " - squadron disabled") + TADC_Log("info", " ✗ Airbase NOT found: " .. config.airbaseName .. " - squadron disabled") end else - env.info("✗ Missing squadron template: " .. config.templateName) + TADC_Log("info", "✗ Missing squadron template: " .. config.templateName) end end @@ -388,11 +394,11 @@ for _, squadron in pairs(TADC.squadrons) do totalAircraft = totalAircraft + squadron.availableAircraft end -env.info("✓ TADC Squadron Database: " .. squadronCount .. " squadrons, " .. totalAircraft .. " total aircraft") +TADC_Log("info", "✓ TADC Squadron Database: " .. squadronCount .. " squadrons, " .. totalAircraft .. " total aircraft") if GCI_Config.supplyMode == "INFINITE" then - env.info("✓ Supply Mode: INFINITE - unlimited aircraft spawning") + TADC_Log("info", "✓ Supply Mode: INFINITE - unlimited aircraft spawning") else - env.info("✓ Supply Mode: FINITE - " .. totalAircraft .. " aircraft available") + TADC_Log("info", "✓ Supply Mode: FINITE - " .. totalAircraft .. " aircraft available") end -- ================================================================================================ @@ -400,14 +406,24 @@ end -- A comprehensive GCI system for intelligent air defense coordination -- ================================================================================================ -env.info("=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===") +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 - env.info("✓ Red EWR Network: " .. RedEWR:Count() .. " detection groups") + 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() @@ -416,16 +432,101 @@ if GCI_Config.useEWRDetection and RedEWR:Count() > 0 then end) if success then - env.info("✓ EWR-based threat detection system initialized (" .. (GCI_Config.ewrDetectionRadius/1000) .. "km range)") + TADC_Log("info", "✓ EWR-based threat detection system initialized (" .. (GCI_Config.ewrDetectionRadius/1000) .. "km range)") else - env.info("⚠ EWR detection failed: " .. tostring(errorMsg) .. " - falling back to omniscient detection") + TADC_Log("info", "⚠ EWR detection failed: " .. tostring(errorMsg) .. " - falling back to omniscient detection") RedDetection = nil end else if GCI_Config.useEWRDetection then - env.info("⚠ Warning: No RED-EWR groups found - falling back to omniscient detection") + TADC_Log("info", "⚠ Warning: No RED-EWR groups found - falling back to omniscient detection") else - env.info("✓ Using omniscient detection (EWR detection disabled in config)") + 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 @@ -481,7 +582,7 @@ local function initializeStrategicTargets() if airbase then target.coord = airbase:GetCoordinate() if GCI_Config.debugLevel >= 1 then - env.info("✓ Strategic target: " .. target.name .. " (Importance: " .. target.importance .. ")") + TADC_Log("info", "✓ Strategic target: " .. target.name .. " (Importance: " .. target.importance .. ")") end end end @@ -532,7 +633,7 @@ local function calculateThreatPriority(classification, coordinate, size, velocit maxProximityScore = math.max(maxProximityScore, targetScore) if GCI_Config.debugLevel >= 2 then - env.info("Threat proximity: " .. target.name .. " (" .. math.floor(distance/1000) .. "km) Score: " .. math.floor(targetScore)) + TADC_Log("info", "Threat proximity: " .. target.name .. " (" .. math.floor(distance/1000) .. "km) Score: " .. math.floor(targetScore)) end end end @@ -545,7 +646,17 @@ local function calculateThreatPriority(classification, coordinate, size, velocit -- 4. SPEED AND HEADING ANALYSIS (10% weight) local speedThreatFactor = 0 if velocity and coordinate then - local speed = velocity -- Assuming velocity is in m/s + -- 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 @@ -559,7 +670,7 @@ local function calculateThreatPriority(classification, coordinate, size, velocit end -- Analyze heading threat (if heading toward strategic targets) - if heading then + if heading and type(heading) == "number" then local headingThreatBonus = 0 for _, target in pairs(STRATEGIC_TARGETS) do if target.coord then @@ -612,7 +723,7 @@ local function calculateThreatPriority(classification, coordinate, size, velocit priority = math.max(1, math.min(priority, 200)) -- Clamp between 1-200 if GCI_Config.debugLevel >= 2 then - env.info(string.format("Smart Priority: %s (x%d) = %.1f [Base:%.1f, Size:%.1f, Prox:%.1f, Speed:%.1f, Time:%.1f, EW:%.1f]", + 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 @@ -636,9 +747,20 @@ local function assessThreatWithPrediction(threats) -- 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 = threat.velocity * futureTime + local futureDistance = speed * futureTime assessment.predictedPosition = threat.coordinate:Translate(futureDistance, threat.heading) -- Calculate time to closest strategic target @@ -646,7 +768,7 @@ local function assessThreatWithPrediction(threats) for _, target in pairs(STRATEGIC_TARGETS) do if target.coord then local distance = threat.coordinate:Get2DDistance(target.coord) - local timeToTarget = distance / math.max(threat.velocity, 50) -- Minimum 50 m/s + local timeToTarget = distance / math.max(speed, 50) -- Minimum 50 m/s minTimeToTarget = math.min(minTimeToTarget, timeToTarget) end end @@ -710,52 +832,41 @@ local function assessThreatStrength(threats) } end -local function updateThreatPicture() +-- 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() - local newThreats = {} - -- Use EWR-based detection if enabled and available, otherwise fall back to omniscient detection + TADC_Log("info", "Checking for threats...") + + -- Use EWR detection if available and enabled if GCI_Config.useEWRDetection and RedDetection then - -- EWR-based realistic detection local detectedItems = RedDetection:GetDetectedItems() - - if GCI_Config.debugLevel >= 2 then - env.info("EWR DETECTION: Found " .. #detectedItems .. " detected items") - end + TADC_Log("info", "EWR detected " .. #detectedItems .. " items") for _, detectedItem in pairs(detectedItems) do local detectedSet = detectedItem.Set if detectedSet then - detectedSet:ForEach(function(detectedGroup) - if detectedGroup and detectedGroup:IsAlive() and detectedGroup:GetCoalition() == coalition.side.BLUE then - local blueCoord = detectedGroup:GetCoordinate() - local threatId = detectedGroup:GetName() - - -- DEBUG: Log each EWR-detected aircraft - if GCI_Config.debugLevel >= 2 then - env.info("EWR DETECTED: " .. threatId .. " at " .. blueCoord:ToStringLLDMS()) - end - - -- 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 - if GCI_Config.debugLevel >= 2 then - env.info(" Zone Check - RED: " .. tostring(inRedZone) .. ", HELO: " .. tostring(inHeloZone)) - end + 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 classification = classifyThreat(detectedGroup) - local size = detectedGroup:GetSize() - local heading = detectedGroup:GetHeading() - local velocity = detectedGroup:GetVelocity() + 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 = detectedGroup, - coordinate = blueCoord, + group = blueGroup, + coordinate = coord, classification = classification, size = size, zone = inRedZone and "RED_BORDER" or "HELO_BORDER", @@ -763,18 +874,18 @@ local function updateThreatPicture() lastSeen = currentTime, heading = heading, velocity = velocity, - priority = calculateThreatPriority(classification, blueCoord, size, velocity, heading, detectedGroup), + priority = calculateThreatPriority(classification, coord, size, velocity, heading, blueGroup), detectionMethod = "EWR", -- Additional smart assessment data - typeName = detectedGroup:GetTypeName(), - altitude = blueCoord:GetLandHeight() + (detectedGroup:GetCoordinate():GetY() or 0) + 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 - env.info("NEW EWR THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone) + TADC_Log("info", "NEW EWR THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone) end end end @@ -784,12 +895,16 @@ local function updateThreatPicture() 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() - if blueCount > 0 and GCI_Config.debugLevel >= 2 then - env.info("OMNISCIENT SCAN: Found " .. blueCount .. " blue aircraft on map") + 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) @@ -798,25 +913,25 @@ local function updateThreatPicture() local threatId = blueGroup:GetName() -- DEBUG: Log each blue aircraft found - if GCI_Config.debugLevel >= 2 then - env.info("CHECKING BLUE AIRCRAFT: " .. threatId .. " at " .. blueCoord:ToStringLLDMS()) - end + 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 - if GCI_Config.debugLevel >= 2 then - env.info(" Zone Check - RED: " .. tostring(inRedZone) .. ", HELO: " .. tostring(inHeloZone)) - end + 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, @@ -838,10 +953,12 @@ local function updateThreatPicture() -- Update statistics if not TADC.threats[threatId] then TADC.stats.threatsDetected = TADC.stats.threatsDetected + 1 - if GCI_Config.debugLevel >= 1 then - env.info("NEW THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone) - end + 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) @@ -851,12 +968,18 @@ local function updateThreatPicture() 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 - env.info("THREAT TIMEOUT: " .. threatId .. " - removing from threat picture") + 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 @@ -946,6 +1069,8 @@ local function calculateRequiredForce(threats, zone) 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 @@ -953,7 +1078,10 @@ local function assignThreatsToSquadrons(threats, zone) 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 @@ -962,11 +1090,11 @@ local function assignThreatsToSquadrons(threats, zone) -- Smart threat assessment logging if GCI_Config.debugLevel >= 1 and #sortedThreats > 0 then - env.info("=== SMART THREAT ASSESSMENT: " .. zone .. " ===") + 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" - env.info(string.format("%d. %s (%s x%d) Priority:%d TTT:%s Response:%s", + 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 @@ -976,12 +1104,12 @@ local function assignThreatsToSquadrons(threats, zone) -- Debug: Show squadron selection results if GCI_Config.debugLevel >= 1 then - env.info("Squadron Selection for " .. zone .. ":") + 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)) - env.info(" " .. i .. ". " .. squadron.displayName .. " - Available: " .. squadron.availableAircraft .. ", Cooldown: " .. math.ceil(cooldownRemaining) .. "s") + TADC_Log("info", " " .. i .. ". " .. squadron.displayName .. " - Available: " .. squadron.availableAircraft .. ", Cooldown: " .. math.ceil(cooldownRemaining) .. "s") end end @@ -1000,7 +1128,7 @@ local function assignThreatsToSquadrons(threats, zone) local squadron = TADC.squadrons[assignedSquadron] if squadron and squadron.readinessLevel == "BUSY" then if GCI_Config.debugLevel >= 2 then - env.info("Threat " .. threat.id .. " already assigned to " .. assignedSquadron) + TADC_Log("info", "Threat " .. threat.id .. " already assigned to " .. assignedSquadron) end skipThreat = true -- Skip this threat, it's being handled else @@ -1093,7 +1221,7 @@ local function assignThreatsToSquadrons(threats, zone) score = score - penalties if GCI_Config.debugLevel >= 2 then - env.info(string.format(" %s vs %s: Score=%.1f [Dist:%.1f, Type:%d, Ready:%.1f, Urg:%d, Pen:%.1f]", + 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 @@ -1120,24 +1248,32 @@ local function assignThreatsToSquadrons(threats, zone) TADC.squadronMissions[bestSquadron.templateName] = bestSquadron.threatId TADC.threatAssignments[bestSquadron.threatId] = bestSquadron.templateName - if GCI_Config.debugLevel >= 1 then - 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" - env.info(string.format("✓ SMART ASSIGNMENT: %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)) - end + 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 - if GCI_Config.debugLevel >= 1 then - env.info("No available squadron for threat " .. threat.id) + 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 @@ -1148,57 +1284,57 @@ end local function launchCAP(config, aircraftCount, reason) aircraftCount = aircraftCount or 1 - env.info("=== LAUNCHING CAP ===") - env.info("Squadron: " .. config.displayName) - env.info("Airbase: " .. config.airbaseName) - env.info("Aircraft: " .. aircraftCount) - env.info("Reason: " .. reason) + 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 - env.info("✗ Could not find airbase: " .. config.airbaseName) + TADC_Log("info", "✗ Could not find airbase: " .. config.airbaseName) return end - env.info("✓ Airbase object found, attempting spawn...") - env.info("Template: " .. config.templateName) - env.info("Aircraft count: " .. config.aircraft) - env.info("Skill: " .. tostring(config.skill)) + 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 - env.info("✗ CRITICAL: Template group not found: " .. config.templateName) - env.info("SOLUTION: In Mission Editor, ensure group '" .. config.templateName .. "' exists and is set to 'Late Activation'") + 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") - env.info("✓ Template group found - Coalition: " .. coalitionName) + TADC_Log("info", "✓ Template group found - Coalition: " .. coalitionName) if coalition ~= 1 then - env.info("✗ CRITICAL: Template group is not Red coalition (coalition=" .. coalition .. ")") - env.info("SOLUTION: In Mission Editor, set group '" .. config.templateName .. "' to Red coalition") + 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() - env.info("Template Status - Alive: " .. tostring(isAlive) .. " (should be false for Late Activation)") + TADC_Log("info", "Template Status - Alive: " .. tostring(isAlive) .. " (should be false for Late Activation)") if isAlive then - env.info("⚠ Warning: Template group is alive - Late Activation may not be set correctly") - env.info("This means the group has already spawned in the mission") + 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 - env.info("✓ Template group correctly set to Late Activation") + TADC_Log("info", "✓ Template group correctly set to Late Activation") end -- Create SPAWN object with proper initialization - env.info("Creating SPAWN object...") + TADC_Log("info", "Creating SPAWN object...") local spawner = SPAWN:New(config.templateName) @@ -1220,17 +1356,17 @@ local function launchCAP(config, aircraftCount, reason) math.random(0, 360) ):SetAltitude(config.altitude * 0.3048) -- Convert feet to meters - env.info("Attempt " .. spawnAttempts .. ": Air spawn at " .. config.altitude .. "ft near " .. config.airbaseName) + 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 - env.info("Attempt " .. spawnAttempts .. ": Hot start from airbase") + 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 - env.info("Attempt " .. spawnAttempts .. ": Cold start from airbase") + TADC_Log("info", "Attempt " .. spawnAttempts .. ": Cold start from airbase") return spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Cold) end @@ -1241,29 +1377,33 @@ local function launchCAP(config, aircraftCount, reason) while spawnAttempts < maxAttempts and not spawnedGroup do spawnedGroup = attemptSpawn() if not spawnedGroup and spawnAttempts < maxAttempts then - env.info("Spawn attempt " .. spawnAttempts .. " failed, retrying in 2 seconds...") + 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 - env.info("✓ Aircraft spawned successfully: " .. config.displayName) + 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 - env.info("Setting up CAP mission for " .. config.displayName) + TADC_Log("info", "Setting up CAP mission for " .. config.displayName) - -- Set proper altitude and speed (with nil checks) - local currentCoord = spawnedGroup:GetCoordinate() - if currentCoord then + -- 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") - env.info("✓ Set altitude to " .. config.altitude .. "ft") + TADC_Log("info", "✓ Set altitude to " .. config.altitude .. "ft") else - env.info("⚠ Coordinate not ready yet, CAP task will handle altitude") + TADC_Log("info", "⚠ Coordinate not ready yet, CAP task will handle altitude") end -- Set up AGGRESSIVE AI options with error handling @@ -1274,7 +1414,7 @@ local function launchCAP(config, aircraftCount, reason) -- 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) + -- 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 @@ -1291,11 +1431,11 @@ local function launchCAP(config, aircraftCount, reason) spawnedGroup:OptionFormation(AI.Option.Air.val.FORMATION.FINGER_FOUR) -- Combat formation for fighters end - env.info("✓ Aggressive AI options set for " .. config.displayName) + TADC_Log("info", "✓ Aggressive AI options set for " .. config.displayName) end) if not success then - env.info("⚠ Warning: Could not set all AI options: " .. tostring(errorMsg)) + TADC_Log("info", "⚠ Warning: Could not set all AI options: " .. tostring(errorMsg)) end -- Create randomized patrol system to prevent clustering @@ -1312,7 +1452,7 @@ local function launchCAP(config, aircraftCount, reason) local patrolPoint = patrolZoneCoord:Translate(randomRadius, randomBearing) patrolPoint = patrolPoint:SetAltitude(config.altitude * 0.3048) -- Convert to meters - env.info("Setting new patrol area for " .. config.displayName .. " at " .. randomRadius .. "m/" .. randomBearing .. "°") + 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() @@ -1368,7 +1508,7 @@ local function launchCAP(config, aircraftCount, reason) } spawnedGroup:PushTask(aggressiveCAP, 2) - env.info("✓ " .. config.displayName .. " assigned to patrol area " .. randomRadius .. "m from zone center") + TADC_Log("info", "✓ " .. config.displayName .. " assigned to patrol area " .. randomRadius .. "m from zone center") end end end @@ -1384,12 +1524,12 @@ local function launchCAP(config, aircraftCount, reason) end) if not capSuccess then - env.info("⚠ Warning: Could not set CAP tasks: " .. tostring(capError)) + TADC_Log("info", "⚠ Warning: Could not set CAP tasks: " .. tostring(capError)) -- Fallback: just set basic engage task spawnedGroup:OptionROEOpenFire() end - env.info("✓ CAP mission established at " .. config.altitude .. "ft altitude") + TADC_Log("info", "✓ CAP mission established at " .. config.altitude .. "ft altitude") end end, {}, 5) -- 5 second delay to let aircraft stabilize @@ -1405,7 +1545,7 @@ local function launchCAP(config, aircraftCount, reason) local patrolDuration = math.max(config.patrolTime * 60, GCI_Config.minPatrolDuration) -- Minimum from config SCHEDULER:New(nil, function() if TADC.activeCAPs[config.templateName] then - env.info(config.displayName .. " completing patrol mission - RTB") + 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 @@ -1415,7 +1555,7 @@ local function launchCAP(config, aircraftCount, reason) local airbaseObj = AIRBASE:FindByName(config.airbaseName) if airbaseObj then group:RouteRTB(airbaseObj) - env.info("✓ " .. config.displayName .. " returning to " .. config.airbaseName) + TADC_Log("info", "✓ " .. config.displayName .. " returning to " .. config.airbaseName) end -- Clean up after RTB delay @@ -1431,7 +1571,7 @@ local function launchCAP(config, aircraftCount, reason) if rtbGroup and rtbGroup:IsAlive() then rtbGroup:Destroy() - env.info("✓ " .. config.displayName .. " landed and available for next sortie") + TADC_Log("info", "✓ " .. config.displayName .. " landed and available for next sortie") end TADC.activeCAPs[config.templateName] = nil end @@ -1447,15 +1587,15 @@ local function launchCAP(config, aircraftCount, reason) end, {}, patrolDuration) else - env.info("✗ Failed to spawn " .. config.displayName) + TADC_Log("info", "✗ Failed to spawn " .. config.displayName) end end) if not success then - env.info("✗ Error launching CAP: " .. tostring(errorMsg)) + TADC_Log("info", "✗ Error launching CAP: " .. tostring(errorMsg)) return false else - env.info("✓ CAP launch completed successfully") + TADC_Log("info", "✓ CAP launch completed successfully") return true end end @@ -1464,26 +1604,26 @@ end -- AGGRESSIVE INTERCEPT FUNCTION - Direct threat vectoring -- ================================================================================================ -local function launchInterceptMission(config, threat, reason) - env.info("=== LAUNCHING INTERCEPT MISSION ===") - env.info("Squadron: " .. config.displayName) - env.info("Target: " .. (threat and threat.id or "Unknown")) - env.info("Target Type: " .. (threat and threat.classification or "Unknown")) - env.info("Reason: " .. reason) +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 - env.info("✗ Could not find airbase: " .. config.airbaseName) - return + 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 - env.info("✗ CRITICAL: Template group not found: " .. config.templateName) - return + TADC_Log("error", "✗ CRITICAL: Template group not found: " .. config.templateName) + error("Template group not found: " .. config.templateName) end -- Create SPAWN object @@ -1494,7 +1634,7 @@ local function launchInterceptMission(config, threat, reason) local spawnCoord = airbaseCoord:Translate(math.random(GCI_Config.spawnDistanceMin, GCI_Config.spawnDistanceMax), math.random(0, 360)) spawnCoord = spawnCoord:SetAltitude(config.altitude) - env.info("Spawning interceptor at " .. config.altitude .. "ft near " .. config.airbaseName) + 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 @@ -1506,12 +1646,40 @@ local function launchInterceptMission(config, threat, reason) end if spawnedGroup then - env.info("✓ Interceptor spawned successfully: " .. config.displayName) + TADC_Log("info", "✓ Interceptor spawned successfully: " .. config.displayName) -- Wait a moment then set up AGGRESSIVE INTERCEPT mission SCHEDULER:New(nil, function() - if spawnedGroup and spawnedGroup:IsAlive() and threat and threat.group and threat.group:IsAlive() then - env.info("Setting up AGGRESSIVE INTERCEPT mission for " .. config.displayName .. " vs " .. threat.id) + -- 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() @@ -1524,7 +1692,7 @@ local function launchInterceptMission(config, threat, reason) spawnedGroup:OptionRTBBingoFuel() spawnedGroup:OptionRTBAmmo(0.03) -- Stay until almost no ammo (3%) - env.info("✓ Maximum aggression AI options set") + TADC_Log("info", "✓ Maximum aggression AI options set") end) -- DIRECT THREAT VECTORING - This is the key difference! @@ -1536,7 +1704,7 @@ local function launchInterceptMission(config, threat, reason) end if threatCoord then - env.info("VECTORING " .. config.displayName .. " directly to threat at " .. threatCoord:ToStringLLDMS()) + TADC_Log("info", "VECTORING " .. config.displayName .. " directly to threat at " .. threatCoord:ToStringLLDMS()) -- Clear any existing tasks spawnedGroup:ClearTasks() @@ -1549,7 +1717,7 @@ local function launchInterceptMission(config, threat, reason) if interceptorCoord and interceptCoord then spawnedGroup:RouteAirTo(interceptCoord, config.speed * 1.2, "BARO") -- 20% faster to intercept else - env.warning("Cannot route " .. config.displayName .. " - interceptor coordinate invalid") + TADC_Log("warning", "Cannot route " .. config.displayName .. " - interceptor coordinate invalid") end -- TASK 2: Attack the specific threat group (Priority 1) @@ -1593,7 +1761,7 @@ local function launchInterceptMission(config, threat, reason) } spawnedGroup:PushTask(huntTask, 3) - env.info("✓ " .. config.displayName .. " vectored to intercept " .. threat.id .. " with aggressive hunter-killer tasks") + 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() @@ -1603,51 +1771,56 @@ local function launchInterceptMission(config, threat, reason) -- Update intercept vector to current threat position local newInterceptCoord = currentThreatCoord:SetAltitude(config.altitude * 0.3048) - -- Additional safety check before routing - local interceptorCoord = spawnedGroup:GetCoordinate() - if interceptorCoord and newInterceptCoord then + -- 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 - env.info("Updated vector: " .. config.displayName .. " → " .. threat.id) + TADC_Log("info", "Updated vector: " .. config.displayName .. " → " .. threat.id) end else - env.warning("Cannot route " .. config.displayName .. " - invalid coordinates") + TADC_Log("warning", "Cannot route " .. config.displayName .. " - invalid coordinates") end else - env.warning("Cannot get threat coordinate for " .. threat.id) + 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 - env.info("Stopping threat tracking for " .. config.displayName) + TADC_Log("info", "Stopping threat tracking for " .. config.displayName) end return false -- Stop scheduler end end, {}, 30, 30) -- Update every 30 seconds else - env.info("⚠ Could not get threat coordinate for vectoring") + 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) - -- Safety check before routing - local interceptorCoord = spawnedGroup:GetCoordinate() - if interceptorCoord then + -- 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 - env.warning("Cannot route " .. config.displayName .. " to patrol - interceptor coordinate invalid") + TADC_Log("warning", "Cannot route " .. config.displayName .. " to patrol - GetCoordinate failed") end else - env.warning("Cannot get patrol zone coordinate for " .. config.displayName) + TADC_Log("warning", "Cannot get patrol zone coordinate for " .. config.displayName) end end - - else - env.info("⚠ Threat no longer valid for intercept mission") - end - end, {}, 3) -- 3 second delay + end, {}, 5) -- Increased to 5 second delay to allow full group initialization -- Mark as active TADC.activeCAPs[config.templateName] = { @@ -1662,7 +1835,7 @@ local function launchInterceptMission(config, threat, reason) local patrolDuration = math.max(config.patrolTime * 60, GCI_Config.minPatrolDuration) SCHEDULER:New(nil, function() if TADC.activeCAPs[config.templateName] then - env.info(config.displayName .. " completing intercept mission - RTB") + TADC_Log("info", config.displayName .. " completing intercept mission - RTB") local group = TADC.activeCAPs[config.templateName].group if group and group:IsAlive() then group:ClearTasks() @@ -1686,15 +1859,15 @@ local function launchInterceptMission(config, threat, reason) end end, {}, patrolDuration) else - env.info("✗ Failed to spawn interceptor: " .. config.displayName) + TADC_Log("info", "✗ Failed to spawn interceptor: " .. config.displayName) end end) if not success then - env.info("✗ Error launching intercept: " .. tostring(errorMsg)) + TADC_Log("info", "✗ Error launching intercept: " .. tostring(errorMsg)) return false else - env.info("✓ Intercept mission launched successfully") + TADC_Log("info", "✓ Intercept mission launched successfully") return true end end @@ -1710,8 +1883,8 @@ local function executeThreatsAssignments(assignments) local currentTime = timer.getTime() - env.info("=== EXECUTING THREAT ASSIGNMENTS ===") - env.info("Processing " .. #assignments .. " threat assignments") + TADC_Log("info", "=== EXECUTING THREAT ASSIGNMENTS ===") + TADC_Log("info", "Processing " .. #assignments .. " threat assignments") local launchedFlights = {} @@ -1740,7 +1913,8 @@ local function executeThreatsAssignments(assignments) squadron.airborneAircraft = squadron.airborneAircraft + 1 squadron.lastLaunch = currentTime squadron.sorties = squadron.sorties + 1 - squadron.readinessLevel = "BUSY" -- Squadron is now handling this threat + -- 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 @@ -1759,9 +1933,9 @@ local function executeThreatsAssignments(assignments) launchTime = currentTime }) - env.info("✓ Launched: " .. squadron.displayName .. " → " .. (assignment.threat and assignment.threat.id or "Unknown")) + TADC_Log("info", "✓ Launched: " .. squadron.displayName .. " → " .. (assignment.threat and assignment.threat.id or "Unknown")) else - env.info("✗ Launch failed: " .. squadron.displayName) + TADC_Log("info", "✗ Launch failed: " .. squadron.displayName) end else local reason = "Unknown" @@ -1772,7 +1946,7 @@ local function executeThreatsAssignments(assignments) elseif (currentTime - squadron.lastLaunch) < squadron.launchCooldown then reason = "On cooldown (" .. math.ceil(squadron.launchCooldown - (currentTime - squadron.lastLaunch)) .. "s remaining)" end - env.info("✗ Cannot launch " .. squadron.displayName .. ": " .. reason) + TADC_Log("info", "✗ Cannot launch " .. squadron.displayName .. ": " .. reason) end end @@ -1835,7 +2009,7 @@ local function launchPersistentCAP(templateName, reason) } if GCI_Config.debugLevel >= 1 then - env.info("✓ Persistent CAP launched: " .. squadron.displayName) + TADC_Log("info", "✓ Persistent CAP launched: " .. squadron.displayName) end return true @@ -1877,14 +2051,14 @@ local function maintainPersistentCAP() needed = math.max(0, needed) if needed < (GCI_Config.persistentCAPCount - currentPersistentCount) and GCI_Config.debugLevel >= 1 then - env.info("⚠ Persistent CAP limited: Target=" .. GCI_Config.persistentCAPCount .. ", Effective=" .. effectiveTarget .. " (reserving " .. math.ceil(GCI_Config.maxSimultaneousCAP * GCI_Config.persistentCAPReserve) .. " slots for threats)") + 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 - env.info("=== PERSISTENT CAP MAINTENANCE ===") - env.info("Current: " .. currentPersistentCount .. ", Target: " .. GCI_Config.persistentCAPCount .. ", Need: " .. needed) - env.info("Total airborne: " .. totalAirborne .. "/" .. GCI_Config.maxSimultaneousCAP .. " (available slots: " .. availableSlots .. ")") + 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 @@ -1906,7 +2080,7 @@ local function maintainPersistentCAP() end if GCI_Config.debugLevel >= 1 then - env.info("✓ Persistent CAP maintenance complete: " .. launched .. " new patrols launched") + TADC_Log("info", "✓ Persistent CAP maintenance complete: " .. launched .. " new patrols launched") end end end @@ -1960,7 +2134,7 @@ local function enhanceAIAwareness() (timer.getTime() - (capData.lastVectorUpdate or 0)) > GCI_Config.engagementUpdateInterval then if GCI_Config.debugLevel >= 2 then - env.info("Vectoring " .. config.displayName .. " to nearby threat: " .. closestThreat.threat.id .. " (" .. math.floor(closestThreat.distance/1000) .. "km)") + 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 @@ -1974,10 +2148,10 @@ local function enhanceAIAwareness() if groupCoord and interceptCoord then group:RouteAirTo(interceptCoord, config.speed * 1.2, "BARO") else - env.warning("Cannot route " .. config.displayName .. " - invalid coordinates for intercept") + TADC_Log("warning", "Cannot route " .. config.displayName .. " - invalid coordinates for intercept") end else - env.warning("No coordinate available for threat " .. closestThreat.threat.id) + TADC_Log("warning", "No coordinate available for threat " .. closestThreat.threat.id) end -- Add aggressive attack task @@ -2022,43 +2196,74 @@ end -- MAIN TADC CONTROL LOOP -- ================================================================================================ -local function mainTADCLoop() +-- SIMPLE GCI MAIN LOOP - Just detect threats and launch intercepts +local function simpleGCILoop() local currentTime = timer.getTime() - -- Update threat picture - local threats = updateThreatPicture() - local threatCount = 0 - for _ in pairs(threats) do threatCount = threatCount + 1 end + -- Count current airborne aircraft + local airborneCount = 0 + for _, squadron in pairs(TADC.squadrons) do + airborneCount = airborneCount + squadron.airborneAircraft + end - if threatCount > 0 then - if GCI_Config.debugLevel >= 2 then - env.info("=== TADC THREAT ASSESSMENT ===") - env.info("Active threats: " .. threatCount) - 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) - -- Plan responses for each zone - local zones = {"RED_BORDER", "HELO_BORDER"} + -- Calculate how many defenders needed (1 to 1.5 ratio) + local defendersNeeded = math.ceil(threat.size * GCI_Config.threatRatio) - for _, zone in pairs(zones) do - local assignments = assignThreatsToSquadrons(threats, zone) - - if #assignments > 0 then - -- Wait for response delay before executing (allows threat picture to stabilize) - local readyAssignments = {} - - for _, assignment in pairs(assignments) do - local threatAge = currentTime - assignment.threat.firstDetected - if threatAge >= GCI_Config.responseDelay then - table.insert(readyAssignments, assignment) - elseif GCI_Config.debugLevel >= 2 then - env.info("Delaying assignment: " .. (assignment.threat and assignment.threat.id or "Unknown") .. " (age: " .. math.floor(threatAge) .. "s)") + -- 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 - - if #readyAssignments > 0 then - executeThreatsAssignments(readyAssignments) + 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 @@ -2100,7 +2305,7 @@ local function mainTADCLoop() TADC.missions[missionId] = nil if GCI_Config.debugLevel >= 1 then - env.info("Mission completed: " .. mission.squadron .. " freed from " .. (mission.threat and mission.threat.id or "unknown threat")) + TADC_Log("info", "Mission completed: " .. mission.squadron .. " freed from " .. (mission.threat and mission.threat.id or "unknown threat")) end end else @@ -2119,17 +2324,17 @@ local function mainTADCLoop() totalAvailable = totalAvailable + squadron.availableAircraft end - env.info("=== TADC STATUS REPORT ===") - env.info("Threats: " .. threatCount .. " active") - env.info("Aircraft: " .. totalAirborne .. " airborne, " .. totalAvailable .. " available") - env.info("Statistics: " .. TADC.stats.threatsDetected .. " threats detected, " .. TADC.stats.interceptsLaunched .. " intercepts launched") + 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 - env.info("Persistent CAP: " .. persistentCount .. "/" .. GCI_Config.persistentCAPCount .. " target (" .. maxPersistentAllowed .. " max, " .. threatReserve .. " reserved for threats)") + TADC_Log("info", "Persistent CAP: " .. persistentCount .. "/" .. GCI_Config.persistentCAPCount .. " target (" .. maxPersistentAllowed .. " max, " .. threatReserve .. " reserved for threats)") end end @@ -2142,47 +2347,207 @@ local function mainTADCLoop() 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() - env.info("=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===") + TADC_Log("info", "=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===") -- Validate configuration before starting if not validateConfiguration() then - env.info("✗ TADC configuration validation failed") + TADC_Log("info", "✗ TADC configuration validation failed") return end - env.info("✓ Configuration loaded and validated:") - env.info(" - Threat Ratio: " .. GCI_Config.threatRatio .. ":1") - env.info(" - Max Simultaneous CAP: " .. GCI_Config.maxSimultaneousCAP) - env.info(" - Reserve Percentage: " .. (GCI_Config.reservePercent * 100) .. "%") - env.info(" - Supply Mode: " .. GCI_Config.supplyMode) - env.info(" - Response Delay: " .. GCI_Config.responseDelay .. " seconds") + 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) - env.info(" - Persistent CAP: ENABLED (" .. GCI_Config.persistentCAPCount .. " target, " .. maxPersistentAllowed .. " max allowed)") - env.info(" - Threat Response Reserve: " .. threatReserve .. " aircraft slots") - env.info(" - Persistent CAP Check Interval: " .. GCI_Config.persistentCAPInterval .. " seconds") + 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 - env.info(" - Persistent CAP: DISABLED") + TADC_Log("info", " - Persistent CAP: DISABLED") end -- CAP Behavior Configuration - env.info(" - CAP Orbit Radius: " .. (GCI_Config.capOrbitRadius / 1000) .. "km") - env.info(" - CAP Engagement Range: " .. (GCI_Config.capEngagementRange / 1000) .. "km") - env.info(" - Zone Constraint: " .. (GCI_Config.capZoneConstraint and "ENABLED" or "DISABLED")) + 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 - env.info("✓ TADC main control loop started") - env.info("✓ Tactical Air Defense Controller operational!") + TADC_Log("info", "✓ TADC main control loop started") + TADC_Log("info", "✓ Tactical Air Defense Controller operational!") end -- Initialize the TADC system @@ -2191,13 +2556,13 @@ SCHEDULER:New(nil, function() -- Launch initial persistent CAP flights if enabled if GCI_Config.enablePersistentCAP then - env.info("=== LAUNCHING INITIAL PERSISTENT CAP ===") + 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() - env.info("=== PERSISTENT CAP FOLLOW-UP CHECK ===") + TADC_Log("info", "=== PERSISTENT CAP FOLLOW-UP CHECK ===") TADC.lastPersistentCheck = 0 -- Force another immediate check maintainPersistentCAP() end, {}, 30) @@ -2231,16 +2596,71 @@ SCHEDULER:New(nil, function() -- Initialize strategic targets for smart prioritization initializeStrategicTargets() - env.info("=== TADC INITIALIZATION COMPLETE ===") - env.info("✓ Smart threat prioritization system with multi-factor analysis") - env.info("✓ Predictive threat assessment and response") - env.info("✓ Intelligent threat assessment and response") - env.info("✓ Multi-squadron coordinated intercepts") - env.info("✓ Dynamic force sizing based on threat strength") - env.info("✓ Resource management with reserve forces") - env.info("✓ EWR network integration with " .. (RedEWR:Count()) .. " detection groups") - env.info("✓ Strategic target protection with distance-based prioritization") - env.info("✓ Enhanced squadron-threat matching algorithm") - env.info("✓ Tactical Air Defense Controller operational!") + 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) \ No newline at end of file +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/Simple_TADC.lua b/DCS_Kola/Operation_Polar_Shield/Simple_TADC.lua new file mode 100644 index 0000000..135e9d1 --- /dev/null +++ b/DCS_Kola/Operation_Polar_Shield/Simple_TADC.lua @@ -0,0 +1,483 @@ +-- Simple TADC - Just Works +-- Detect blue aircraft, launch red fighters, make them intercept + +-- Configuration +local TADC_CONFIG = { + checkInterval = 30, -- Check for threats every 10 seconds + interceptRatio = 1.4, -- Launch 1 fighter per threat (minimum) + maxActiveCAP = 24, -- Max fighters airborne at once + squadronCooldown = 900, -- Squadron cooldown after destruction (15 minutes) +} + +-- 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", + 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", + 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", + 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", + aircraft = 1, + skill = AI.Skill.GOOD, + altitude = 18000, + speed = 320, + patrolTime = 22, + type = "FIGHTER" + }, + { + templateName = "FIGHTER_SWEEP_RED_Monchegorsk", + displayName = "Monchegorsk CAP", + airbaseName = "Monchegorsk", + aircraft = 1, + skill = AI.Skill.GOOD, + altitude = 22000, + speed = 380, + patrolTime = 25, + type = "FIGHTER" + }, + { + templateName = "FIGHTER_SWEEP_RED_Olenya", + displayName = "Olenya CAP", + airbaseName = "Olenya", + 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", + aircraft = 4, + skill = AI.Skill.GOOD, + altitude = 1000, + speed = 150, + patrolTime = 30, + type = "HELICOPTER" + } + --]] +} + +-- Track active missions +local activeInterceptors = {} +local lastLaunchTime = {} +local assignedThreats = {} -- Track which threats already have interceptors assigned +local squadronCooldowns = {} -- Track squadron cooldowns after destruction + +-- Simple logging +local function log(message) + env.info("[Simple TADC] " .. message) +end + +-- Send interceptor back to base +local function sendInterceptorHome(interceptor) + if not interceptor or not interceptor:IsAlive() then + return + end + + -- Find nearest friendly airbase + local interceptorCoord = interceptor:GetCoordinate() + local nearestAirbase = nil + local shortestDistance = math.huge + + -- Check all squadron airbases to find the nearest one that's still friendly + for _, squadron in pairs(squadronConfigs) do + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if airbase and airbase:GetCoalition() == coalition.side.RED and airbase:IsAlive() then + local airbaseCoord = airbase:GetCoordinate() + local distance = interceptorCoord:Get2DDistance(airbaseCoord) + if distance < shortestDistance then + shortestDistance = distance + nearestAirbase = airbase + end + end + end + + if nearestAirbase then + local airbaseCoord = nearestAirbase:GetCoordinate() + local rtbAltitude = 3000 -- RTB at 3000 feet + local rtbCoord = airbaseCoord:SetAltitude(rtbAltitude * 0.3048) -- Convert feet to meters + + -- Clear current tasks and route home + interceptor:ClearTasks() + interceptor:RouteAirTo(rtbCoord, 250 * 0.5144, "BARO") -- RTB at 250 knots + + log("Sending " .. interceptor:GetName() .. " back to " .. nearestAirbase:GetName()) + + -- Schedule cleanup after they should have landed (give them time to get home) + local flightTime = math.ceil(shortestDistance / (250 * 0.5144)) + 300 -- Flight time + 5 min buffer + SCHEDULER:New(nil, function() + if activeInterceptors[interceptor:GetName()] then + activeInterceptors[interceptor:GetName()] = nil + log("Cleaned up " .. interceptor:GetName() .. " after RTB") + end + end, {}, flightTime) + else + log("No friendly airbase found for " .. interceptor:GetName() .. ", will clean up normally") + end +end + +-- Check if airbase is still usable +local function isAirbaseUsable(airbaseName) + local airbase = AIRBASE:FindByName(airbaseName) + if not airbase then + return false, "not found" + elseif airbase:GetCoalition() ~= coalition.side.RED then + return false, "captured by " .. (airbase:GetCoalition() == coalition.side.BLUE and "Blue" or "Neutral") + elseif not airbase:IsAlive() then + return false, "destroyed" + else + return true, "operational" + end +end + +-- Count active red fighters +local function countActiveFighters() + local count = 0 + for _, interceptorData in pairs(activeInterceptors) do + if interceptorData and interceptorData.group and interceptorData.group:IsAlive() then + count = count + interceptorData.group:GetSize() + end + end + return count +end + +-- Find best squadron to launch +local function findBestSquadron(threatCoord) + local bestSquadron = nil + local shortestDistance = math.huge + local currentTime = timer.getTime() + + for _, squadron in pairs(squadronConfigs) do + -- Check if squadron is on cooldown + local squadronAvailable = true + if squadronCooldowns[squadron.templateName] then + local cooldownEnd = squadronCooldowns[squadron.templateName] + if currentTime < cooldownEnd then + local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) + log("Squadron " .. squadron.displayName .. " on cooldown for " .. timeLeft .. " more minutes") + squadronAvailable = false + else + -- Cooldown expired, remove it + squadronCooldowns[squadron.templateName] = nil + log("Squadron " .. squadron.displayName .. " cooldown expired, available for launch") + end + end + + if squadronAvailable then + -- Check if airbase is still under Red control + local airbase = AIRBASE:FindByName(squadron.airbaseName) + if not airbase then + log("Warning: Airbase " .. squadron.airbaseName .. " not found") + elseif airbase:GetCoalition() ~= coalition.side.RED then + log("Warning: Airbase " .. squadron.airbaseName .. " no longer under Red control") + elseif not airbase:IsAlive() then + log("Warning: Airbase " .. squadron.airbaseName .. " is destroyed") + else + -- Airbase is valid, check if squadron can spawn + local spawn = SPAWN:New(squadron.templateName) + if spawn then + -- Get squadron's airbase + local template = GROUP:FindByName(squadron.templateName) + if template then + local airbaseCoord = template:GetCoordinate() + if airbaseCoord then + local distance = airbaseCoord:Get2DDistance(threatCoord) + if distance < shortestDistance then + shortestDistance = distance + bestSquadron = squadron + end + end + end + end + end + end + end + + return bestSquadron +end + +-- Launch interceptor +local function launchInterceptor(threatGroup) + if not threatGroup or not threatGroup:IsAlive() then + return + end + + local threatCoord = threatGroup:GetCoordinate() + local threatName = threatGroup:GetName() + local threatSize = threatGroup:GetSize() -- Get the number of aircraft in the threat group + + -- Check if threat already has interceptors assigned + if assignedThreats[threatName] then + local assignedInterceptors = assignedThreats[threatName] + local aliveCount = 0 + + -- Check if assigned interceptors are still alive + if type(assignedInterceptors) == "table" then + for _, interceptor in pairs(assignedInterceptors) do + if interceptor and interceptor:IsAlive() then + aliveCount = aliveCount + 1 + end + end + else + -- Handle legacy single interceptor assignment + if assignedInterceptors and assignedInterceptors:IsAlive() then + aliveCount = 1 + end + end + + if aliveCount > 0 then + return -- Still being intercepted + else + -- All interceptors are dead, clear the assignment + assignedThreats[threatName] = nil + end + end + + -- Calculate how many interceptors to launch (at least match threat size, up to ratio) + local interceptorsNeeded = math.max(threatSize, math.ceil(threatSize * TADC_CONFIG.interceptRatio)) + + -- Check if we have capacity + if countActiveFighters() + interceptorsNeeded > TADC_CONFIG.maxActiveCAP then + interceptorsNeeded = TADC_CONFIG.maxActiveCAP - countActiveFighters() + if interceptorsNeeded <= 0 then + log("Max fighters airborne, skipping launch") + return + end + end + + -- Find best squadron + local squadron = findBestSquadron(threatCoord) + if not squadron then + log("No squadron available") + return + end + + -- Launch multiple interceptors to match threat + local spawn = SPAWN:New(squadron.templateName) + local interceptors = {} + + for i = 1, interceptorsNeeded do + local interceptor = spawn:Spawn() + + if interceptor then + table.insert(interceptors, interceptor) + + -- Wait a moment for initialization + SCHEDULER:New(nil, function() + if interceptor and interceptor:IsAlive() then + -- Set aggressive AI + interceptor:OptionROEOpenFire() + interceptor:OptionROTVertical() + + -- Route to threat + local currentThreatCoord = threatGroup:GetCoordinate() + if currentThreatCoord then + local interceptCoord = currentThreatCoord:SetAltitude(squadron.altitude * 0.3048) -- Convert feet to meters + interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO") -- Convert kts to m/s + + -- Attack the threat + local attackTask = { + id = 'AttackGroup', + params = { + groupId = threatGroup:GetID(), + weaponType = 'Auto', + attackQtyLimit = 0, + priority = 1 + } + } + interceptor:PushTask(attackTask, 1) + end + end + end, {}, 3) + + -- Track the interceptor with squadron info + activeInterceptors[interceptor:GetName()] = { + group = interceptor, + squadron = squadron.templateName, + displayName = squadron.displayName + } + + -- Emergency cleanup (safety net - should normally RTB before this) + SCHEDULER:New(nil, function() + if activeInterceptors[interceptor:GetName()] then + log("Emergency cleanup of " .. interceptor:GetName() .. " (should have RTB'd)") + activeInterceptors[interceptor:GetName()] = nil + end + end, {}, 7200) -- Emergency cleanup after 2 hours + end + end + + -- Log the launch and track assignment + if #interceptors > 0 then + log("Launched " .. #interceptors .. " x " .. squadron.displayName .. " to intercept " .. + threatSize .. " x " .. threatName) + assignedThreats[threatName] = interceptors -- Track which interceptors are assigned to this threat + lastLaunchTime[threatName] = timer.getTime() + end +end + +-- Main threat detection loop +local function detectThreats() + log("Scanning for threats...") + + -- Clean up dead threats from tracking + local currentThreats = {} + + -- Find all blue aircraft + local blueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() + local threatCount = 0 + + blueAircraft:ForEach(function(blueGroup) + if blueGroup and blueGroup:IsAlive() then + threatCount = threatCount + 1 + currentThreats[blueGroup:GetName()] = true + log("Found threat: " .. blueGroup:GetName() .. " (" .. blueGroup:GetTypeName() .. ")") + + -- Launch interceptor for this threat + launchInterceptor(blueGroup) + end + end) + + -- Clean up assignments for threats that no longer exist and send interceptors home + for threatName, assignedInterceptors in pairs(assignedThreats) do + if not currentThreats[threatName] then + log("Threat " .. threatName .. " eliminated, sending interceptors home...") + + -- Send assigned interceptors back to base + if type(assignedInterceptors) == "table" then + for _, interceptor in pairs(assignedInterceptors) do + if interceptor and interceptor:IsAlive() then + sendInterceptorHome(interceptor) + end + end + else + -- Handle legacy single interceptor assignment + if assignedInterceptors and assignedInterceptors:IsAlive() then + sendInterceptorHome(assignedInterceptors) + end + end + + assignedThreats[threatName] = nil + end + end + + -- Count assigned threats + local assignedCount = 0 + for _ in pairs(assignedThreats) do assignedCount = assignedCount + 1 end + + log("Scan complete: " .. threatCount .. " threats, " .. countActiveFighters() .. " active fighters, " .. + assignedCount .. " assigned") +end + +-- Monitor interceptor groups and apply cooldowns when destroyed +local function monitorInterceptors() + local currentTime = timer.getTime() + local destroyedSquadrons = {} + + -- Check all active interceptors + for interceptorName, interceptorData in pairs(activeInterceptors) do + if interceptorData and interceptorData.group then + if not interceptorData.group:IsAlive() then + -- Interceptor group is destroyed + local squadronName = interceptorData.squadron + local displayName = interceptorData.displayName + + -- Track destroyed squadrons (avoid duplicate cooldowns) + if not destroyedSquadrons[squadronName] then + destroyedSquadrons[squadronName] = displayName + + -- Apply cooldown + squadronCooldowns[squadronName] = currentTime + TADC_CONFIG.squadronCooldown + local cooldownMinutes = TADC_CONFIG.squadronCooldown / 60 + log("Squadron " .. displayName .. " DESTROYED! Applying " .. cooldownMinutes .. " minute cooldown") + end + + -- Remove from active tracking + activeInterceptors[interceptorName] = nil + end + end + end +end + +-- Periodic airbase status check +local function checkAirbaseStatus() + log("=== AIRBASE STATUS REPORT ===") + local usableCount = 0 + local currentTime = timer.getTime() + + for _, squadron in pairs(squadronConfigs) do + local usable, status = isAirbaseUsable(squadron.airbaseName) + + -- Check if squadron is on cooldown + local cooldownStatus = "" + if squadronCooldowns[squadron.templateName] then + local cooldownEnd = squadronCooldowns[squadron.templateName] + if currentTime < cooldownEnd then + local timeLeft = math.ceil((cooldownEnd - currentTime) / 60) + cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" + status = status .. cooldownStatus + end + end + + if usable and cooldownStatus == "" then + usableCount = usableCount + 1 + log("✓ " .. squadron.airbaseName .. " - " .. status) + else + log("✗ " .. squadron.airbaseName .. " - " .. status) + end + end + + log("Status: " .. usableCount .. "/" .. #squadronConfigs .. " airbases operational") +end + +-- Start the system +log("Simple TADC starting...") +log("Squadrons configured: " .. #squadronConfigs) + +-- Run detection every interval +SCHEDULER:New(nil, detectThreats, {}, 5, TADC_CONFIG.checkInterval) + +-- Run interceptor monitoring every 30 seconds +SCHEDULER:New(nil, monitorInterceptors, {}, 10, 30) + +-- Run airbase status check every 2 minutes +SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, 120) + +log("Simple TADC operational!") \ No newline at end of file diff --git a/DCS_Kola/Operation_Polar_Shield/TADC_Diagnostic.lua b/DCS_Kola/Operation_Polar_Shield/TADC_Diagnostic.lua new file mode 100644 index 0000000..cc3f644 --- /dev/null +++ b/DCS_Kola/Operation_Polar_Shield/TADC_Diagnostic.lua @@ -0,0 +1,304 @@ +-- TADC Diagnostic Script +-- This script will run diagnostics on the TADC system to identify why no aircraft are launching + +env.info("=== TADC DIAGNOSTIC SCRIPT STARTING ===") + +-- Test 1: Check if zones exist and are properly defined +local function testZones() + env.info("=== ZONE DIAGNOSTIC ===") + + local redBorderGroup = GROUP:FindByName("RED BORDER") + local heloBorderGroup = GROUP:FindByName("HELO BORDER") + + if redBorderGroup then + env.info("✓ RED BORDER group found") + local redZone = ZONE_POLYGON:New("RED BORDER TEST", redBorderGroup) + if redZone then + env.info("✓ RED BORDER zone created successfully") + local coord = redZone:GetCoordinate() + if coord then + env.info("✓ RED BORDER zone coordinate: " .. coord:ToStringLLDMS()) + else + env.info("✗ RED BORDER zone has no coordinate") + end + else + env.info("✗ Failed to create RED BORDER zone") + end + else + env.info("✗ RED BORDER group NOT found - this is likely the problem!") + end + + if heloBorderGroup then + env.info("✓ HELO BORDER group found") + local heloZone = ZONE_POLYGON:New("HELO BORDER TEST", heloBorderGroup) + if heloZone then + env.info("✓ HELO BORDER zone created successfully") + local coord = heloZone:GetCoordinate() + if coord then + env.info("✓ HELO BORDER zone coordinate: " .. coord:ToStringLLDMS()) + else + env.info("✗ HELO BORDER zone has no coordinate") + end + else + env.info("✗ Failed to create HELO BORDER zone") + end + else + env.info("✗ HELO BORDER group NOT found - this may be the problem!") + end +end + +-- Test 2: Check if Blue aircraft exist on the map +local function testBlueAircraft() + env.info("=== BLUE AIRCRAFT DIAGNOSTIC ===") + + local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() + local blueCount = BlueAircraft:Count() + + env.info("Total Blue aircraft on map: " .. blueCount) + + if blueCount > 0 then + env.info("Blue aircraft found:") + BlueAircraft:ForEach(function(blueGroup) + if blueGroup and blueGroup:IsAlive() then + local coord = blueGroup:GetCoordinate() + local name = blueGroup:GetName() + if coord then + env.info(" - " .. name .. " at " .. coord:ToStringLLDMS()) + else + env.info(" - " .. name .. " (no coordinate)") + end + end + end) + else + env.info("✗ NO BLUE AIRCRAFT FOUND - this is likely why no intercepts are launching!") + env.info("SOLUTION: Add Blue coalition aircraft to the mission or spawn some for testing") + end +end + +-- Test 3: Check if Blue aircraft are in the detection zones +local function testBlueInZones() + env.info("=== BLUE AIRCRAFT IN ZONES DIAGNOSTIC ===") + + local redBorderGroup = GROUP:FindByName("RED BORDER") + local heloBorderGroup = GROUP:FindByName("HELO BORDER") + + if not redBorderGroup or not heloBorderGroup then + env.info("✗ Cannot test zones - border groups missing") + return + end + + local redZone = ZONE_POLYGON:New("RED BORDER TEST", redBorderGroup) + local heloZone = ZONE_POLYGON:New("HELO BORDER TEST", heloBorderGroup) + + local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() + local blueCount = BlueAircraft:Count() + + if blueCount == 0 then + env.info("✗ No Blue aircraft to test zone containment") + return + end + + local inRedZone = 0 + local inHeloZone = 0 + + BlueAircraft:ForEach(function(blueGroup) + if blueGroup and blueGroup:IsAlive() then + local coord = blueGroup:GetCoordinate() + local name = blueGroup:GetName() + + if coord then + local inRed = redZone and redZone:IsCoordinateInZone(coord) + local inHelo = heloZone and heloZone:IsCoordinateInZone(coord) + + if inRed then + inRedZone = inRedZone + 1 + env.info(" ✓ " .. name .. " is in RED BORDER zone") + elseif inHelo then + inHeloZone = inHeloZone + 1 + env.info(" ✓ " .. name .. " is in HELO BORDER zone") + else + env.info(" - " .. name .. " is NOT in any border zone") + end + end + end + end) + + env.info("Summary: " .. inRedZone .. " in RED zone, " .. inHeloZone .. " in HELO zone") + + if inRedZone == 0 and inHeloZone == 0 then + env.info("✗ NO BLUE AIRCRAFT IN DETECTION ZONES - this is why no intercepts are launching!") + env.info("SOLUTION: Move Blue aircraft into the border zones or expand the zone definitions") + end +end + +-- Test 4: Check squadron templates +local function testSquadronTemplates() + env.info("=== SQUADRON TEMPLATE DIAGNOSTIC ===") + + local squadronTemplates = { + "FIGHTER_SWEEP_RED_Kilpyavr", + "FIGHTER_SWEEP_RED_Severomorsk-1", + "FIGHTER_SWEEP_RED_Severomorsk-3", + "FIGHTER_SWEEP_RED_Murmansk", + "FIGHTER_SWEEP_RED_Monchegorsk", + "FIGHTER_SWEEP_RED_Olenya", + "HELO_SWEEP_RED_Afrikanda" + } + + local found = 0 + local total = #squadronTemplates + + for _, templateName in pairs(squadronTemplates) do + local template = GROUP:FindByName(templateName) + if template then + env.info("✓ Found template: " .. templateName) + + -- Check if template is alive (should NOT be for Late Activation) + local isAlive = template:IsAlive() + if isAlive then + env.info(" ⚠ WARNING: Template is ALIVE - should be set to Late Activation") + else + env.info(" ✓ Template correctly set to Late Activation") + end + + -- Check coalition + local coalition = template:GetCoalition() + if coalition == 1 then + env.info(" ✓ Template is Red coalition") + else + env.info(" ✗ Template is NOT Red coalition (coalition=" .. coalition .. ")") + end + + found = found + 1 + else + env.info("✗ Missing template: " .. templateName) + end + end + + env.info("Squadron templates found: " .. found .. "/" .. total) + + if found == 0 then + env.info("✗ NO SQUADRON TEMPLATES FOUND - this is why no aircraft can launch!") + env.info("SOLUTION: Create squadron groups in Mission Editor with the correct names and set to Late Activation") + end +end + +-- Test 5: Check airbases +local function testAirbases() + env.info("=== AIRBASE DIAGNOSTIC ===") + + local airbases = { + "Kilpyavr", "Severomorsk-1", "Severomorsk-3", + "Murmansk International", "Monchegorsk", "Olenya", "Afrikanda" + } + + local found = 0 + local redBases = 0 + + for _, airbaseName in pairs(airbases) do + local airbase = AIRBASE:FindByName(airbaseName) + if airbase then + env.info("✓ Found airbase: " .. airbaseName) + + local coalition = airbase:GetCoalition() + local coalitionName = coalition == 1 and "Red" or (coalition == 2 and "Blue" or "Neutral") + env.info(" Coalition: " .. coalitionName) + + if coalition == 1 then + redBases = redBases + 1 + end + + found = found + 1 + else + env.info("✗ Airbase not found: " .. airbaseName) + end + end + + env.info("Airbases found: " .. found .. "/" .. #airbases .. " (Red: " .. redBases .. ")") +end + +-- Test 6: Manual threat detection test +local function testThreatDetection() + env.info("=== THREAT DETECTION TEST ===") + + -- Try to manually detect threats + local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart() + local blueCount = BlueAircraft:Count() + + env.info("Manual threat scan - found " .. blueCount .. " blue aircraft") + + if blueCount > 0 then + local redBorderGroup = GROUP:FindByName("RED BORDER") + local heloBorderGroup = GROUP:FindByName("HELO BORDER") + + if redBorderGroup and heloBorderGroup then + local redZone = ZONE_POLYGON:New("RED BORDER TEST", redBorderGroup) + local heloZone = ZONE_POLYGON:New("HELO BORDER TEST", heloBorderGroup) + + local threatsInZones = 0 + + BlueAircraft:ForEach(function(blueGroup) + if blueGroup and blueGroup:IsAlive() then + local coord = blueGroup:GetCoordinate() + local name = blueGroup:GetName() + + if coord then + local inRed = redZone:IsCoordinateInZone(coord) + local inHelo = heloZone:IsCoordinateInZone(coord) + + if inRed or inHelo then + threatsInZones = threatsInZones + 1 + local classification = "UNKNOWN" + local category = blueGroup:GetCategory() + local typeName = blueGroup:GetTypeName() or "Unknown" + + if category == Group.Category.AIRPLANE then + if string.find(typeName:upper(), "B-") or string.find(typeName:upper(), "BOMBER") then + classification = "BOMBER" + elseif string.find(typeName:upper(), "A-") or string.find(typeName:upper(), "ATTACK") then + classification = "ATTACK" + else + classification = "FIGHTER" + end + elseif category == Group.Category.HELICOPTER then + classification = "HELICOPTER" + end + + env.info(" THREAT DETECTED: " .. name .. " (" .. classification .. ") in " .. (inRed and "RED BORDER" or "HELO BORDER")) + end + end + end + end) + + if threatsInZones == 0 then + env.info("✗ No threats detected in border zones") + else + env.info("✓ " .. threatsInZones .. " threats detected in border zones") + end + end + end +end + +-- Run all diagnostic tests +local function runAllDiagnostics() + env.info("Starting comprehensive TADC diagnostic...") + + testZones() + testBlueAircraft() + testBlueInZones() + testSquadronTemplates() + testAirbases() + testThreatDetection() + + env.info("=== DIAGNOSTIC COMPLETE ===") + env.info("Check the log above for any ✗ (failed) items - these are likely the cause of the problem") +end + +-- Schedule the diagnostic to run after a delay +SCHEDULER:New(nil, runAllDiagnostics, {}, 3) + +-- Also create a repeating diagnostic every 60 seconds for ongoing monitoring +SCHEDULER:New(nil, function() + env.info("=== PERIODIC THREAT CHECK ===") + testBlueInZones() +end, {}, 10, 60) -- Start after 10 seconds, repeat every 60 seconds \ No newline at end of file diff --git a/DCS_Kola/Operation_Polar_Shield/TADC_ManualTest.lua b/DCS_Kola/Operation_Polar_Shield/TADC_ManualTest.lua new file mode 100644 index 0000000..748ce03 --- /dev/null +++ b/DCS_Kola/Operation_Polar_Shield/TADC_ManualTest.lua @@ -0,0 +1,164 @@ +-- TADC Manual Launch Test +-- This script will attempt to manually launch one aircraft to test if the spawn system works + +env.info("=== TADC MANUAL LAUNCH TEST ===") + +-- Wait a few seconds for MOOSE to initialize, then try a manual launch +SCHEDULER:New(nil, function() + + env.info("Attempting manual aircraft launch...") + + -- Test configuration - using the first squadron + local testConfig = { + templateName = "FIGHTER_SWEEP_RED_Severomorsk-1", + displayName = "Severomorsk-1 TEST", + airbaseName = "Severomorsk-1", + aircraft = 1, + skill = AI.Skill.GOOD, + altitude = 20000, + speed = 350, + patrolTime = 25, + type = "FIGHTER" + } + + -- Manual launch function (simplified version) + local function manualLaunch(config) + env.info("=== MANUAL LAUNCH ATTEMPT ===") + env.info("Template: " .. config.templateName) + env.info("Airbase: " .. config.airbaseName) + + local success, errorMsg = pcall(function() + -- Check if template exists + local templateGroup = GROUP:FindByName(config.templateName) + if not templateGroup then + env.info("✗ CRITICAL: Template group not found: " .. config.templateName) + env.info("DIAGNOSIS: This template does not exist in the mission!") + env.info("SOLUTION: Create a group named '" .. config.templateName .. "' in Mission Editor") + return + end + + env.info("✓ Template group found: " .. config.templateName) + + -- Check template properties + local coalition = templateGroup:GetCoalition() + local isAlive = templateGroup:IsAlive() + + env.info("Template coalition: " .. (coalition == 1 and "Red" or (coalition == 2 and "Blue" or "Neutral"))) + env.info("Template alive: " .. tostring(isAlive)) + + if coalition ~= 1 then + env.info("✗ CRITICAL: Template is not Red coalition!") + env.info("SOLUTION: Set '" .. config.templateName .. "' to Red coalition in Mission Editor") + return + end + + if isAlive then + env.info("⚠ WARNING: Template is alive - Late Activation may not be set") + env.info("RECOMMENDATION: Set '" .. config.templateName .. "' to Late Activation in Mission Editor") + end + + -- Check airbase + local airbaseObj = AIRBASE:FindByName(config.airbaseName) + if not airbaseObj then + env.info("✗ CRITICAL: Airbase not found: " .. config.airbaseName) + env.info("SOLUTION: Check airbase name spelling or use a different airbase") + return + end + + env.info("✓ Airbase found: " .. config.airbaseName) + + local airbaseCoalition = airbaseObj:GetCoalition() + env.info("Airbase coalition: " .. (airbaseCoalition == 1 and "Red" or (airbaseCoalition == 2 and "Blue" or "Neutral"))) + + -- Create SPAWN object + env.info("Creating SPAWN object...") + local spawner = SPAWN:New(config.templateName) + + -- Try to spawn + env.info("Attempting to spawn aircraft...") + local spawnedGroup = nil + + -- Method 1: Air spawn + local airbaseCoord = airbaseObj:GetCoordinate() + local spawnCoord = airbaseCoord:Translate(2000, math.random(0, 360)):SetAltitude(config.altitude * 0.3048) + + env.info("Trying air spawn at " .. config.altitude .. "ft...") + spawnedGroup = spawner:SpawnFromCoordinate(spawnCoord, nil, SPAWN.Takeoff.Air) + + if not spawnedGroup then + env.info("Air spawn failed, trying hot start at airbase...") + spawnedGroup = spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Hot) + end + + if not spawnedGroup then + env.info("Hot start failed, trying cold start at airbase...") + spawnedGroup = spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Cold) + end + + if spawnedGroup then + env.info("✓ SUCCESS: Aircraft spawned successfully!") + env.info("Spawned group: " .. spawnedGroup:GetName()) + + -- Set basic CAP task after a delay + SCHEDULER:New(nil, function() + if spawnedGroup and spawnedGroup:IsAlive() then + env.info("Setting up basic CAP task...") + + local currentCoord = spawnedGroup:GetCoordinate() + if currentCoord then + env.info("Aircraft position: " .. currentCoord:ToStringLLDMS()) + + -- Clear tasks and set basic patrol + spawnedGroup:ClearTasks() + spawnedGroup:OptionROEOpenFire() + + -- Simple patrol task + local patrolCoord = currentCoord:Translate(10000, math.random(0, 360)):SetAltitude(config.altitude * 0.3048) + + local patrolTask = { + id = 'Orbit', + params = { + pattern = 'Circle', + point = {x = patrolCoord.x, y = patrolCoord.z}, + radius = 5000, + altitude = config.altitude * 0.3048, + speed = config.speed * 0.514444, + } + } + spawnedGroup:PushTask(patrolTask, 1) + + env.info("✓ Basic CAP task assigned") + + -- Clean up after 10 minutes + SCHEDULER:New(nil, function() + if spawnedGroup and spawnedGroup:IsAlive() then + env.info("Test complete - cleaning up spawned aircraft") + spawnedGroup:Destroy() + end + end, {}, 600) -- 10 minutes + + else + env.info("⚠ Could not get aircraft coordinate") + end + else + env.info("⚠ Aircraft not alive for task assignment") + end + end, {}, 5) -- 5 second delay + + else + env.info("✗ CRITICAL: All spawn methods failed!") + env.info("DIAGNOSIS: There may be an issue with the template or MOOSE setup") + end + end) + + if not success then + env.info("✗ CRITICAL ERROR in manual launch: " .. tostring(errorMsg)) + end + end + + -- Attempt the manual launch + manualLaunch(testConfig) + +end, {}, 5) -- Wait 5 seconds for MOOSE initialization + +env.info("Manual launch test scheduled - check log in 5 seconds") \ No newline at end of file diff --git a/DCS_Kola/Operation_Polar_Shield/TADC_Verification.lua b/DCS_Kola/Operation_Polar_Shield/TADC_Verification.lua new file mode 100644 index 0000000..bc19c16 --- /dev/null +++ b/DCS_Kola/Operation_Polar_Shield/TADC_Verification.lua @@ -0,0 +1,100 @@ +-- TADC Verification Script +-- This script provides a simple way to verify that all TADC components are properly configured +-- Run this in DCS to check for missing functions, configuration errors, etc. + +-- Verification function to check if all required elements exist +local function verifyTADC() + local errors = {} + local warnings = {} + + -- Check if GCI_Config exists and has required values + if not GCI_Config then + table.insert(errors, "GCI_Config not found") + else + local requiredConfig = { + "threatRatio", "maxSimultaneousCAP", "useEWRDetection", "ewrDetectionRadius", + "mainLoopInterval", "mainLoopDelay", "squadronCooldown", "supplyMode", + "defaultSquadronSize", "reservePercent", "responseDelay", "capSetupDelay", + "capOrbitRadius", "capEngagementRange", "capZoneConstraint", + "statusReportInterval", "engagementUpdateInterval", "fighterVsFighter", + "fighterVsBomber", "fighterVsHelicopter", "threatTimeout", "debugLevel" + } + + for _, configKey in pairs(requiredConfig) do + if GCI_Config[configKey] == nil then + table.insert(errors, "Missing config: GCI_Config." .. configKey) + end + end + end + + -- Check if TADC data structure exists + if not TADC then + table.insert(errors, "TADC data structure not found") + else + local requiredTADC = {"squadrons", "activeCAPs", "threats", "missions"} + for _, key in pairs(requiredTADC) do + if TADC[key] == nil then + table.insert(errors, "Missing TADC." .. key) + end + end + end + + -- Check if zones exist + if not CCCPBorderZone then + table.insert(errors, "CCCPBorderZone not found") + end + if not HeloBorderZone then + table.insert(errors, "HeloBorderZone not found") + end + + -- Check if main functions exist + local requiredFunctions = { + "TADC_Log", "validateConfiguration", "mainTADCLoop", "simpleDetectThreats", + "launchCAP", "launchInterceptMission", "maintainPersistentCAP" + } + + for _, funcName in pairs(requiredFunctions) do + if not _G[funcName] then + table.insert(errors, "Missing function: " .. funcName) + end + end + + -- Count squadrons + local squadronCount = 0 + if TADC and TADC.squadrons then + for _ in pairs(TADC.squadrons) do + squadronCount = squadronCount + 1 + end + end + + -- Print results + env.info("=== TADC VERIFICATION RESULTS ===") + + if #errors > 0 then + env.error("VERIFICATION FAILED - " .. #errors .. " errors found:") + for _, error in pairs(errors) do + env.error(" ❌ " .. error) + end + else + env.info("✅ All critical components verified successfully!") + end + + if #warnings > 0 then + env.warning("Warnings found:") + for _, warning in pairs(warnings) do + env.warning(" ⚠️ " .. warning) + end + end + + env.info("📊 Squadron count: " .. squadronCount) + env.info("📊 Config debug level: " .. (GCI_Config and GCI_Config.debugLevel or "N/A")) + env.info("=== VERIFICATION COMPLETE ===") + + return #errors == 0 +end + +-- Run verification after a short delay to ensure everything is loaded +SCHEDULER:New(nil, function() + env.info("Starting TADC verification...") + verifyTADC() +end, {}, 10) \ No newline at end of file