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
|
-- 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)
|
-- ZONE COLOR CONFIGURATION (Centralized)
|
||||||
-- ==========================================
|
-- ==========================================
|
||||||
@ -164,7 +179,7 @@ end
|
|||||||
-- Logging configuration: toggle logging behavior for this module
|
-- Logging configuration: toggle logging behavior for this module
|
||||||
-- Set `CAPTURE_ZONE_LOGGING.enabled = false` to silence module logs
|
-- Set `CAPTURE_ZONE_LOGGING.enabled = false` to silence module logs
|
||||||
if not CAPTURE_ZONE_LOGGING then
|
if not CAPTURE_ZONE_LOGGING then
|
||||||
CAPTURE_ZONE_LOGGING = { enabled = true, prefix = "[CAPTURE Module]" }
|
CAPTURE_ZONE_LOGGING = { enabled = false, prefix = "[CAPTURE Module]" }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function log(message, detailed)
|
local function log(message, detailed)
|
||||||
@ -254,43 +269,63 @@ end
|
|||||||
local totalZones = InitializeZones()
|
local totalZones = InitializeZones()
|
||||||
|
|
||||||
|
|
||||||
-- Helper functions for tactical information
|
|
||||||
|
|
||||||
-- Global cached unit set - created once and maintained automatically by MOOSE
|
-- Global cached unit set - created once and maintained automatically by MOOSE
|
||||||
local CachedUnitSet = nil
|
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
|
-- Initialize the cached unit set once
|
||||||
local function InitializeCachedUnitSet()
|
local function InitializeCachedUnitSet()
|
||||||
if not CachedUnitSet then
|
if not CachedUnitSet then
|
||||||
CachedUnitSet = SET_UNIT:New()
|
CachedUnitSet = SET_UNIT:New()
|
||||||
:FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types
|
: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")
|
log("[PERFORMANCE] Initialized cached unit set for zone scanning")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetZoneForceStrengths(ZoneCapture)
|
local function GetZoneForceStrengths(ZoneCapture)
|
||||||
if not ZoneCapture then
|
if not ZoneCapture then
|
||||||
return {red = 0, blue = 0, neutral = 0}
|
return { red = 0, blue = 0, neutral = 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, zone = pcall(function() return ZoneCapture:GetZone() end)
|
local success, zone = pcall(function()
|
||||||
if not success or not zone then
|
return ZoneCapture:GetZone()
|
||||||
return {red = 0, blue = 0, neutral = 0}
|
end)
|
||||||
|
|
||||||
|
if not success or not zone then
|
||||||
|
return { red = 0, blue = 0, neutral = 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
local redCount = 0
|
local redCount = 0
|
||||||
local blueCount = 0
|
local blueCount = 0
|
||||||
local neutralCount = 0
|
local neutralCount = 0
|
||||||
|
|
||||||
-- Get all units in the zone using MOOSE's zone scanning
|
InitializeCachedUnitSet()
|
||||||
local unitsInZone = SET_UNIT:New()
|
|
||||||
:FilterZones({zone})
|
if CachedUnitSet then
|
||||||
:FilterOnce()
|
CachedUnitSet:ForEachUnit(function(unit)
|
||||||
|
if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
|
||||||
if unitsInZone then
|
|
||||||
unitsInZone:ForEachUnit(function(unit)
|
|
||||||
if unit and unit:IsAlive() then
|
|
||||||
local unitCoalition = unit:GetCoalition()
|
local unitCoalition = unit:GetCoalition()
|
||||||
if unitCoalition == coalition.side.RED then
|
if unitCoalition == coalition.side.RED then
|
||||||
redCount = redCount + 1
|
redCount = redCount + 1
|
||||||
@ -302,10 +337,10 @@ local function GetZoneForceStrengths(ZoneCapture)
|
|||||||
end
|
end
|
||||||
end)
|
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))
|
ZoneCapture:GetZoneName(), redCount, blueCount, neutralCount))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
red = redCount,
|
red = redCount,
|
||||||
blue = blueCount,
|
blue = blueCount,
|
||||||
@ -314,63 +349,62 @@ local function GetZoneForceStrengths(ZoneCapture)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
|
local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
|
||||||
local zone = ZoneCapture:GetZone()
|
if not ZoneCapture or not enemyCoalition then
|
||||||
if not zone then return {} end
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local success, zone = pcall(function()
|
||||||
|
return ZoneCapture:GetZone()
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not success or not zone then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
InitializeCachedUnitSet()
|
||||||
|
|
||||||
local coords = {}
|
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 totalUnits = 0
|
||||||
local enemyUnits = 0
|
local enemyUnits = 0
|
||||||
local unitsWithCoords = 0
|
local unitsWithCoords = 0
|
||||||
|
|
||||||
if unitsInZone then
|
if CachedUnitSet then
|
||||||
unitsInZone:ForEachUnit(function(unit)
|
CachedUnitSet:ForEachUnit(function(unit)
|
||||||
totalUnits = totalUnits + 1
|
if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
|
||||||
if unit and unit:IsAlive() then
|
totalUnits = totalUnits + 1
|
||||||
local unitCoalition = unit:GetCoalition()
|
local unitCoalition = unit:GetCoalition()
|
||||||
|
|
||||||
-- Process units of the specified enemy coalition
|
|
||||||
if unitCoalition == enemyCoalition then
|
if unitCoalition == enemyCoalition then
|
||||||
enemyUnits = enemyUnits + 1
|
enemyUnits = enemyUnits + 1
|
||||||
local coord = unit:GetCoordinate()
|
local coord = unit:GetCoordinate()
|
||||||
|
|
||||||
if coord then
|
if coord then
|
||||||
-- Try multiple methods to get coordinates
|
|
||||||
local mgrs = nil
|
local mgrs = nil
|
||||||
local success_mgrs = false
|
local success_mgrs = false
|
||||||
|
|
||||||
-- Method 1: Try ToStringMGRS
|
|
||||||
success_mgrs, mgrs = pcall(function()
|
success_mgrs, mgrs = pcall(function()
|
||||||
return coord:ToStringMGRS(5)
|
return coord:ToStringMGRS(5)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Method 2: Try ToStringMGRS without precision parameter
|
|
||||||
if not success_mgrs or not mgrs then
|
if not success_mgrs or not mgrs then
|
||||||
success_mgrs, mgrs = pcall(function()
|
success_mgrs, mgrs = pcall(function()
|
||||||
return coord:ToStringMGRS()
|
return coord:ToStringMGRS()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Method 3: Try ToMGRS
|
|
||||||
if not success_mgrs or not mgrs then
|
if not success_mgrs or not mgrs then
|
||||||
success_mgrs, mgrs = pcall(function()
|
success_mgrs, mgrs = pcall(function()
|
||||||
return coord:ToMGRS()
|
return coord:ToMGRS()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Method 4: Fallback to Lat/Long
|
|
||||||
if not success_mgrs or not mgrs then
|
if not success_mgrs or not mgrs then
|
||||||
success_mgrs, mgrs = pcall(function()
|
success_mgrs, mgrs = pcall(function()
|
||||||
local lat, lon = coord:GetLLDDM()
|
local lat, lon = coord:GetLLDDM()
|
||||||
return string.format("N%s E%s", lat, lon)
|
return string.format("N%s E%s", lat, lon)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if success_mgrs and mgrs then
|
if success_mgrs and mgrs then
|
||||||
unitsWithCoords = unitsWithCoords + 1
|
unitsWithCoords = unitsWithCoords + 1
|
||||||
local unitType = unit:GetTypeName() or "Unknown"
|
local unitType = unit:GetTypeName() or "Unknown"
|
||||||
@ -389,13 +423,13 @@ local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
|
|||||||
end
|
end
|
||||||
end)
|
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))
|
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()))
|
#coords, ZoneCapture:GetZoneName()))
|
||||||
|
|
||||||
return coords
|
return coords
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -463,19 +497,39 @@ local function CreateTacticalInfoMarker(ZoneCapture)
|
|||||||
if coord then
|
if coord then
|
||||||
local offsetCoord = coord:Translate(200, 45) -- 200m NE
|
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
|
-- Remove legacy single marker if present
|
||||||
if ZoneCapture.TacticalMarkerID then
|
if ZoneCapture.TacticalMarkerID then
|
||||||
log(string.format("[TACTICAL] Removing old marker ID %d for %s", ZoneCapture.TacticalMarkerID, zoneName))
|
log(string.format("[TACTICAL] Removing old marker ID %d for %s", ZoneCapture.TacticalMarkerID, zoneName))
|
||||||
pcall(function() offsetCoord:RemoveMark(ZoneCapture.TacticalMarkerID) end)
|
removeMarker(ZoneCapture.TacticalMarkerID)
|
||||||
pcall(function() trigger.action.removeMark(ZoneCapture.TacticalMarkerID) end)
|
|
||||||
pcall(function() coord:RemoveMark(ZoneCapture.TacticalMarkerID) end)
|
|
||||||
ZoneCapture.TacticalMarkerID = nil
|
ZoneCapture.TacticalMarkerID = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- BLUE Coalition Marker
|
-- BLUE Coalition Marker
|
||||||
if ZoneCapture.TacticalMarkerID_BLUE then
|
if ZoneCapture.TacticalMarkerID_BLUE then
|
||||||
log(string.format("[TACTICAL] Removing old BLUE marker ID %d for %s", ZoneCapture.TacticalMarkerID_BLUE, zoneName))
|
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
|
ZoneCapture.TacticalMarkerID_BLUE = nil
|
||||||
end
|
end
|
||||||
local successBlue, markerIDBlue = pcall(function()
|
local successBlue, markerIDBlue = pcall(function()
|
||||||
@ -492,7 +546,7 @@ local function CreateTacticalInfoMarker(ZoneCapture)
|
|||||||
-- RED Coalition Marker
|
-- RED Coalition Marker
|
||||||
if ZoneCapture.TacticalMarkerID_RED then
|
if ZoneCapture.TacticalMarkerID_RED then
|
||||||
log(string.format("[TACTICAL] Removing old RED marker ID %d for %s", ZoneCapture.TacticalMarkerID_RED, zoneName))
|
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
|
ZoneCapture.TacticalMarkerID_RED = nil
|
||||||
end
|
end
|
||||||
local successRed, markerIDRed = pcall(function()
|
local successRed, markerIDRed = pcall(function()
|
||||||
@ -516,16 +570,18 @@ local function OnEnterGuarded(ZoneCapture, From, Event, To)
|
|||||||
ZoneCapture:Smoke( SMOKECOLOR.Blue )
|
ZoneCapture:Smoke( SMOKECOLOR.Blue )
|
||||||
-- Update zone visual markers to BLUE
|
-- Update zone visual markers to BLUE
|
||||||
ZoneCapture:UndrawZone()
|
ZoneCapture:UndrawZone()
|
||||||
ZoneCapture:DrawZone(-1, ZONE_COLORS.BLUE_CAPTURED, 0.5, ZONE_COLORS.BLUE_CAPTURED, 0.2, 2, true)
|
local color = ZONE_COLORS.BLUE_CAPTURED
|
||||||
US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
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
|
else
|
||||||
ZoneCapture:Smoke( SMOKECOLOR.Red )
|
ZoneCapture:Smoke( SMOKECOLOR.Red )
|
||||||
-- Update zone visual markers to RED
|
-- Update zone visual markers to RED
|
||||||
ZoneCapture:UndrawZone()
|
ZoneCapture:UndrawZone()
|
||||||
ZoneCapture:DrawZone(-1, ZONE_COLORS.RED_CAPTURED, 0.5, ZONE_COLORS.RED_CAPTURED, 0.2, 2, true)
|
local color = ZONE_COLORS.RED_CAPTURED
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
|
||||||
US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
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
|
end
|
||||||
-- Create/update tactical information marker
|
-- Create/update tactical information marker
|
||||||
CreateTacticalInfoMarker(ZoneCapture)
|
CreateTacticalInfoMarker(ZoneCapture)
|
||||||
@ -536,9 +592,10 @@ local function OnEnterEmpty(ZoneCapture)
|
|||||||
ZoneCapture:Smoke( SMOKECOLOR.Green )
|
ZoneCapture:Smoke( SMOKECOLOR.Green )
|
||||||
-- Update zone visual markers to GREEN (neutral)
|
-- Update zone visual markers to GREEN (neutral)
|
||||||
ZoneCapture:UndrawZone()
|
ZoneCapture:UndrawZone()
|
||||||
ZoneCapture:DrawZone(-1, ZONE_COLORS.EMPTY, 0.5, ZONE_COLORS.EMPTY, 0.2, 2, true)
|
local color = ZONE_COLORS.EMPTY
|
||||||
US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
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
|
-- Create/update tactical information marker
|
||||||
CreateTacticalInfoMarker(ZoneCapture)
|
CreateTacticalInfoMarker(ZoneCapture)
|
||||||
end
|
end
|
||||||
@ -551,14 +608,14 @@ local function OnEnterAttacked(ZoneCapture)
|
|||||||
local color
|
local color
|
||||||
if Coalition == coalition.side.BLUE then
|
if Coalition == coalition.side.BLUE then
|
||||||
color = ZONE_COLORS.BLUE_ATTACKED
|
color = ZONE_COLORS.BLUE_ATTACKED
|
||||||
US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", 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 )
|
RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
|
||||||
else
|
else
|
||||||
color = ZONE_COLORS.RED_ATTACKED
|
color = ZONE_COLORS.RED_ATTACKED
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", 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 )
|
US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION )
|
||||||
end
|
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
|
-- Create/update tactical information marker
|
||||||
CreateTacticalInfoMarker(ZoneCapture)
|
CreateTacticalInfoMarker(ZoneCapture)
|
||||||
end
|
end
|
||||||
@ -591,13 +648,13 @@ local function CheckVictoryCondition()
|
|||||||
"VICTORY! All capture zones have been secured by coalition forces!\n\n" ..
|
"VICTORY! All capture zones have been secured by coalition forces!\n\n" ..
|
||||||
"Operation Polar Shield is complete. Outstanding work!\n" ..
|
"Operation Polar Shield is complete. Outstanding work!\n" ..
|
||||||
"Mission will end in 60 seconds.",
|
"Mission will end in 60 seconds.",
|
||||||
MESSAGE.Type.Information, 30
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
|
|
||||||
RU_CC:MessageTypeToCoalition(
|
RU_CC:MessageTypeToCoalition(
|
||||||
"DEFEAT! All strategic positions have been lost to coalition forces.\n\n" ..
|
"DEFEAT! All strategic positions have been lost to coalition forces.\n\n" ..
|
||||||
"Operation Polar Shield has failed. Mission ending in 60 seconds.",
|
"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
|
-- Add victory celebration effects
|
||||||
@ -632,13 +689,13 @@ local function CheckVictoryCondition()
|
|||||||
"VICTORY! All strategic positions secured for the Motherland!\n\n" ..
|
"VICTORY! All strategic positions secured for the Motherland!\n\n" ..
|
||||||
"NATO forces have been repelled. Outstanding work!\n" ..
|
"NATO forces have been repelled. Outstanding work!\n" ..
|
||||||
"Mission will end in 60 seconds.",
|
"Mission will end in 60 seconds.",
|
||||||
MESSAGE.Type.Information, 30
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
|
|
||||||
US_CC:MessageTypeToCoalition(
|
US_CC:MessageTypeToCoalition(
|
||||||
"DEFEAT! All capture zones have been lost to Russian forces.\n\n" ..
|
"DEFEAT! All capture zones have been lost to Russian forces.\n\n" ..
|
||||||
"Operation Polar Shield has failed. Mission ending in 60 seconds.",
|
"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
|
-- Add victory celebration effects
|
||||||
@ -673,15 +730,17 @@ local function OnEnterCaptured(ZoneCapture)
|
|||||||
if Coalition == coalition.side.BLUE then
|
if Coalition == coalition.side.BLUE then
|
||||||
-- Update zone visual markers to BLUE for captured
|
-- Update zone visual markers to BLUE for captured
|
||||||
ZoneCapture:UndrawZone()
|
ZoneCapture:UndrawZone()
|
||||||
ZoneCapture:DrawZone(-1, ZONE_COLORS.BLUE_CAPTURED, 0.5, ZONE_COLORS.BLUE_CAPTURED, 0.2, 2, true)
|
local color = ZONE_COLORS.BLUE_CAPTURED
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
|
||||||
US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
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
|
else
|
||||||
-- Update zone visual markers to RED for captured
|
-- Update zone visual markers to RED for captured
|
||||||
ZoneCapture:UndrawZone()
|
ZoneCapture:UndrawZone()
|
||||||
ZoneCapture:DrawZone(-1, ZONE_COLORS.RED_CAPTURED, 0.5, ZONE_COLORS.RED_CAPTURED, 0.2, 2, true)
|
local color = ZONE_COLORS.RED_CAPTURED
|
||||||
US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
|
||||||
RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
|
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
|
end
|
||||||
|
|
||||||
ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", ZONE_SETTINGS.captureScore )
|
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
|
-- Get initial coalition color for this zone
|
||||||
local initialCoalition = zoneCapture:GetCoalition()
|
local initialCoalition = zoneCapture:GetCoalition()
|
||||||
local colorRGB = {0, 1, 0} -- Default green for neutral
|
local colorRGB = ZONE_COLORS.EMPTY
|
||||||
|
|
||||||
if initialCoalition == coalition.side.RED then
|
if initialCoalition == coalition.side.RED then
|
||||||
colorRGB = {1, 0, 0} -- Red
|
colorRGB = ZONE_COLORS.RED_CAPTURED
|
||||||
elseif initialCoalition == coalition.side.BLUE then
|
elseif initialCoalition == coalition.side.BLUE then
|
||||||
colorRGB = {0, 0, 1} -- Blue
|
colorRGB = ZONE_COLORS.BLUE_CAPTURED
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialize zone borders with appropriate initial color
|
-- Initialize zone borders with appropriate initial color
|
||||||
local drawSuccess, drawError = pcall(function()
|
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)
|
end)
|
||||||
|
|
||||||
if not drawSuccess then
|
if not drawSuccess then
|
||||||
@ -834,13 +893,14 @@ local function BroadcastZoneStatus()
|
|||||||
local fullMessage = reportMessage .. detailMessage
|
local fullMessage = reportMessage .. detailMessage
|
||||||
|
|
||||||
-- Broadcast to BOTH coalitions with their specific victory progress
|
-- 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)
|
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)
|
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", " | "))
|
log("[ZONE STATUS] " .. reportMessage:gsub("\n", " | "))
|
||||||
|
|
||||||
@ -856,13 +916,13 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
|
|||||||
US_CC:MessageTypeToCoalition(
|
US_CC:MessageTypeToCoalition(
|
||||||
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
|
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
|
||||||
status.total - status.blue),
|
status.total - status.blue),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
|
|
||||||
RU_CC:MessageTypeToCoalition(
|
RU_CC:MessageTypeToCoalition(
|
||||||
string.format("CRITICAL SITUATION! Coalition forces control %d/%d zones! We must recapture territory!",
|
string.format("CRITICAL SITUATION! Coalition forces control %d/%d zones! We must recapture territory!",
|
||||||
status.blue, status.total),
|
status.blue, status.total),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -871,17 +931,17 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
|
|||||||
RU_CC:MessageTypeToCoalition(
|
RU_CC:MessageTypeToCoalition(
|
||||||
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
|
string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!",
|
||||||
status.total - status.red),
|
status.total - status.red),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
|
|
||||||
US_CC:MessageTypeToCoalition(
|
US_CC:MessageTypeToCoalition(
|
||||||
string.format("CRITICAL SITUATION! Russian forces control %d/%d zones! We must recapture territory!",
|
string.format("CRITICAL SITUATION! Russian forces control %d/%d zones! We must recapture territory!",
|
||||||
status.red, status.total),
|
status.red, status.total),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
end
|
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)
|
-- Periodic zone color verification system (every 2 minutes)
|
||||||
local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
||||||
@ -897,8 +957,8 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
|||||||
local zoneColor = GetZoneColor(zoneCapture)
|
local zoneColor = GetZoneColor(zoneCapture)
|
||||||
|
|
||||||
-- Force redraw the zone with correct color based on CURRENT STATE
|
-- Force redraw the zone with correct color based on CURRENT STATE
|
||||||
zoneCapture:UndrawZone()
|
zoneCapture:UndrawZone()
|
||||||
zoneCapture:DrawZone(-1, zoneColor, 0.5, zoneColor, 0.2, 2, true)
|
zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true)
|
||||||
|
|
||||||
-- Log the color assignment for debugging
|
-- Log the color assignment for debugging
|
||||||
local colorName = "UNKNOWN"
|
local colorName = "UNKNOWN"
|
||||||
@ -917,20 +977,35 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
|||||||
end
|
end
|
||||||
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()
|
local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function()
|
||||||
log("[TACTICAL] Running periodic tactical marker update...")
|
log("[TACTICAL] Running periodic tactical marker update (change-detected)...")
|
||||||
|
|
||||||
-- Update tactical markers for all zones
|
|
||||||
for i, zoneCapture in ipairs(zoneCaptureObjects) do
|
for i, zoneCapture in ipairs(zoneCaptureObjects) do
|
||||||
if zoneCapture then
|
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
|
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
|
-- Function to refresh all zone colors based on current ownership
|
||||||
local function RefreshAllZoneColors()
|
local function RefreshAllZoneColors()
|
||||||
@ -945,11 +1020,11 @@ local function RefreshAllZoneColors()
|
|||||||
-- Get color for current state/ownership
|
-- Get color for current state/ownership
|
||||||
local zoneColor = GetZoneColor(zoneCapture)
|
local zoneColor = GetZoneColor(zoneCapture)
|
||||||
|
|
||||||
-- Clear existing drawings
|
-- Clear existing drawings
|
||||||
zoneCapture:UndrawZone()
|
zoneCapture:UndrawZone()
|
||||||
|
|
||||||
-- Redraw with correct color
|
-- Redraw with correct color
|
||||||
zoneCapture:DrawZone(-1, zoneColor, 0.5, zoneColor, 0.2, 2, true)
|
zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true)
|
||||||
|
|
||||||
-- Log the color assignment for debugging
|
-- Log the color assignment for debugging
|
||||||
local colorName = "UNKNOWN"
|
local colorName = "UNKNOWN"
|
||||||
@ -969,8 +1044,8 @@ local function RefreshAllZoneColors()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Notify BOTH coalitions
|
-- Notify BOTH coalitions
|
||||||
US_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, 5)
|
RU_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Manual zone status commands for players (F10 radio menu) - BOTH COALITIONS
|
-- 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()
|
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Check Victory Progress", USMenu, function()
|
||||||
local status = GetZoneOwnershipStatus()
|
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(
|
US_CC:MessageTypeToCoalition(
|
||||||
string.format(
|
string.format(
|
||||||
@ -998,7 +1074,7 @@ local function SetupZoneStatusCommands()
|
|||||||
progressPercent >= 50 and "GOOD PROGRESS!" or
|
progressPercent >= 50 and "GOOD PROGRESS!" or
|
||||||
"KEEP FIGHTING!"
|
"KEEP FIGHTING!"
|
||||||
),
|
),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
end )
|
end )
|
||||||
|
|
||||||
@ -1013,7 +1089,8 @@ local function SetupZoneStatusCommands()
|
|||||||
|
|
||||||
MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function()
|
MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function()
|
||||||
local status = GetZoneOwnershipStatus()
|
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(
|
RU_CC:MessageTypeToCoalition(
|
||||||
string.format(
|
string.format(
|
||||||
@ -1029,7 +1106,7 @@ local function SetupZoneStatusCommands()
|
|||||||
progressPercent >= 50 and "GOOD PROGRESS!" or
|
progressPercent >= 50 and "GOOD PROGRESS!" or
|
||||||
"KEEP FIGHTING!"
|
"KEEP FIGHTING!"
|
||||||
),
|
),
|
||||||
MESSAGE.Type.Information, 10
|
MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION
|
||||||
)
|
)
|
||||||
end )
|
end )
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
DESCRIPTION:
|
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:
|
CONFIGURATION:
|
||||||
- Update static templates and airfield lists as needed for your mission.
|
- 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
|
GLOBAL STATE AND CONFIGURATION
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
@ -264,9 +272,9 @@ local function cleanupCargoMissions()
|
|||||||
for _, coalitionKey in ipairs({"red", "blue"}) do
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
for i = #cargoMissions[coalitionKey], 1, -1 do
|
for i = #cargoMissions[coalitionKey], 1, -1 do
|
||||||
local m = cargoMissions[coalitionKey][i]
|
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
|
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)
|
table.remove(cargoMissions[coalitionKey], i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -444,12 +452,21 @@ local function dispatchCargo(squadron, coalitionKey)
|
|||||||
rat:SetDeparture(origin)
|
rat:SetDeparture(origin)
|
||||||
rat:SetDestination(destination)
|
rat:SetDestination(destination)
|
||||||
rat:NoRespawn()
|
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:InitLateActivated(false)
|
||||||
rat:SetSpawnLimit(1)
|
rat:SetSpawnLimit(1)
|
||||||
rat:SetSpawnDelay(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.
|
-- 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)
|
-- 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.
|
-- that detect parked/stopped cargo aircraft can register the delivery.
|
||||||
|
|||||||
@ -140,15 +140,15 @@ local TADC_SETTINGS = {
|
|||||||
-- Timing settings (applies to both coalitions)
|
-- Timing settings (applies to both coalitions)
|
||||||
checkInterval = 30, -- How often to scan for threats (seconds)
|
checkInterval = 30, -- How often to scan for threats (seconds)
|
||||||
monitorInterval = 30, -- How often to check interceptor status (seconds)
|
monitorInterval = 30, -- How often to check interceptor status (seconds)
|
||||||
statusReportInterval = 120, -- How often to report airbase status (seconds)
|
statusReportInterval = 1805, -- How often to report airbase status (seconds)
|
||||||
squadronSummaryInterval = 600, -- How often to broadcast squadron summary (seconds)
|
squadronSummaryInterval = 1800, -- How often to broadcast squadron summary (seconds)
|
||||||
cargoCheckInterval = 15, -- How often to check for cargo deliveries (seconds)
|
cargoCheckInterval = 15, -- How often to check for cargo deliveries (seconds)
|
||||||
|
|
||||||
-- RED Coalition Settings
|
-- RED Coalition Settings
|
||||||
red = {
|
red = {
|
||||||
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
|
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
|
||||||
squadronCooldown = 600, -- RED cooldown after squadron launch (seconds)
|
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
|
cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
|
||||||
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
|
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
|
||||||
rtbFlightBuffer = 300, -- RED extra landing time before cleanup (seconds)
|
rtbFlightBuffer = 300, -- RED extra landing time before cleanup (seconds)
|
||||||
@ -158,7 +158,7 @@ local TADC_SETTINGS = {
|
|||||||
blue = {
|
blue = {
|
||||||
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
|
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
|
||||||
squadronCooldown = 600, -- BLUE cooldown after squadron launch (seconds)
|
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
|
cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
|
||||||
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
|
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
|
||||||
rtbFlightBuffer = 300, -- BLUE extra landing time before cleanup (seconds)
|
rtbFlightBuffer = 300, -- BLUE extra landing time before cleanup (seconds)
|
||||||
@ -276,6 +276,105 @@ local airbaseHealthStatus = {
|
|||||||
blue = {}
|
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
|
-- Logging function
|
||||||
local function log(message, detailed)
|
local function log(message, detailed)
|
||||||
if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then
|
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)
|
MESSAGE:New(msg, 10):ToCoalition(coalitionSide)
|
||||||
USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
|
USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
finalizeCargoMission(cargoGroup, squadron, coalitionKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Event handler for cargo aircraft landing (backup for actual landings)
|
-- Event handler for cargo aircraft landing (backup for actual landings)
|
||||||
@ -1271,10 +1372,9 @@ local function monitorStuckAircraft()
|
|||||||
-- Mark airbase as having stuck aircraft
|
-- Mark airbase as having stuck aircraft
|
||||||
airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft"
|
airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft"
|
||||||
|
|
||||||
-- Remove the stuck aircraft
|
-- Remove the stuck aircraft and clear tracking
|
||||||
trackingData.group:Destroy()
|
pcall(function() trackingData.group:Destroy() end)
|
||||||
activeInterceptors[coalitionKey][aircraftName] = nil
|
cleanupInterceptorEntry(aircraftName, coalitionKey)
|
||||||
aircraftSpawnTracking[coalitionKey][aircraftName] = nil
|
|
||||||
|
|
||||||
-- Reassign squadron to alternative airbase
|
-- Reassign squadron to alternative airbase
|
||||||
reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey)
|
reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey)
|
||||||
@ -1342,9 +1442,14 @@ local function sendInterceptorHome(interceptor, coalitionSide)
|
|||||||
|
|
||||||
SCHEDULER:New(nil, function()
|
SCHEDULER:New(nil, function()
|
||||||
local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
|
local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
|
||||||
if activeInterceptors[coalitionKey][interceptor:GetName()] then
|
local name = nil
|
||||||
activeInterceptors[coalitionKey][interceptor:GetName()] = nil
|
if interceptor and interceptor.GetName then
|
||||||
log("Cleaned up " .. coalitionName .. " " .. interceptor:GetName() .. " after RTB", true)
|
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
|
||||||
end, {}, flightTime)
|
end, {}, flightTime)
|
||||||
else
|
else
|
||||||
@ -1616,6 +1721,7 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
|||||||
log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName)
|
log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
spawn:InitCleanUp(900)
|
||||||
|
|
||||||
local interceptors = {}
|
local interceptors = {}
|
||||||
|
|
||||||
@ -1674,11 +1780,14 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
|||||||
|
|
||||||
-- Emergency cleanup (safety net)
|
-- Emergency cleanup (safety net)
|
||||||
SCHEDULER:New(nil, function()
|
SCHEDULER:New(nil, function()
|
||||||
if activeInterceptors[coalitionKey][interceptor:GetName()] then
|
local name = nil
|
||||||
log("Emergency cleanup of " .. coalitionName .. " " .. interceptor:GetName() .. " (should have RTB'd)")
|
if interceptor and interceptor.GetName then
|
||||||
activeInterceptors[coalitionKey][interceptor:GetName()] = nil
|
local ok, value = pcall(function() return interceptor:GetName() end)
|
||||||
-- Also clean up spawn tracking
|
if ok then name = value end
|
||||||
aircraftSpawnTracking[coalitionKey][interceptor:GetName()] = nil
|
end
|
||||||
|
if name and activeInterceptors[coalitionKey][name] then
|
||||||
|
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
|
||||||
|
destroyInterceptorGroup(interceptor, coalitionKey, 0)
|
||||||
end
|
end
|
||||||
end, {}, coalitionSettings.emergencyCleanupTime)
|
end, {}, coalitionSettings.emergencyCleanupTime)
|
||||||
end
|
end
|
||||||
@ -2182,20 +2291,29 @@ end
|
|||||||
initializeSystem()
|
initializeSystem()
|
||||||
|
|
||||||
-- Add F10 menu command for squadron summary
|
-- 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)
|
local summary = getSquadronResourceSummary(coalition.side.RED)
|
||||||
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
|
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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)
|
local summary = getSquadronResourceSummary(coalition.side.BLUE)
|
||||||
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
|
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- 1. Show Airbase Status Report
|
-- 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"
|
local report = "=== RED Airbase Status ===\n"
|
||||||
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
||||||
local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED)
|
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)
|
MESSAGE:New(report, 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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"
|
local report = "=== BLUE Airbase Status ===\n"
|
||||||
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
||||||
local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE)
|
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)
|
end)
|
||||||
|
|
||||||
-- 2. Show Active Interceptors
|
-- 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:"}
|
local lines = {"Active RED Interceptors:"}
|
||||||
for name, data in pairs(activeInterceptors.red) do
|
for name, data in pairs(activeInterceptors.red) do
|
||||||
if data and data.group and data.group:IsAlive() then
|
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)
|
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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:"}
|
local lines = {"Active BLUE Interceptors:"}
|
||||||
for name, data in pairs(activeInterceptors.blue) do
|
for name, data in pairs(activeInterceptors.blue) do
|
||||||
if data and data.group and data.group:IsAlive() then
|
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)
|
end)
|
||||||
|
|
||||||
-- 3. Show Threat Summary
|
-- 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:"}
|
local lines = {"Detected BLUE Threats:"}
|
||||||
if cachedSets.blueAircraft then
|
if cachedSets.blueAircraft then
|
||||||
cachedSets.blueAircraft:ForEach(function(group)
|
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)
|
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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:"}
|
local lines = {"Detected RED Threats:"}
|
||||||
if cachedSets.redAircraft then
|
if cachedSets.redAircraft then
|
||||||
cachedSets.redAircraft:ForEach(function(group)
|
cachedSets.redAircraft:ForEach(function(group)
|
||||||
@ -2276,18 +2394,18 @@ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRoot,
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
-- 4. Request Immediate Squadron Summary Broadcast
|
-- 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)
|
local summary = getSquadronResourceSummary(coalition.side.RED)
|
||||||
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
|
MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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)
|
local summary = getSquadronResourceSummary(coalition.side.BLUE)
|
||||||
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
|
MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- 5. Show Cargo Delivery Log
|
-- 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:"}
|
local lines = {"Recent RED Cargo Deliveries:"}
|
||||||
if _G.processedDeliveries then
|
if _G.processedDeliveries then
|
||||||
for key, timestamp in pairs(_G.processedDeliveries) do
|
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)
|
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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:"}
|
local lines = {"Recent BLUE Cargo Deliveries:"}
|
||||||
if _G.processedDeliveries then
|
if _G.processedDeliveries then
|
||||||
for key, timestamp in pairs(_G.processedDeliveries) do
|
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)
|
end)
|
||||||
|
|
||||||
-- 6. Show Zone Coverage Map
|
-- 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:"}
|
local lines = {"RED Zone Coverage:"}
|
||||||
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
for _, squadron in pairs(RED_SQUADRON_CONFIG) do
|
||||||
local zones = {}
|
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)
|
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
|
||||||
end)
|
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:"}
|
local lines = {"BLUE Zone Coverage:"}
|
||||||
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
|
||||||
local zones = {}
|
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)
|
MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- 7. Request Emergency Cleanup (admin/global)
|
-- 7. Admin/Debug Commands - Create submenus under each coalition's TADC Utilities
|
||||||
MENU_MISSION_COMMAND:New("Emergency Cleanup Interceptors", menuRoot, function()
|
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
|
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
|
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
|
||||||
interceptors.group = nil
|
cleanupInterceptorEntry(name, "red")
|
||||||
cleaned = cleaned + 1
|
cleaned = cleaned + 1
|
||||||
end
|
end
|
||||||
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
|
if interceptors and interceptors.group and not interceptors.group:IsAlive() then
|
||||||
interceptors.group = nil
|
cleanupInterceptorEntry(name, "blue")
|
||||||
cleaned = cleaned + 1
|
cleaned = cleaned + 1
|
||||||
end
|
end
|
||||||
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)
|
end)
|
||||||
|
|
||||||
-- 9. Show System Uptime/Status
|
-- 9. Show System Uptime/Status
|
||||||
local systemStartTime = timer.getTime()
|
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 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)
|
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)
|
end)
|
||||||
|
|
||||||
-- 10. Check for Stuck Aircraft (manual trigger)
|
-- 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()
|
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)
|
end)
|
||||||
|
|
||||||
-- 11. Show Airbase Health Status
|
-- 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:"}
|
local lines = {"Airbase Health Status:"}
|
||||||
for _, coalitionKey in ipairs({"red", "blue"}) do
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
|
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)
|
table.insert(lines, " " .. airbaseName .. ": " .. status)
|
||||||
end
|
end
|
||||||
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)
|
end)
|
||||||
|
|
||||||
-- Initialize airbase health status for all configured airbases
|
-- Initialize airbase health status for all configured airbases
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user