Version 1.0.4 Running on prod.

This commit is contained in:
iTracerFacer 2025-10-09 20:50:01 -05:00
parent 4c43418bc1
commit a2786f2325
11 changed files with 2195 additions and 402 deletions

View File

@ -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 ******************

View File

@ -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!")

View File

@ -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()

View File

@ -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!")

View File

@ -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

View File

@ -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")

View File

@ -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)