mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Updated with performance tweaks.
This commit is contained in:
parent
830f08441d
commit
a0accf2adc
@ -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 )
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
Loading…
x
Reference in New Issue
Block a user