mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Bug Fixes to TADC Main and TADC Cargo Systems
This commit is contained in:
parent
a3da2a6b8b
commit
ed662a6289
Binary file not shown.
Binary file not shown.
@ -133,6 +133,20 @@ log("[DEBUG] The Lakes zone initialization complete")
|
||||
|
||||
|
||||
-- Helper functions for tactical information
|
||||
|
||||
-- Global cached unit set - created once and maintained automatically by MOOSE
|
||||
local CachedUnitSet = nil
|
||||
|
||||
-- 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
|
||||
log("[PERFORMANCE] Initialized cached unit set for zone scanning")
|
||||
end
|
||||
end
|
||||
|
||||
local function GetZoneForceStrengths(ZoneCapture)
|
||||
local zone = ZoneCapture:GetZone()
|
||||
if not zone then return {red = 0, blue = 0, neutral = 0} end
|
||||
@ -141,15 +155,15 @@ local function GetZoneForceStrengths(ZoneCapture)
|
||||
local blueCount = 0
|
||||
local neutralCount = 0
|
||||
|
||||
-- Simple approach: scan all units and check if they're in the zone
|
||||
local coord = zone:GetCoordinate()
|
||||
local radius = zone:GetRadius() or 1000
|
||||
-- Use MOOSE's optimized zone scanning instead of manual distance checks
|
||||
local success, scannedUnits = pcall(function()
|
||||
return zone:GetScannedUnits()
|
||||
end)
|
||||
|
||||
local allUnits = SET_UNIT:New():FilterStart()
|
||||
allUnits:ForEachUnit(function(unit)
|
||||
if unit and unit:IsAlive() then
|
||||
local unitCoord = unit:GetCoordinate()
|
||||
if unitCoord and coord:Get2DDistance(unitCoord) <= radius then
|
||||
if success and scannedUnits then
|
||||
-- Use MOOSE's built-in scanned units (much faster)
|
||||
for _, unit in pairs(scannedUnits) do
|
||||
if unit and unit:IsAlive() then
|
||||
local unitCoalition = unit:GetCoalition()
|
||||
if unitCoalition == coalition.side.RED then
|
||||
redCount = redCount + 1
|
||||
@ -160,7 +174,32 @@ local function GetZoneForceStrengths(ZoneCapture)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
-- Fallback: Use zone's built-in scanning with the cached set
|
||||
InitializeCachedUnitSet()
|
||||
|
||||
-- Use zone radius to limit search area
|
||||
local coord = zone:GetCoordinate()
|
||||
local radius = zone:GetRadius() or 1000
|
||||
|
||||
-- Only scan units within a reasonable distance of the zone
|
||||
local nearbyUnits = coord:ScanUnits(radius)
|
||||
if nearbyUnits then
|
||||
for _, unitData in pairs(nearbyUnits) do
|
||||
local unit = unitData -- ScanUnits returns unit objects
|
||||
if unit and type(unit.IsAlive) == "function" and unit:IsAlive() then
|
||||
local unitCoalition = unit:GetCoalition()
|
||||
if unitCoalition == coalition.side.RED then
|
||||
redCount = redCount + 1
|
||||
elseif unitCoalition == coalition.side.BLUE then
|
||||
blueCount = blueCount + 1
|
||||
elseif unitCoalition == coalition.side.NEUTRAL then
|
||||
neutralCount = neutralCount + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
log(string.format("[TACTICAL] Zone %s scan result: R:%d B:%d N:%d",
|
||||
ZoneCapture:GetZoneName(), redCount, blueCount, neutralCount))
|
||||
@ -179,62 +218,57 @@ local function GetRedUnitMGRSCoords(ZoneCapture)
|
||||
local coords = {}
|
||||
local units = nil
|
||||
|
||||
-- Try multiple methods to get units (same as GetZoneForceStrengths)
|
||||
-- Optimized: Try MOOSE's built-in zone scanning first (fastest method)
|
||||
local success1 = pcall(function()
|
||||
units = zone:GetScannedUnits()
|
||||
end)
|
||||
|
||||
-- Fallback: Use coordinate-based scanning (much faster than SET_UNIT filtering)
|
||||
if not success1 or not units then
|
||||
local coord = zone:GetCoordinate()
|
||||
local radius = zone:GetRadius() or 1000
|
||||
|
||||
local success2 = pcall(function()
|
||||
local unitSet = SET_UNIT:New():FilterZones({zone}):FilterStart()
|
||||
units = {}
|
||||
unitSet:ForEachUnit(function(unit)
|
||||
if unit then
|
||||
units[unit:GetName()] = unit
|
||||
end
|
||||
end)
|
||||
units = coord:ScanUnits(radius)
|
||||
end)
|
||||
|
||||
-- Last resort: Manual zone check with cached unit set
|
||||
if not success2 or not units then
|
||||
InitializeCachedUnitSet()
|
||||
units = {}
|
||||
if CachedUnitSet then
|
||||
CachedUnitSet:ForEachUnit(function(unit)
|
||||
if unit and unit:IsAlive() and unit:IsInZone(zone) then
|
||||
units[unit:GetName()] = unit
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not units then
|
||||
local success3 = pcall(function()
|
||||
units = {}
|
||||
local unitSet = SET_UNIT:New():FilterZones({zone}):FilterStart()
|
||||
unitSet:ForEachUnit(function(unit)
|
||||
if unit and unit:IsInZone(zone) then
|
||||
units[unit:GetName()] = unit
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Extract RED unit coordinates
|
||||
-- Extract RED unit coordinates with optimized error handling
|
||||
if units then
|
||||
for unitName, unit in pairs(units) do
|
||||
-- Enhanced nil checking and safe method calls
|
||||
if unit and type(unit) == "table" and unit.IsAlive then
|
||||
local isAlive = false
|
||||
local success_alive = pcall(function()
|
||||
isAlive = unit:IsAlive()
|
||||
end)
|
||||
-- Streamlined nil checking
|
||||
if unit and type(unit) == "table" then
|
||||
local success, isAlive = pcall(function() return unit:IsAlive() end)
|
||||
|
||||
if success_alive and isAlive then
|
||||
local coalition_side = nil
|
||||
local success_coalition = pcall(function()
|
||||
coalition_side = unit:GetCoalition()
|
||||
end)
|
||||
if success and isAlive then
|
||||
local success_coalition, coalition_side = pcall(function() return unit:GetCoalition() end)
|
||||
|
||||
if success_coalition and coalition_side == coalition.side.RED then
|
||||
local coord = unit:GetCoordinate()
|
||||
if coord then
|
||||
local success, mgrs = pcall(function()
|
||||
local success_coord, coord = pcall(function() return unit:GetCoordinate() end)
|
||||
|
||||
if success_coord and coord then
|
||||
local success_mgrs, mgrs = pcall(function()
|
||||
return coord:ToStringMGRS(5) -- 5-digit precision
|
||||
end)
|
||||
|
||||
if success and mgrs then
|
||||
if success_mgrs and mgrs then
|
||||
local success_type, unitType = pcall(function() return unit:GetTypeName() end)
|
||||
table.insert(coords, {
|
||||
name = unit:GetName(),
|
||||
type = unit:GetTypeName(),
|
||||
type = success_type and unitType or "Unknown",
|
||||
mgrs = mgrs
|
||||
})
|
||||
end
|
||||
@ -673,21 +707,37 @@ end, {}, 10, 300 ) -- Start after 10 seconds, repeat every 300 seconds (5 minute
|
||||
local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
||||
log("[ZONE COLORS] Running periodic zone color verification...")
|
||||
|
||||
-- Verify each zone's visual marker matches its coalition
|
||||
-- Verify each zone's visual marker matches its CURRENT STATE (not just coalition)
|
||||
for i, zoneCapture in ipairs(zoneCaptureObjects) do
|
||||
if zoneCapture then
|
||||
local zoneCoalition = zoneCapture:GetCoalition()
|
||||
local zoneName = zoneNames[i] or ("Zone " .. i)
|
||||
local currentState = zoneCapture:GetCurrentState()
|
||||
|
||||
-- Force redraw the zone with correct color (helps prevent desync issues)
|
||||
-- Force redraw the zone with correct color based on CURRENT STATE
|
||||
zoneCapture:UndrawZone()
|
||||
|
||||
if zoneCoalition == coalition.side.BLUE then
|
||||
zoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue
|
||||
-- Color priority: State (Attacked/Empty) overrides coalition ownership
|
||||
if currentState == "Attacked" then
|
||||
-- Orange for contested zones (highest priority)
|
||||
zoneCapture:DrawZone(-1, {1, 0.5, 0}, 0.5, {1, 0.5, 0}, 0.2, 2, true)
|
||||
log(string.format("[ZONE COLORS] %s: Set to ORANGE (Attacked)", zoneName))
|
||||
elseif currentState == "Empty" then
|
||||
-- Green for neutral/empty zones
|
||||
zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true)
|
||||
log(string.format("[ZONE COLORS] %s: Set to GREEN (Empty)", zoneName))
|
||||
elseif zoneCoalition == coalition.side.BLUE then
|
||||
-- Blue for BLUE-owned zones (Guarded or Captured state)
|
||||
zoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true)
|
||||
log(string.format("[ZONE COLORS] %s: Set to BLUE (Owned)", zoneName))
|
||||
elseif zoneCoalition == coalition.side.RED then
|
||||
zoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red
|
||||
-- Red for RED-owned zones (Guarded or Captured state)
|
||||
zoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true)
|
||||
log(string.format("[ZONE COLORS] %s: Set to RED (Owned)", zoneName))
|
||||
else
|
||||
zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green (neutral)
|
||||
-- Fallback to green for any other state
|
||||
zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true)
|
||||
log(string.format("[ZONE COLORS] %s: Set to GREEN (Fallback)", zoneName))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -713,22 +763,29 @@ local function RefreshAllZoneColors()
|
||||
|
||||
for i, zoneCapture in ipairs(zoneCaptureObjects) do
|
||||
if zoneCapture then
|
||||
local coalition = zoneCapture:GetCoalition()
|
||||
local zoneCoalition = zoneCapture:GetCoalition()
|
||||
local zoneName = zoneNames[i] or ("Zone " .. i)
|
||||
local currentState = zoneCapture:GetCurrentState()
|
||||
|
||||
-- Clear existing drawings
|
||||
zoneCapture:UndrawZone()
|
||||
|
||||
-- Redraw with correct color based on current coalition
|
||||
if coalition == coalition.side.BLUE then
|
||||
-- Redraw with correct color based on CURRENT STATE (priority over coalition)
|
||||
if currentState == "Attacked" then
|
||||
zoneCapture:DrawZone(-1, {1, 0.5, 0}, 0.5, {1, 0.5, 0}, 0.2, 2, true) -- Orange
|
||||
log(string.format("[ZONE COLORS] %s: Set to ORANGE (Attacked)", zoneName))
|
||||
elseif currentState == "Empty" then
|
||||
zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green
|
||||
log(string.format("[ZONE COLORS] %s: Set to GREEN (Empty)", zoneName))
|
||||
elseif zoneCoalition == coalition.side.BLUE then
|
||||
zoneCapture:DrawZone(-1, {0, 0, 1}, 0.5, {0, 0, 1}, 0.2, 2, true) -- Blue
|
||||
log(string.format("[ZONE COLORS] %s: Set to BLUE", zoneName))
|
||||
elseif coalition == coalition.side.RED then
|
||||
log(string.format("[ZONE COLORS] %s: Set to BLUE (Owned)", zoneName))
|
||||
elseif zoneCoalition == coalition.side.RED then
|
||||
zoneCapture:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true) -- Red
|
||||
log(string.format("[ZONE COLORS] %s: Set to RED", zoneName))
|
||||
log(string.format("[ZONE COLORS] %s: Set to RED (Owned)", zoneName))
|
||||
else
|
||||
zoneCapture:DrawZone(-1, {0, 1, 0}, 0.5, {0, 1, 0}, 0.2, 2, true) -- Green (neutral)
|
||||
log(string.format("[ZONE COLORS] %s: Set to NEUTRAL/GREEN", zoneName))
|
||||
log(string.format("[ZONE COLORS] %s: Set to NEUTRAL/GREEN (Fallback)", zoneName))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -773,6 +830,10 @@ end
|
||||
-- Initialize zone status monitoring
|
||||
SCHEDULER:New( nil, function()
|
||||
log("[VICTORY SYSTEM] Initializing zone monitoring system...")
|
||||
|
||||
-- Initialize performance optimization caches
|
||||
InitializeCachedUnitSet()
|
||||
|
||||
SetupZoneStatusCommands()
|
||||
|
||||
-- Initial status report
|
||||
|
||||
@ -212,13 +212,16 @@ end
|
||||
hasActiveCargoMission(coalitionKey, airbaseName)
|
||||
--------------------------------------------------------------------------
|
||||
Returns true if there is an active (not completed/failed) cargo mission for the given airbase.
|
||||
Failed missions are immediately removed from tracking to allow retries.
|
||||
]]
|
||||
local function hasActiveCargoMission(coalitionKey, airbaseName)
|
||||
for _, mission in pairs(cargoMissions[coalitionKey]) do
|
||||
for i = #cargoMissions[coalitionKey], 1, -1 do
|
||||
local mission = cargoMissions[coalitionKey][i]
|
||||
if mission.destination == airbaseName then
|
||||
-- Ignore completed or failed missions
|
||||
-- Remove completed or failed missions immediately to allow retries
|
||||
if mission.status == "completed" or mission.status == "failed" then
|
||||
-- not active
|
||||
log("Removing " .. mission.status .. " cargo mission for " .. airbaseName .. " from tracking")
|
||||
table.remove(cargoMissions[coalitionKey], i)
|
||||
else
|
||||
-- Consider mission active only if the group is alive OR we're still within the grace window
|
||||
local stillActive = false
|
||||
@ -283,12 +286,25 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
local origin
|
||||
local attempts = 0
|
||||
local maxAttempts = 10
|
||||
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||
|
||||
repeat
|
||||
origin = selectRandomAirfield(config.supplyAirfields)
|
||||
attempts = attempts + 1
|
||||
|
||||
-- Ensure origin is not the same as destination
|
||||
if origin == squadron.airbaseName then
|
||||
origin = nil
|
||||
else
|
||||
-- Validate that origin airbase exists and is controlled by correct coalition
|
||||
local originAirbase = AIRBASE:FindByName(origin)
|
||||
if not originAirbase then
|
||||
log("WARNING: Origin airbase '" .. tostring(origin) .. "' does not exist. Trying another...")
|
||||
origin = nil
|
||||
elseif originAirbase:GetCoalition() ~= coalitionSide then
|
||||
log("WARNING: Origin airbase '" .. tostring(origin) .. "' is not controlled by " .. coalitionKey .. " coalition. Trying another...")
|
||||
origin = nil
|
||||
end
|
||||
end
|
||||
until origin or attempts >= maxAttempts
|
||||
|
||||
@ -372,14 +388,45 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
local alias = cargoTemplate .. "_TO_" .. destination .. "_" .. tostring(math.random(1000,9999))
|
||||
log("DEBUG: Attempting RAT spawn for template: '" .. cargoTemplate .. "' alias: '" .. alias .. "'", true)
|
||||
|
||||
-- Check if destination airbase is still controlled by the correct coalition
|
||||
-- Validate destination airbase: RAT's "Airbase doesn't exist" error actually means
|
||||
-- "Airbase not found OR not owned by the correct coalition" because RAT filters by coalition internally.
|
||||
-- We perform the same validation here to fail fast with better error messages.
|
||||
local destAirbase = AIRBASE:FindByName(destination)
|
||||
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||
|
||||
if not destAirbase then
|
||||
log("ERROR: Destination airbase '" .. destination .. "' does not exist. Skipping dispatch.")
|
||||
log("ERROR: Destination airbase '" .. destination .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (airbase not found on map)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
if destAirbase:GetCoalition() ~= getCoalitionSide(coalitionKey) then
|
||||
log("ERROR: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition. Skipping dispatch.")
|
||||
|
||||
local destCoalition = destAirbase:GetCoalition()
|
||||
if destCoalition ~= coalitionSide then
|
||||
log("WARNING: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(destCoalition) .. "). This will cause RAT to fail with 'Airbase doesn't exist' error. Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (airbase captured by enemy)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
-- Validate origin airbase with same coalition filtering logic
|
||||
local originAirbase = AIRBASE:FindByName(origin)
|
||||
if not originAirbase then
|
||||
log("ERROR: Origin airbase '" .. origin .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (airbase not found on map)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
local originCoalition = originAirbase:GetCoalition()
|
||||
if originCoalition ~= coalitionSide then
|
||||
log("WARNING: Origin airbase '" .. origin .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(originCoalition) .. "). This will cause RAT to fail. Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (origin airbase captured by enemy)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
@ -390,6 +437,8 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
log("TRACEBACK: " .. tostring(debug.traceback(rat)), true)
|
||||
end
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn init error)!")
|
||||
-- Mark mission as failed and cleanup immediately - do NOT track failed RAT spawns
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
@ -478,6 +527,9 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
if debug and debug.traceback then
|
||||
log("TRACEBACK: " .. tostring(debug.traceback(errSpawn)), true)
|
||||
end
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn error)!")
|
||||
-- Mark mission as failed and cleanup immediately - do NOT track failed spawns
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -629,6 +681,112 @@ log("═════════════════════════
|
||||
-- End Moose_TDAC_CargoDispatcher.lua
|
||||
|
||||
|
||||
--[[
|
||||
DIAGNOSTIC CONSOLE HELPERS
|
||||
--------------------------------------------------------------------------
|
||||
Functions you can call from the DCS Lua console (F12) to debug issues.
|
||||
]]
|
||||
|
||||
-- Check airbase coalition ownership for all configured supply airbases
|
||||
-- Usage: _G.TDAC_CheckAirbaseOwnership()
|
||||
function _G.TDAC_CheckAirbaseOwnership()
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
env.info("[TDAC DIAGNOSTIC] Checking Coalition Ownership of All Supply Airbases")
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
|
||||
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
|
||||
local expectedCoalition = getCoalitionSide(coalitionKey)
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] %s COALITION (expected coalition ID: %s)", coalitionKey:upper(), tostring(expectedCoalition)))
|
||||
|
||||
if config and config.supplyAirfields then
|
||||
for _, airbaseName in ipairs(config.supplyAirfields) do
|
||||
local airbase = AIRBASE:FindByName(airbaseName)
|
||||
if not airbase then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] ✗ %-30s - NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||
else
|
||||
local actualCoalition = airbase:GetCoalition()
|
||||
local coalitionName = "UNKNOWN"
|
||||
local status = "✗"
|
||||
|
||||
if actualCoalition == coalition.side.NEUTRAL then
|
||||
coalitionName = "NEUTRAL"
|
||||
elseif actualCoalition == coalition.side.RED then
|
||||
coalitionName = "RED"
|
||||
elseif actualCoalition == coalition.side.BLUE then
|
||||
coalitionName = "BLUE"
|
||||
end
|
||||
|
||||
if actualCoalition == expectedCoalition then
|
||||
status = "✓"
|
||||
end
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] %s %-30s - %s (coalition ID: %s)", status, airbaseName, coalitionName, tostring(actualCoalition)))
|
||||
end
|
||||
end
|
||||
else
|
||||
env.info("[TDAC DIAGNOSTIC] ERROR: No supply airfields configured!")
|
||||
end
|
||||
env.info("[TDAC DIAGNOSTIC] ───────────────────────────────────────")
|
||||
end
|
||||
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
env.info("[TDAC DIAGNOSTIC] Check complete. ✓ = Owned by correct coalition, ✗ = Wrong coalition or not found")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check specific airbase coalition ownership
|
||||
-- Usage: _G.TDAC_CheckAirbase("Olenya")
|
||||
function _G.TDAC_CheckAirbase(airbaseName)
|
||||
if type(airbaseName) ~= 'string' then
|
||||
env.info("[TDAC DIAGNOSTIC] ERROR: airbaseName must be a string")
|
||||
return false
|
||||
end
|
||||
|
||||
local airbase = AIRBASE:FindByName(airbaseName)
|
||||
if not airbase then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||
return false, "not_found"
|
||||
end
|
||||
|
||||
local actualCoalition = airbase:GetCoalition()
|
||||
local coalitionName = "UNKNOWN"
|
||||
|
||||
if actualCoalition == coalition.side.NEUTRAL then
|
||||
coalitionName = "NEUTRAL"
|
||||
elseif actualCoalition == coalition.side.RED then
|
||||
coalitionName = "RED"
|
||||
elseif actualCoalition == coalition.side.BLUE then
|
||||
coalitionName = "BLUE"
|
||||
end
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' - Coalition: %s (ID: %s)", airbaseName, coalitionName, tostring(actualCoalition)))
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] IsAlive: %s", tostring(airbase:IsAlive())))
|
||||
|
||||
-- Check parking spots
|
||||
local function spotsFor(term, termName)
|
||||
local ok, n = pcall(function() return airbase:GetParkingSpotsNumber(term) end)
|
||||
if ok and n then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Parking %-15s: %d spots", termName, n))
|
||||
end
|
||||
end
|
||||
|
||||
spotsFor(AIRBASE.TerminalType.OpenBig, "OpenBig")
|
||||
spotsFor(AIRBASE.TerminalType.OpenMed, "OpenMed")
|
||||
spotsFor(AIRBASE.TerminalType.OpenMedOrBig, "OpenMedOrBig")
|
||||
spotsFor(AIRBASE.TerminalType.Runway, "Runway")
|
||||
|
||||
return true, coalitionName, actualCoalition
|
||||
end
|
||||
|
||||
env.info("[TDAC DIAGNOSTIC] Console helpers loaded:")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbaseOwnership() - Check all supply airbases")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbase('Olenya') - Check specific airbase")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_RunConfigCheck() - Validate dispatcher config")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_LogAirbaseParking('Olenya') - Check parking availability")
|
||||
|
||||
|
||||
-- Diagnostic helper: call from DCS console to test spawn-by-name and routing.
|
||||
-- Example (paste into DCS Lua console):
|
||||
-- _G.TDAC_CargoDispatcher_TestSpawn("CARGO_BLUE_C130_TEMPLATE", "Kittila", "Luostari Pechenga")
|
||||
|
||||
@ -147,7 +147,7 @@ local TADC_SETTINGS = {
|
||||
-- RED Coalition Settings
|
||||
red = {
|
||||
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
|
||||
squadronCooldown = 300, -- RED cooldown after squadron launch (seconds)
|
||||
squadronCooldown = 600, -- RED cooldown after squadron launch (seconds)
|
||||
interceptRatio = 0.8, -- RED interceptors per threat aircraft
|
||||
cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
|
||||
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
|
||||
@ -157,7 +157,7 @@ local TADC_SETTINGS = {
|
||||
-- BLUE Coalition Settings
|
||||
blue = {
|
||||
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
|
||||
squadronCooldown = 300, -- BLUE cooldown after squadron launch (seconds)
|
||||
squadronCooldown = 600, -- BLUE cooldown after squadron launch (seconds)
|
||||
interceptRatio = 0.8, -- BLUE interceptors per threat aircraft
|
||||
cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
|
||||
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
|
||||
|
||||
@ -212,13 +212,16 @@ end
|
||||
hasActiveCargoMission(coalitionKey, airbaseName)
|
||||
--------------------------------------------------------------------------
|
||||
Returns true if there is an active (not completed/failed) cargo mission for the given airbase.
|
||||
Failed missions are immediately removed from tracking to allow retries.
|
||||
]]
|
||||
local function hasActiveCargoMission(coalitionKey, airbaseName)
|
||||
for _, mission in pairs(cargoMissions[coalitionKey]) do
|
||||
for i = #cargoMissions[coalitionKey], 1, -1 do
|
||||
local mission = cargoMissions[coalitionKey][i]
|
||||
if mission.destination == airbaseName then
|
||||
-- Ignore completed or failed missions
|
||||
-- Remove completed or failed missions immediately to allow retries
|
||||
if mission.status == "completed" or mission.status == "failed" then
|
||||
-- not active
|
||||
log("Removing " .. mission.status .. " cargo mission for " .. airbaseName .. " from tracking")
|
||||
table.remove(cargoMissions[coalitionKey], i)
|
||||
else
|
||||
-- Consider mission active only if the group is alive OR we're still within the grace window
|
||||
local stillActive = false
|
||||
@ -283,12 +286,25 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
local origin
|
||||
local attempts = 0
|
||||
local maxAttempts = 10
|
||||
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||
|
||||
repeat
|
||||
origin = selectRandomAirfield(config.supplyAirfields)
|
||||
attempts = attempts + 1
|
||||
|
||||
-- Ensure origin is not the same as destination
|
||||
if origin == squadron.airbaseName then
|
||||
origin = nil
|
||||
else
|
||||
-- Validate that origin airbase exists and is controlled by correct coalition
|
||||
local originAirbase = AIRBASE:FindByName(origin)
|
||||
if not originAirbase then
|
||||
log("WARNING: Origin airbase '" .. tostring(origin) .. "' does not exist. Trying another...")
|
||||
origin = nil
|
||||
elseif originAirbase:GetCoalition() ~= coalitionSide then
|
||||
log("WARNING: Origin airbase '" .. tostring(origin) .. "' is not controlled by " .. coalitionKey .. " coalition. Trying another...")
|
||||
origin = nil
|
||||
end
|
||||
end
|
||||
until origin or attempts >= maxAttempts
|
||||
|
||||
@ -372,14 +388,45 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
local alias = cargoTemplate .. "_TO_" .. destination .. "_" .. tostring(math.random(1000,9999))
|
||||
log("DEBUG: Attempting RAT spawn for template: '" .. cargoTemplate .. "' alias: '" .. alias .. "'", true)
|
||||
|
||||
-- Check if destination airbase is still controlled by the correct coalition
|
||||
-- Validate destination airbase: RAT's "Airbase doesn't exist" error actually means
|
||||
-- "Airbase not found OR not owned by the correct coalition" because RAT filters by coalition internally.
|
||||
-- We perform the same validation here to fail fast with better error messages.
|
||||
local destAirbase = AIRBASE:FindByName(destination)
|
||||
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||
|
||||
if not destAirbase then
|
||||
log("ERROR: Destination airbase '" .. destination .. "' does not exist. Skipping dispatch.")
|
||||
log("ERROR: Destination airbase '" .. destination .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (airbase not found on map)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
if destAirbase:GetCoalition() ~= getCoalitionSide(coalitionKey) then
|
||||
log("ERROR: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition. Skipping dispatch.")
|
||||
|
||||
local destCoalition = destAirbase:GetCoalition()
|
||||
if destCoalition ~= coalitionSide then
|
||||
log("WARNING: Destination airbase '" .. destination .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(destCoalition) .. "). This will cause RAT to fail with 'Airbase doesn't exist' error. Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (airbase captured by enemy)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
-- Validate origin airbase with same coalition filtering logic
|
||||
local originAirbase = AIRBASE:FindByName(origin)
|
||||
if not originAirbase then
|
||||
log("ERROR: Origin airbase '" .. origin .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (airbase not found on map)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
local originCoalition = originAirbase:GetCoalition()
|
||||
if originCoalition ~= coalitionSide then
|
||||
log("WARNING: Origin airbase '" .. origin .. "' is not controlled by " .. coalitionKey .. " coalition (currently coalition " .. tostring(originCoalition) .. "). This will cause RAT to fail. Skipping dispatch.")
|
||||
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (origin airbase captured by enemy)!")
|
||||
-- Mark mission as failed and cleanup immediately
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
@ -390,6 +437,8 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
log("TRACEBACK: " .. tostring(debug.traceback(rat)), true)
|
||||
end
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn init error)!")
|
||||
-- Mark mission as failed and cleanup immediately - do NOT track failed RAT spawns
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
|
||||
@ -478,6 +527,9 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
if debug and debug.traceback then
|
||||
log("TRACEBACK: " .. tostring(debug.traceback(errSpawn)), true)
|
||||
end
|
||||
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn error)!")
|
||||
-- Mark mission as failed and cleanup immediately - do NOT track failed spawns
|
||||
mission.status = "failed"
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -629,6 +681,112 @@ log("═════════════════════════
|
||||
-- End Moose_TDAC_CargoDispatcher.lua
|
||||
|
||||
|
||||
--[[
|
||||
DIAGNOSTIC CONSOLE HELPERS
|
||||
--------------------------------------------------------------------------
|
||||
Functions you can call from the DCS Lua console (F12) to debug issues.
|
||||
]]
|
||||
|
||||
-- Check airbase coalition ownership for all configured supply airbases
|
||||
-- Usage: _G.TDAC_CheckAirbaseOwnership()
|
||||
function _G.TDAC_CheckAirbaseOwnership()
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
env.info("[TDAC DIAGNOSTIC] Checking Coalition Ownership of All Supply Airbases")
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
|
||||
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
|
||||
local expectedCoalition = getCoalitionSide(coalitionKey)
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] %s COALITION (expected coalition ID: %s)", coalitionKey:upper(), tostring(expectedCoalition)))
|
||||
|
||||
if config and config.supplyAirfields then
|
||||
for _, airbaseName in ipairs(config.supplyAirfields) do
|
||||
local airbase = AIRBASE:FindByName(airbaseName)
|
||||
if not airbase then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] ✗ %-30s - NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||
else
|
||||
local actualCoalition = airbase:GetCoalition()
|
||||
local coalitionName = "UNKNOWN"
|
||||
local status = "✗"
|
||||
|
||||
if actualCoalition == coalition.side.NEUTRAL then
|
||||
coalitionName = "NEUTRAL"
|
||||
elseif actualCoalition == coalition.side.RED then
|
||||
coalitionName = "RED"
|
||||
elseif actualCoalition == coalition.side.BLUE then
|
||||
coalitionName = "BLUE"
|
||||
end
|
||||
|
||||
if actualCoalition == expectedCoalition then
|
||||
status = "✓"
|
||||
end
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] %s %-30s - %s (coalition ID: %s)", status, airbaseName, coalitionName, tostring(actualCoalition)))
|
||||
end
|
||||
end
|
||||
else
|
||||
env.info("[TDAC DIAGNOSTIC] ERROR: No supply airfields configured!")
|
||||
end
|
||||
env.info("[TDAC DIAGNOSTIC] ───────────────────────────────────────")
|
||||
end
|
||||
|
||||
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||
env.info("[TDAC DIAGNOSTIC] Check complete. ✓ = Owned by correct coalition, ✗ = Wrong coalition or not found")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check specific airbase coalition ownership
|
||||
-- Usage: _G.TDAC_CheckAirbase("Olenya")
|
||||
function _G.TDAC_CheckAirbase(airbaseName)
|
||||
if type(airbaseName) ~= 'string' then
|
||||
env.info("[TDAC DIAGNOSTIC] ERROR: airbaseName must be a string")
|
||||
return false
|
||||
end
|
||||
|
||||
local airbase = AIRBASE:FindByName(airbaseName)
|
||||
if not airbase then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||
return false, "not_found"
|
||||
end
|
||||
|
||||
local actualCoalition = airbase:GetCoalition()
|
||||
local coalitionName = "UNKNOWN"
|
||||
|
||||
if actualCoalition == coalition.side.NEUTRAL then
|
||||
coalitionName = "NEUTRAL"
|
||||
elseif actualCoalition == coalition.side.RED then
|
||||
coalitionName = "RED"
|
||||
elseif actualCoalition == coalition.side.BLUE then
|
||||
coalitionName = "BLUE"
|
||||
end
|
||||
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' - Coalition: %s (ID: %s)", airbaseName, coalitionName, tostring(actualCoalition)))
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] IsAlive: %s", tostring(airbase:IsAlive())))
|
||||
|
||||
-- Check parking spots
|
||||
local function spotsFor(term, termName)
|
||||
local ok, n = pcall(function() return airbase:GetParkingSpotsNumber(term) end)
|
||||
if ok and n then
|
||||
env.info(string.format("[TDAC DIAGNOSTIC] Parking %-15s: %d spots", termName, n))
|
||||
end
|
||||
end
|
||||
|
||||
spotsFor(AIRBASE.TerminalType.OpenBig, "OpenBig")
|
||||
spotsFor(AIRBASE.TerminalType.OpenMed, "OpenMed")
|
||||
spotsFor(AIRBASE.TerminalType.OpenMedOrBig, "OpenMedOrBig")
|
||||
spotsFor(AIRBASE.TerminalType.Runway, "Runway")
|
||||
|
||||
return true, coalitionName, actualCoalition
|
||||
end
|
||||
|
||||
env.info("[TDAC DIAGNOSTIC] Console helpers loaded:")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbaseOwnership() - Check all supply airbases")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbase('Olenya') - Check specific airbase")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_RunConfigCheck() - Validate dispatcher config")
|
||||
env.info("[TDAC DIAGNOSTIC] _G.TDAC_LogAirbaseParking('Olenya') - Check parking availability")
|
||||
|
||||
|
||||
-- Diagnostic helper: call from DCS console to test spawn-by-name and routing.
|
||||
-- Example (paste into DCS Lua console):
|
||||
-- _G.TDAC_CargoDispatcher_TestSpawn("CARGO_BLUE_C130_TEMPLATE", "Kittila", "Luostari Pechenga")
|
||||
|
||||
@ -147,7 +147,7 @@ local TADC_SETTINGS = {
|
||||
-- RED Coalition Settings
|
||||
red = {
|
||||
maxActiveCAP = 24, -- Maximum RED fighters airborne at once
|
||||
squadronCooldown = 300, -- RED cooldown after squadron launch (seconds)
|
||||
squadronCooldown = 600, -- RED cooldown after squadron launch (seconds)
|
||||
interceptRatio = 0.8, -- RED interceptors per threat aircraft
|
||||
cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
|
||||
emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
|
||||
@ -157,7 +157,7 @@ local TADC_SETTINGS = {
|
||||
-- BLUE Coalition Settings
|
||||
blue = {
|
||||
maxActiveCAP = 24, -- Maximum BLUE fighters airborne at once
|
||||
squadronCooldown = 300, -- BLUE cooldown after squadron launch (seconds)
|
||||
squadronCooldown = 600, -- BLUE cooldown after squadron launch (seconds)
|
||||
interceptRatio = 0.8, -- BLUE interceptors per threat aircraft
|
||||
cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
|
||||
emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user