Updated with performance tweaks.

This commit is contained in:
iTracerFacer 2025-11-12 09:54:58 -06:00
parent 830f08441d
commit a0accf2adc
4 changed files with 425 additions and 170 deletions

View File

@ -1,6 +1,21 @@
-- Setup Capture Missions & Zones
-- Refactored version with configurable zone ownership
-- ==========================================
-- MESSAGE AND TIMING CONFIGURATION
-- ==========================================
local MESSAGE_CONFIG = {
STATUS_BROADCAST_FREQUENCY = 3602, -- Zone status broadcast cadence (seconds)
STATUS_BROADCAST_START_DELAY = 10, -- Delay before first broadcast (seconds)
COLOR_VERIFICATION_FREQUENCY = 240, -- Zone color verification cadence (seconds)
COLOR_VERIFICATION_START_DELAY = 60, -- Delay before first color check (seconds)
TACTICAL_UPDATE_FREQUENCY = 180, -- Tactical marker update cadence (seconds)
TACTICAL_UPDATE_START_DELAY = 30, -- Delay before first tactical update (seconds)
STATUS_MESSAGE_DURATION = 15, -- How long general status messages stay onscreen
VICTORY_MESSAGE_DURATION = 300, -- How long victory/defeat alerts stay onscreen
CAPTURE_MESSAGE_DURATION = 15, -- Duration for capture/guard/empty notices
ATTACK_MESSAGE_DURATION = 15 -- Duration for attack alerts
}
-- ==========================================
-- ZONE COLOR CONFIGURATION (Centralized)
-- ==========================================
@ -164,7 +179,7 @@ end
-- Logging configuration: toggle logging behavior for this module
-- Set `CAPTURE_ZONE_LOGGING.enabled = false` to silence module logs
if not CAPTURE_ZONE_LOGGING then
CAPTURE_ZONE_LOGGING = { enabled = true, prefix = "[CAPTURE Module]" }
CAPTURE_ZONE_LOGGING = { enabled = false, prefix = "[CAPTURE Module]" }
end
local function log(message, detailed)
@ -254,43 +269,63 @@ end
local totalZones = InitializeZones()
-- Helper functions for tactical information
-- Global cached unit set - created once and maintained automatically by MOOSE
local CachedUnitSet = nil
-- Utility guard to safely test whether a unit is inside a zone without throwing
local function IsUnitInZone(unit, zone)
if not unit or not zone then
return false
end
local ok, point = pcall(function()
return unit:GetPointVec3()
end)
if not ok or not point then
return false
end
local inZone = false
pcall(function()
inZone = zone:IsPointVec3InZone(point)
end)
return inZone
end
-- Initialize the cached unit set once
local function InitializeCachedUnitSet()
if not CachedUnitSet then
CachedUnitSet = SET_UNIT:New()
:FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types
:FilterOnce() -- Don't filter continuously, we'll use the live set
:FilterStart() -- Keep the set updated by MOOSE without recreating it
log("[PERFORMANCE] Initialized cached unit set for zone scanning")
end
end
local function GetZoneForceStrengths(ZoneCapture)
if not ZoneCapture then
return {red = 0, blue = 0, neutral = 0}
if not ZoneCapture then
return { red = 0, blue = 0, neutral = 0 }
end
local success, zone = pcall(function() return ZoneCapture:GetZone() end)
if not success or not zone then
return {red = 0, blue = 0, neutral = 0}
local success, zone = pcall(function()
return ZoneCapture:GetZone()
end)
if not success or not zone then
return { red = 0, blue = 0, neutral = 0 }
end
local redCount = 0
local blueCount = 0
local blueCount = 0
local neutralCount = 0
-- Get all units in the zone using MOOSE's zone scanning
local unitsInZone = SET_UNIT:New()
:FilterZones({zone})
:FilterOnce()
if unitsInZone then
unitsInZone:ForEachUnit(function(unit)
if unit and unit:IsAlive() then
InitializeCachedUnitSet()
if CachedUnitSet then
CachedUnitSet:ForEachUnit(function(unit)
if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
local unitCoalition = unit:GetCoalition()
if unitCoalition == coalition.side.RED then
redCount = redCount + 1
@ -302,10 +337,10 @@ local function GetZoneForceStrengths(ZoneCapture)
end
end)
end
log(string.format("[TACTICAL] Zone %s scan result: R:%d B:%d N:%d",
log(string.format("[TACTICAL] Zone %s scan result: R:%d B:%d N:%d",
ZoneCapture:GetZoneName(), redCount, blueCount, neutralCount))
return {
red = redCount,
blue = blueCount,
@ -314,63 +349,62 @@ local function GetZoneForceStrengths(ZoneCapture)
end
local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
local zone = ZoneCapture:GetZone()
if not zone then return {} end
if not ZoneCapture or not enemyCoalition then
return {}
end
local success, zone = pcall(function()
return ZoneCapture:GetZone()
end)
if not success or not zone then
return {}
end
InitializeCachedUnitSet()
local coords = {}
-- Get all units in the zone using MOOSE's zone scanning
local unitsInZone = SET_UNIT:New()
:FilterZones({zone})
:FilterOnce()
local totalUnits = 0
local enemyUnits = 0
local unitsWithCoords = 0
if unitsInZone then
unitsInZone:ForEachUnit(function(unit)
totalUnits = totalUnits + 1
if unit and unit:IsAlive() then
if CachedUnitSet then
CachedUnitSet:ForEachUnit(function(unit)
if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
totalUnits = totalUnits + 1
local unitCoalition = unit:GetCoalition()
-- Process units of the specified enemy coalition
if unitCoalition == enemyCoalition then
enemyUnits = enemyUnits + 1
local coord = unit:GetCoordinate()
if coord then
-- Try multiple methods to get coordinates
local mgrs = nil
local success_mgrs = false
-- Method 1: Try ToStringMGRS
success_mgrs, mgrs = pcall(function()
return coord:ToStringMGRS(5)
end)
-- Method 2: Try ToStringMGRS without precision parameter
if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function()
return coord:ToStringMGRS()
end)
end
-- Method 3: Try ToMGRS
if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function()
return coord:ToMGRS()
end)
end
-- Method 4: Fallback to Lat/Long
if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function()
local lat, lon = coord:GetLLDDM()
return string.format("N%s E%s", lat, lon)
end)
end
if success_mgrs and mgrs then
unitsWithCoords = unitsWithCoords + 1
local unitType = unit:GetTypeName() or "Unknown"
@ -389,13 +423,13 @@ local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
end
end)
end
log(string.format("[TACTICAL DEBUG] %s - Total units scanned: %d, Enemy units: %d, units with MGRS: %d",
log(string.format("[TACTICAL DEBUG] %s - Total units scanned: %d, Enemy units: %d, units with MGRS: %d",
ZoneCapture:GetZoneName(), totalUnits, enemyUnits, unitsWithCoords))
log(string.format("[TACTICAL] Found %d enemy units with coordinates in %s",
log(string.format("[TACTICAL] Found %d enemy units with coordinates in %s",
#coords, ZoneCapture:GetZoneName()))
return coords
end
@ -463,19 +497,39 @@ local function CreateTacticalInfoMarker(ZoneCapture)
if coord then
local offsetCoord = coord:Translate(200, 45) -- 200m NE
local function removeMarker(markerID)
if not markerID then
return
end
local removed = pcall(function()
offsetCoord:RemoveMark(markerID)
end)
if not removed then
removed = pcall(function()
trigger.action.removeMark(markerID)
end)
end
if not removed then
pcall(function()
coord:RemoveMark(markerID)
end)
end
end
-- Remove legacy single marker if present
if ZoneCapture.TacticalMarkerID then
log(string.format("[TACTICAL] Removing old marker ID %d for %s", ZoneCapture.TacticalMarkerID, zoneName))
pcall(function() offsetCoord:RemoveMark(ZoneCapture.TacticalMarkerID) end)
pcall(function() trigger.action.removeMark(ZoneCapture.TacticalMarkerID) end)
pcall(function() coord:RemoveMark(ZoneCapture.TacticalMarkerID) end)
removeMarker(ZoneCapture.TacticalMarkerID)
ZoneCapture.TacticalMarkerID = nil
end
-- BLUE Coalition Marker
if ZoneCapture.TacticalMarkerID_BLUE then
log(string.format("[TACTICAL] Removing old BLUE marker ID %d for %s", ZoneCapture.TacticalMarkerID_BLUE, zoneName))
pcall(function() offsetCoord:RemoveMark(ZoneCapture.TacticalMarkerID_BLUE) end)
removeMarker(ZoneCapture.TacticalMarkerID_BLUE)
ZoneCapture.TacticalMarkerID_BLUE = nil
end
local successBlue, markerIDBlue = pcall(function()
@ -492,7 +546,7 @@ local function CreateTacticalInfoMarker(ZoneCapture)
-- RED Coalition Marker
if ZoneCapture.TacticalMarkerID_RED then
log(string.format("[TACTICAL] Removing old RED marker ID %d for %s", ZoneCapture.TacticalMarkerID_RED, zoneName))
pcall(function() offsetCoord:RemoveMark(ZoneCapture.TacticalMarkerID_RED) end)
removeMarker(ZoneCapture.TacticalMarkerID_RED)
ZoneCapture.TacticalMarkerID_RED = nil
end
local successRed, markerIDRed = pcall(function()
@ -516,16 +570,18 @@ local function OnEnterGuarded(ZoneCapture, From, Event, To)
ZoneCapture:Smoke( SMOKECOLOR.Blue )
-- Update zone visual markers to BLUE
ZoneCapture:UndrawZone()
ZoneCapture:DrawZone(-1, ZONE_COLORS.BLUE_CAPTURED, 0.5, ZONE_COLORS.BLUE_CAPTURED, 0.2, 2, true)
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 )
local color = ZONE_COLORS.BLUE_CAPTURED
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
else
ZoneCapture:Smoke( SMOKECOLOR.Red )
-- Update zone visual markers to RED
ZoneCapture:UndrawZone()
ZoneCapture:DrawZone(-1, ZONE_COLORS.RED_CAPTURED, 0.5, ZONE_COLORS.RED_CAPTURED, 0.2, 2, true)
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 )
local color = ZONE_COLORS.RED_CAPTURED
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
end
-- Create/update tactical information marker
CreateTacticalInfoMarker(ZoneCapture)
@ -536,9 +592,10 @@ local function OnEnterEmpty(ZoneCapture)
ZoneCapture:Smoke( SMOKECOLOR.Green )
-- Update zone visual markers to GREEN (neutral)
ZoneCapture:UndrawZone()
ZoneCapture:DrawZone(-1, ZONE_COLORS.EMPTY, 0.5, ZONE_COLORS.EMPTY, 0.2, 2, true)
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 )
local color = ZONE_COLORS.EMPTY
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
-- Create/update tactical information marker
CreateTacticalInfoMarker(ZoneCapture)
end
@ -551,14 +608,14 @@ local function OnEnterAttacked(ZoneCapture)
local color
if Coalition == coalition.side.BLUE then
color = ZONE_COLORS.BLUE_ATTACKED
US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
else
color = ZONE_COLORS.RED_ATTACKED
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 )
RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
end
ZoneCapture:DrawZone(-1, color, 0.5, color, 0.2, 2, true)
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
-- Create/update tactical information marker
CreateTacticalInfoMarker(ZoneCapture)
end
@ -591,13 +648,13 @@ local function CheckVictoryCondition()
"VICTORY! All capture zones have been secured by coalition forces!\n\n" ..
"Operation Polar Shield is complete. Outstanding work!\n" ..
"Mission will end in 60 seconds.",
MESSAGE.Type.Information, 30
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
RU_CC:MessageTypeToCoalition(
"DEFEAT! All strategic positions have been lost to coalition forces.\n\n" ..
"Operation Polar Shield has failed. Mission ending in 60 seconds.",
MESSAGE.Type.Information, 30
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
-- Add victory celebration effects
@ -632,13 +689,13 @@ local function CheckVictoryCondition()
"VICTORY! All strategic positions secured for the Motherland!\n\n" ..
"NATO forces have been repelled. Outstanding work!\n" ..
"Mission will end in 60 seconds.",
MESSAGE.Type.Information, 30
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
US_CC:MessageTypeToCoalition(
"DEFEAT! All capture zones have been lost to Russian forces.\n\n" ..
"Operation Polar Shield has failed. Mission ending in 60 seconds.",
MESSAGE.Type.Information, 30
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
-- Add victory celebration effects
@ -673,15 +730,17 @@ local function OnEnterCaptured(ZoneCapture)
if Coalition == coalition.side.BLUE then
-- Update zone visual markers to BLUE for captured
ZoneCapture:UndrawZone()
ZoneCapture:DrawZone(-1, ZONE_COLORS.BLUE_CAPTURED, 0.5, ZONE_COLORS.BLUE_CAPTURED, 0.2, 2, true)
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 )
local color = ZONE_COLORS.BLUE_CAPTURED
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
else
-- Update zone visual markers to RED for captured
ZoneCapture:UndrawZone()
ZoneCapture:DrawZone(-1, ZONE_COLORS.RED_CAPTURED, 0.5, ZONE_COLORS.RED_CAPTURED, 0.2, 2, true)
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 )
local color = ZONE_COLORS.RED_CAPTURED
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
end
ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", ZONE_SETTINGS.captureScore )
@ -712,17 +771,17 @@ for i, zoneCapture in ipairs(zoneCaptureObjects) do
-- Get initial coalition color for this zone
local initialCoalition = zoneCapture:GetCoalition()
local colorRGB = {0, 1, 0} -- Default green for neutral
local colorRGB = ZONE_COLORS.EMPTY
if initialCoalition == coalition.side.RED then
colorRGB = {1, 0, 0} -- Red
colorRGB = ZONE_COLORS.RED_CAPTURED
elseif initialCoalition == coalition.side.BLUE then
colorRGB = {0, 0, 1} -- Blue
colorRGB = ZONE_COLORS.BLUE_CAPTURED
end
-- Initialize zone borders with appropriate initial color
local drawSuccess, drawError = pcall(function()
zone:DrawZone(-1, colorRGB, 0.5, colorRGB, 0.2, 2, true)
zone:DrawZone(-1, {0, 0, 0}, 1, colorRGB, 0.2, 2, true)
end)
if not drawSuccess then
@ -834,13 +893,14 @@ local function BroadcastZoneStatus()
local fullMessage = reportMessage .. detailMessage
-- Broadcast to BOTH coalitions with their specific victory progress
local blueProgressPercent = math.floor((status.blue / status.total) * 100)
local totalZones = math.max(status.total, 1)
local blueProgressPercent = math.floor((status.blue / totalZones) * 100)
local blueFullMessage = fullMessage .. string.format("\n\nYour Progress to Victory: %d%%", blueProgressPercent)
US_CC:MessageTypeToCoalition( blueFullMessage, MESSAGE.Type.Information, 15 )
US_CC:MessageTypeToCoalition( blueFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION )
local redProgressPercent = math.floor((status.red / status.total) * 100)
local redProgressPercent = math.floor((status.red / totalZones) * 100)
local redFullMessage = fullMessage .. string.format("\n\nYour Progress to Victory: %d%%", redProgressPercent)
RU_CC:MessageTypeToCoalition( redFullMessage, MESSAGE.Type.Information, 15 )
RU_CC:MessageTypeToCoalition( redFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION )
log("[ZONE STATUS] " .. reportMessage:gsub("\n", " | "))
@ -856,13 +916,13 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
US_CC:MessageTypeToCoalition(
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
status.total - status.blue),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
RU_CC:MessageTypeToCoalition(
string.format("CRITICAL SITUATION! Coalition forces control %d/%d zones! We must recapture territory!",
status.blue, status.total),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
end
@ -871,17 +931,17 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
RU_CC:MessageTypeToCoalition(
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
status.total - status.red),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
US_CC:MessageTypeToCoalition(
string.format("CRITICAL SITUATION! Russian forces control %d/%d zones! We must recapture territory!",
status.red, status.total),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
end
end, {}, 10, 300 ) -- Start after 10 seconds, repeat every 300 seconds (5 minutes)
end, {}, MESSAGE_CONFIG.STATUS_BROADCAST_START_DELAY, MESSAGE_CONFIG.STATUS_BROADCAST_FREQUENCY )
-- Periodic zone color verification system (every 2 minutes)
local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
@ -897,8 +957,8 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
local zoneColor = GetZoneColor(zoneCapture)
-- Force redraw the zone with correct color based on CURRENT STATE
zoneCapture:UndrawZone()
zoneCapture:DrawZone(-1, zoneColor, 0.5, zoneColor, 0.2, 2, true)
zoneCapture:UndrawZone()
zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true)
-- Log the color assignment for debugging
local colorName = "UNKNOWN"
@ -917,20 +977,35 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
end
end
end, {}, 60, 120 ) -- Start after 60 seconds, repeat every 120 seconds (2 minutes)
end, {}, MESSAGE_CONFIG.COLOR_VERIFICATION_START_DELAY, MESSAGE_CONFIG.COLOR_VERIFICATION_FREQUENCY )
-- Periodic tactical marker update system (every 1 minute)
-- Periodic tactical marker update system with change detection
local __lastForceCountsByZone = {}
local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function()
log("[TACTICAL] Running periodic tactical marker update...")
-- Update tactical markers for all zones
log("[TACTICAL] Running periodic tactical marker update (change-detected)...")
for i, zoneCapture in ipairs(zoneCaptureObjects) do
if zoneCapture then
CreateTacticalInfoMarker(zoneCapture)
local zoneName = zoneCapture.GetZoneName and zoneCapture:GetZoneName() or (zoneNames[i] or ("Zone " .. i))
local counts = GetZoneForceStrengths(zoneCapture)
local last = __lastForceCountsByZone[zoneName]
local changed = (not last)
or (last.red ~= counts.red)
or (last.blue ~= counts.blue)
or (last.neutral ~= counts.neutral)
if changed then
__lastForceCountsByZone[zoneName] = {
red = counts.red,
blue = counts.blue,
neutral = counts.neutral
}
CreateTacticalInfoMarker(zoneCapture)
end
end
end
end, {}, 30, 60 ) -- Start after 30 seconds, repeat every 60 seconds (1 minute)
end, {}, MESSAGE_CONFIG.TACTICAL_UPDATE_START_DELAY, MESSAGE_CONFIG.TACTICAL_UPDATE_FREQUENCY )
-- Function to refresh all zone colors based on current ownership
local function RefreshAllZoneColors()
@ -945,11 +1020,11 @@ local function RefreshAllZoneColors()
-- Get color for current state/ownership
local zoneColor = GetZoneColor(zoneCapture)
-- Clear existing drawings
zoneCapture:UndrawZone()
-- Clear existing drawings
zoneCapture:UndrawZone()
-- Redraw with correct color
zoneCapture:DrawZone(-1, zoneColor, 0.5, zoneColor, 0.2, 2, true)
-- Redraw with correct color
zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true)
-- Log the color assignment for debugging
local colorName = "UNKNOWN"
@ -969,8 +1044,8 @@ local function RefreshAllZoneColors()
end
-- Notify BOTH coalitions
US_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, 5)
RU_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, 5)
US_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION)
RU_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION)
end
-- Manual zone status commands for players (F10 radio menu) - BOTH COALITIONS
@ -982,7 +1057,8 @@ local function SetupZoneStatusCommands()
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Check Victory Progress", USMenu, function()
local status = GetZoneOwnershipStatus()
local progressPercent = math.floor((status.blue / status.total) * 100)
local totalZones = math.max(status.total, 1)
local progressPercent = math.floor((status.blue / totalZones) * 100)
US_CC:MessageTypeToCoalition(
string.format(
@ -998,7 +1074,7 @@ local function SetupZoneStatusCommands()
progressPercent >= 50 and "GOOD PROGRESS!" or
"KEEP FIGHTING!"
),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION
)
end )
@ -1013,7 +1089,8 @@ local function SetupZoneStatusCommands()
MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function()
local status = GetZoneOwnershipStatus()
local progressPercent = math.floor((status.red / status.total) * 100)
local totalZones = math.max(status.total, 1)
local progressPercent = math.floor((status.red / totalZones) * 100)
RU_CC:MessageTypeToCoalition(
string.format(
@ -1029,7 +1106,7 @@ local function SetupZoneStatusCommands()
progressPercent >= 50 and "GOOD PROGRESS!" or
"KEEP FIGHTING!"
),
MESSAGE.Type.Information, 10
MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION
)
end )

View File

@ -5,7 +5,8 @@
DESCRIPTION:
This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them. It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system.
This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them.
It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system.
CONFIGURATION:
- Update static templates and airfield lists as needed for your mission.
@ -19,6 +20,13 @@ REQUIRES:
]]
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
if _G.__TDAC_DISPATCHER_RUNNING then
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
return
end
_G.__TDAC_DISPATCHER_RUNNING = true
--[[
GLOBAL STATE AND CONFIGURATION
--------------------------------------------------------------------------
@ -264,9 +272,9 @@ local function cleanupCargoMissions()
for _, coalitionKey in ipairs({"red", "blue"}) do
for i = #cargoMissions[coalitionKey], 1, -1 do
local m = cargoMissions[coalitionKey][i]
if m.status == "failed" then
if m.status == "failed" or m.status == "completed" then
if not (m.group and m.group:IsAlive()) then
log("Cleaning up failed cargo mission: " .. (m.group and m.group:GetName() or "nil group") .. " status: failed")
log("Cleaning up " .. m.status .. " cargo mission: " .. (m.group and m.group:GetName() or "nil group"))
table.remove(cargoMissions[coalitionKey], i)
end
end
@ -444,12 +452,21 @@ local function dispatchCargo(squadron, coalitionKey)
rat:SetDeparture(origin)
rat:SetDestination(destination)
rat:NoRespawn()
rat:InitUnControlled(false) -- ensure template-level 'Uncontrolled' flag does not leave transports parked
rat:InitUnControlled(false) -- force departing transports to spawn in a controllable state
rat:InitLateActivated(false)
rat:SetSpawnLimit(1)
rat:SetSpawnDelay(1)
-- Ensure RAT takes off immediately from the runway (hot start) instead of staying parked
if rat.SetTakeoffHot then rat:SetTakeoffHot() end
-- CRITICAL: Force takeoff from runway to prevent aircraft getting stuck at parking
-- SetTakeoffRunway() ensures aircraft spawn directly on runway and take off immediately
if rat.SetTakeoffRunway then
rat:SetTakeoffRunway()
log("DEBUG: Configured cargo to take off from runway at " .. origin, true)
else
log("WARNING: SetTakeoffRunway() not available - falling back to SetTakeoffHot()", true)
if rat.SetTakeoffHot then rat:SetTakeoffHot() end
end
-- Ensure RAT will look for parking and not despawn the group immediately on landing.
-- This makes the group taxi to parking and come to a stop so other scripts (e.g. Load2nd)
-- that detect parked/stopped cargo aircraft can register the delivery.

View File

@ -140,15 +140,15 @@ local TADC_SETTINGS = {
-- Timing settings (applies to both coalitions)
checkInterval = 30, -- How often to scan for threats (seconds)
monitorInterval = 30, -- How often to check interceptor status (seconds)
statusReportInterval = 120, -- How often to report airbase status (seconds)
squadronSummaryInterval = 600, -- How often to broadcast squadron summary (seconds)
statusReportInterval = 1805, -- How often to report airbase status (seconds)
squadronSummaryInterval = 1800, -- How often to broadcast squadron summary (seconds)
cargoCheckInterval = 15, -- How often to check for cargo deliveries (seconds)
-- RED Coalition Settings
red = {
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
squadronCooldown = 600, -- RED cooldown after squadron launch (seconds)
interceptRatio = 0.8, -- RED interceptors per threat aircraft
interceptRatio = 1.2, -- RED interceptors per threat aircraft
cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
rtbFlightBuffer = 300, -- RED extra landing time before cleanup (seconds)
@ -158,7 +158,7 @@ local TADC_SETTINGS = {
blue = {
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
squadronCooldown = 600, -- BLUE cooldown after squadron launch (seconds)
interceptRatio = 0.8, -- BLUE interceptors per threat aircraft
interceptRatio = 1.2, -- BLUE interceptors per threat aircraft
cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
rtbFlightBuffer = 300, -- BLUE extra landing time before cleanup (seconds)
@ -276,6 +276,105 @@ local airbaseHealthStatus = {
blue = {}
}
local function coalitionKeyFromSide(side)
if side == coalition.side.RED then return "red" end
if side == coalition.side.BLUE then return "blue" end
return nil
end
local function cleanupInterceptorEntry(interceptorName, coalitionKey)
if not interceptorName or not coalitionKey then return end
if activeInterceptors[coalitionKey] then
activeInterceptors[coalitionKey][interceptorName] = nil
end
if aircraftSpawnTracking[coalitionKey] then
aircraftSpawnTracking[coalitionKey][interceptorName] = nil
end
end
local function destroyInterceptorGroup(interceptor, coalitionKey, delaySeconds)
if not interceptor then return end
local name = nil
if interceptor.GetName then
local ok, value = pcall(function() return interceptor:GetName() end)
if ok then name = value end
end
local resolvedKey = coalitionKey
if not resolvedKey and interceptor.GetCoalition then
local ok, side = pcall(function() return interceptor:GetCoalition() end)
if ok then
resolvedKey = coalitionKeyFromSide(side)
end
end
local function doDestroy()
if interceptor and interceptor.IsAlive and interceptor:IsAlive() then
pcall(function() interceptor:Destroy() end)
end
if name and resolvedKey then
cleanupInterceptorEntry(name, resolvedKey)
end
end
if delaySeconds and delaySeconds > 0 then
timer.scheduleFunction(function()
doDestroy()
return
end, {}, timer.getTime() + delaySeconds)
else
doDestroy()
end
end
local function finalizeCargoMission(cargoGroup, squadron, coalitionKey)
if not cargoMissions or not coalitionKey or not squadron or not squadron.airbaseName then
return
end
local coalitionBucket = cargoMissions[coalitionKey]
if type(coalitionBucket) ~= "table" then
return
end
local groupName = nil
if cargoGroup and cargoGroup.GetName then
local ok, value = pcall(function() return cargoGroup:GetName() end)
if ok then groupName = value end
end
for idx = #coalitionBucket, 1, -1 do
local mission = coalitionBucket[idx]
if mission and mission.destination == squadron.airbaseName then
local missionGroupName = nil
if mission.group and mission.group.GetName then
local ok, value = pcall(function() return mission.group:GetName() end)
if ok then missionGroupName = value end
end
if not groupName or missionGroupName == groupName then
mission.status = "completed"
mission.completedAt = timer.getTime()
if mission.group and mission.group.Destroy then
local targetGroup = mission.group
timer.scheduleFunction(function()
pcall(function()
if targetGroup and targetGroup.IsAlive and targetGroup:IsAlive() then
targetGroup:Destroy()
end
end)
return
end, {}, timer.getTime() + 90)
end
table.remove(coalitionBucket, idx)
end
end
end
end
-- Logging function
local function log(message, detailed)
if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then
@ -709,6 +808,8 @@ local function processCargoDelivery(cargoGroup, squadron, coalitionSide, coaliti
MESSAGE:New(msg, 10):ToCoalition(coalitionSide)
USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
end
finalizeCargoMission(cargoGroup, squadron, coalitionKey)
end
-- Event handler for cargo aircraft landing (backup for actual landings)
@ -1271,10 +1372,9 @@ local function monitorStuckAircraft()
-- Mark airbase as having stuck aircraft
airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft"
-- Remove the stuck aircraft
trackingData.group:Destroy()
activeInterceptors[coalitionKey][aircraftName] = nil
aircraftSpawnTracking[coalitionKey][aircraftName] = nil
-- Remove the stuck aircraft and clear tracking
pcall(function() trackingData.group:Destroy() end)
cleanupInterceptorEntry(aircraftName, coalitionKey)
-- Reassign squadron to alternative airbase
reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey)
@ -1342,9 +1442,14 @@ local function sendInterceptorHome(interceptor, coalitionSide)
SCHEDULER:New(nil, function()
local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
if activeInterceptors[coalitionKey][interceptor:GetName()] then
activeInterceptors[coalitionKey][interceptor:GetName()] = nil
log("Cleaned up " .. coalitionName .. " " .. interceptor:GetName() .. " after RTB", true)
local name = nil
if interceptor and interceptor.GetName then
local ok, value = pcall(function() return interceptor:GetName() end)
if ok then name = value end
end
if name and activeInterceptors[coalitionKey][name] then
destroyInterceptorGroup(interceptor, coalitionKey, 0)
log("Cleaned up " .. coalitionName .. " " .. name .. " after RTB", true)
end
end, {}, flightTime)
else
@ -1616,6 +1721,7 @@ local function launchInterceptor(threatGroup, coalitionSide)
log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName)
return
end
spawn:InitCleanUp(900)
local interceptors = {}
@ -1674,11 +1780,14 @@ local function launchInterceptor(threatGroup, coalitionSide)
-- Emergency cleanup (safety net)
SCHEDULER:New(nil, function()
if activeInterceptors[coalitionKey][interceptor:GetName()] then
log("Emergency cleanup of " .. coalitionName .. " " .. interceptor:GetName() .. " (should have RTB'd)")
activeInterceptors[coalitionKey][interceptor:GetName()] = nil
-- Also clean up spawn tracking
aircraftSpawnTracking[coalitionKey][interceptor:GetName()] = nil
local name = nil
if interceptor and interceptor.GetName then
local ok, value = pcall(function() return interceptor:GetName() end)
if ok then name = value end
end
if name and activeInterceptors[coalitionKey][name] then
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
destroyInterceptorGroup(interceptor, coalitionKey, 0)
end
end, {}, coalitionSettings.emergencyCleanupTime)
end
@ -2182,20 +2291,29 @@ end
initializeSystem()
-- Add F10 menu command for squadron summary
local menuRoot = MENU_MISSION:New("TADC Utilities")
-- Use MenuManager to create coalition-specific menus (not mission-wide)
local menuRootBlue, menuRootRed
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Squadron Resource Summary", menuRoot, function()
if MenuManager then
menuRootBlue = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "TADC Utilities")
menuRootRed = MenuManager.CreateCoalitionMenu(coalition.side.RED, "TADC Utilities")
else
menuRootBlue = MENU_COALITION:New(coalition.side.BLUE, "TADC Utilities")
menuRootRed = MENU_COALITION:New(coalition.side.RED, "TADC Utilities")
end
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Squadron Resource Summary", menuRootRed, function()
local summary = getSquadronResourceSummary(coalition.side.RED)
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Squadron Resource Summary", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Squadron Resource Summary", menuRootBlue, function()
local summary = getSquadronResourceSummary(coalition.side.BLUE)
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
end)
-- 1. Show Airbase Status Report
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Status Report", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Status Report", menuRootRed, function()
local report = "=== RED Airbase Status ===\n"
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED)
@ -2212,7 +2330,7 @@ MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Status Report", men
MESSAGE:New(report, 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Status Report", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Status Report", menuRootBlue, function()
local report = "=== BLUE Airbase Status ===\n"
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE)
@ -2230,7 +2348,7 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Status Report", me
end)
-- 2. Show Active Interceptors
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Active Interceptors", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Active Interceptors", menuRootRed, function()
local lines = {"Active RED Interceptors:"}
for name, data in pairs(activeInterceptors.red) do
if data and data.group and data.group:IsAlive() then
@ -2240,7 +2358,7 @@ MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Active Interceptors", menuR
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Active Interceptors", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Active Interceptors", menuRootBlue, function()
local lines = {"Active BLUE Interceptors:"}
for name, data in pairs(activeInterceptors.blue) do
if data and data.group and data.group:IsAlive() then
@ -2251,7 +2369,7 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Active Interceptors", menu
end)
-- 3. Show Threat Summary
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Threat Summary", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Threat Summary", menuRootRed, function()
local lines = {"Detected BLUE Threats:"}
if cachedSets.blueAircraft then
cachedSets.blueAircraft:ForEach(function(group)
@ -2263,7 +2381,7 @@ MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Threat Summary", menuRoot,
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRootBlue, function()
local lines = {"Detected RED Threats:"}
if cachedSets.redAircraft then
cachedSets.redAircraft:ForEach(function(group)
@ -2276,18 +2394,18 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRoot,
end)
-- 4. Request Immediate Squadron Summary Broadcast
MENU_COALITION_COMMAND:New(coalition.side.RED, "Broadcast Squadron Summary Now", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Broadcast Squadron Summary Now", menuRootRed, function()
local summary = getSquadronResourceSummary(coalition.side.RED)
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Broadcast Squadron Summary Now", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Broadcast Squadron Summary Now", menuRootBlue, function()
local summary = getSquadronResourceSummary(coalition.side.BLUE)
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
end)
-- 5. Show Cargo Delivery Log
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Cargo Delivery Log", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Cargo Delivery Log", menuRootRed, function()
local lines = {"Recent RED Cargo Deliveries:"}
if _G.processedDeliveries then
for key, timestamp in pairs(_G.processedDeliveries) do
@ -2299,7 +2417,7 @@ MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Cargo Delivery Log", menuRo
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Cargo Delivery Log", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Cargo Delivery Log", menuRootBlue, function()
local lines = {"Recent BLUE Cargo Deliveries:"}
if _G.processedDeliveries then
for key, timestamp in pairs(_G.processedDeliveries) do
@ -2312,7 +2430,7 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Cargo Delivery Log", menuR
end)
-- 6. Show Zone Coverage Map
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Zone Coverage Map", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Zone Coverage Map", menuRootRed, function()
local lines = {"RED Zone Coverage:"}
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
local zones = {}
@ -2324,7 +2442,7 @@ MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Zone Coverage Map", menuRoo
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Zone Coverage Map", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Zone Coverage Map", menuRootBlue, function()
local lines = {"BLUE Zone Coverage:"}
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
local zones = {}
@ -2336,40 +2454,71 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Zone Coverage Map", menuRo
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
end)
-- 7. Request Emergency Cleanup (admin/global)
MENU_MISSION_COMMAND:New("Emergency Cleanup Interceptors", menuRoot, function()
-- 7. Admin/Debug Commands - Create submenus under each coalition's TADC Utilities
local menuAdminBlue = MENU_COALITION:New(coalition.side.BLUE, "Admin / Debug", menuRootBlue)
local menuAdminRed = MENU_COALITION:New(coalition.side.RED, "Admin / Debug", menuRootRed)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Emergency Cleanup Interceptors", menuAdminBlue, function()
local cleaned = 0
for _, interceptors in pairs(activeInterceptors.red) do
for name, interceptors in pairs(activeInterceptors.red) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil
cleanupInterceptorEntry(name, "red")
cleaned = cleaned + 1
end
end
for _, interceptors in pairs(activeInterceptors.blue) do
for name, interceptors in pairs(activeInterceptors.blue) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil
cleanupInterceptorEntry(name, "blue")
cleaned = cleaned + 1
end
end
MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToAll()
MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToBlue()
end)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Emergency Cleanup Interceptors", menuAdminRed, function()
local cleaned = 0
for name, interceptors in pairs(activeInterceptors.red) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
cleanupInterceptorEntry(name, "red")
cleaned = cleaned + 1
end
end
for name, interceptors in pairs(activeInterceptors.blue) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
cleanupInterceptorEntry(name, "blue")
cleaned = cleaned + 1
end
end
MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToRed()
end)
-- 9. Show System Uptime/Status
local systemStartTime = timer.getTime()
MENU_MISSION_COMMAND:New("Show TADC System Status", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show TADC System Status", menuAdminBlue, function()
local uptime = math.floor((timer.getTime() - systemStartTime) / 60)
local status = string.format("TADC System Uptime: %d minutes\nCheck Interval: %ds\nMonitor Interval: %ds\nStatus Report Interval: %ds\nSquadron Summary Interval: %ds\nCargo Check Interval: %ds", uptime, TADC_SETTINGS.checkInterval, TADC_SETTINGS.monitorInterval, TADC_SETTINGS.statusReportInterval, TADC_SETTINGS.squadronSummaryInterval, TADC_SETTINGS.cargoCheckInterval)
MESSAGE:New(status, 20):ToAll()
MESSAGE:New(status, 20):ToBlue()
end)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show TADC System Status", menuAdminRed, function()
local uptime = math.floor((timer.getTime() - systemStartTime) / 60)
local status = string.format("TADC System Uptime: %d minutes\nCheck Interval: %ds\nMonitor Interval: %ds\nStatus Report Interval: %ds\nSquadron Summary Interval: %ds\nCargo Check Interval: %ds", uptime, TADC_SETTINGS.checkInterval, TADC_SETTINGS.monitorInterval, TADC_SETTINGS.statusReportInterval, TADC_SETTINGS.squadronSummaryInterval, TADC_SETTINGS.cargoCheckInterval)
MESSAGE:New(status, 20):ToRed()
end)
-- 10. Check for Stuck Aircraft (manual trigger)
MENU_MISSION_COMMAND:New("Check for Stuck Aircraft", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Check for Stuck Aircraft", menuAdminBlue, function()
monitorStuckAircraft()
MESSAGE:New("Stuck aircraft check completed", 10):ToAll()
MESSAGE:New("Stuck aircraft check completed", 10):ToBlue()
end)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Check for Stuck Aircraft", menuAdminRed, function()
monitorStuckAircraft()
MESSAGE:New("Stuck aircraft check completed", 10):ToRed()
end)
-- 11. Show Airbase Health Status
MENU_MISSION_COMMAND:New("Show Airbase Health Status", menuRoot, function()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Health Status", menuAdminBlue, function()
local lines = {"Airbase Health Status:"}
for _, coalitionKey in ipairs({"red", "blue"}) do
local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
@ -2378,7 +2527,19 @@ MENU_MISSION_COMMAND:New("Show Airbase Health Status", menuRoot, function()
table.insert(lines, " " .. airbaseName .. ": " .. status)
end
end
MESSAGE:New(table.concat(lines, "\n"), 20):ToAll()
MESSAGE:New(table.concat(lines, "\n"), 20):ToBlue()
end)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Health Status", menuAdminRed, function()
local lines = {"Airbase Health Status:"}
for _, coalitionKey in ipairs({"red", "blue"}) do
local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
table.insert(lines, coalitionName .. " Coalition:")
for airbaseName, status in pairs(airbaseHealthStatus[coalitionKey]) do
table.insert(lines, " " .. airbaseName .. ": " .. status)
end
end
MESSAGE:New(table.concat(lines, "\n"), 20):ToRed()
end)
-- Initialize airbase health status for all configured airbases

Binary file not shown.