mirror of
https://github.com/iTracerFacer/Moose_TADC.git
synced 2026-08-15 15:26:24 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66632775a0 | |||
| 32aa359ac3 | |||
| f22955c47c | |||
| da8ea23809 | |||
| 0f40d7d1fb | |||
| ec1d880d60 | |||
| 3dbe7fd66d | |||
| 48c56398ac | |||
| 5377738925 |
@@ -0,0 +1,4 @@
|
||||
|
||||
# DCS log files
|
||||
dcs.log
|
||||
*.log
|
||||
@@ -15,11 +15,14 @@ CONFIGURATION:
|
||||
|
||||
REQUIRES:
|
||||
- MOOSE framework (for SPAWN, AIRBASE, etc.)
|
||||
- Optional: MIST for deep copy of templates
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
]]
|
||||
---@diagnostic disable: undefined-global, lowercase-global
|
||||
-- MOOSE framework globals are defined at runtime by DCS World
|
||||
|
||||
|
||||
-- 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")
|
||||
@@ -27,6 +30,32 @@ if _G.__TDAC_DISPATCHER_RUNNING then
|
||||
end
|
||||
_G.__TDAC_DISPATCHER_RUNNING = true
|
||||
|
||||
--[[
|
||||
GLOBAL STATE AND CONFIGURATION
|
||||
--------------------------------------------------------------------------
|
||||
Tracks all active cargo missions and dispatcher configuration.
|
||||
]]
|
||||
if not cargoMissions then
|
||||
cargoMissions = { red = {}, blue = {} }
|
||||
end
|
||||
|
||||
-- Stuck aircraft tracking per airbase
|
||||
if not stuckCounts then
|
||||
stuckCounts = { red = {}, blue = {} }
|
||||
end
|
||||
|
||||
-- Dispatcher config (interval in seconds)
|
||||
if not DISPATCHER_CONFIG then
|
||||
-- default interval (seconds) and a slightly larger grace period to account for slow servers/networks
|
||||
DISPATCHER_CONFIG = { interval = 60, gracePeriod = 25 }
|
||||
end
|
||||
|
||||
-- Safety flag: when false, do NOT fall back to spawning from in-memory template tables.
|
||||
-- Set to true if you understand the tweaked-template warning and accept the risk.
|
||||
if DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE == nil then
|
||||
DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE = false
|
||||
end
|
||||
|
||||
--[[
|
||||
CARGO SUPPLY CONFIGURATION
|
||||
--------------------------------------------------------------------------
|
||||
@@ -45,29 +74,6 @@ local CARGO_SUPPLY_CONFIG = {
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
GLOBAL STATE AND CONFIGURATION
|
||||
--------------------------------------------------------------------------
|
||||
Tracks all active cargo missions and dispatcher configuration.
|
||||
]]
|
||||
if not cargoMissions then
|
||||
cargoMissions = { red = {}, blue = {} }
|
||||
end
|
||||
|
||||
-- Dispatcher config (interval in seconds)
|
||||
if not DISPATCHER_CONFIG then
|
||||
-- default interval (seconds) and a slightly larger grace period to account for slow servers/networks
|
||||
DISPATCHER_CONFIG = { interval = 60, gracePeriod = 25 }
|
||||
end
|
||||
|
||||
-- Safety flag: when false, do NOT fall back to spawning from in-memory template tables.
|
||||
-- Set to true if you understand the tweaked-template warning and accept the risk.
|
||||
if DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE == nil then
|
||||
DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE = false
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
@@ -101,7 +107,7 @@ end
|
||||
Advanced logging configuration and helper function for debug output.
|
||||
]]
|
||||
local ADVANCED_LOGGING = {
|
||||
enableDetailedLogging = false,
|
||||
enableDetailedLogging = true,
|
||||
logPrefix = "[TADC Cargo]"
|
||||
}
|
||||
|
||||
@@ -135,6 +141,10 @@ end
|
||||
local CARGO_DISPATCH_COOLDOWN = DISPATCHER_CONFIG and DISPATCHER_CONFIG.cooldown or 300 -- default 5 minutes
|
||||
local lastDispatchAttempt = { red = {}, blue = {} }
|
||||
|
||||
-- Per-airbase spawn throttling to prevent runway congestion (minimum time between spawns at same airbase)
|
||||
local AIRBASE_SPAWN_THROTTLE = 120 -- 2 minutes between spawns at same airbase
|
||||
local lastSpawnAtAirbase = { red = {}, blue = {} }
|
||||
|
||||
local function getCoalitionSide(coalitionKey)
|
||||
if coalitionKey == 'blue' then return coalition.side.BLUE end
|
||||
if coalitionKey == 'red' then return coalition.side.RED end
|
||||
@@ -329,6 +339,15 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
log("No valid origin airfield found for cargo dispatch to " .. squadron.airbaseName .. " (avoiding same origin/destination)")
|
||||
return
|
||||
end
|
||||
|
||||
-- Check airbase spawn throttle to prevent runway congestion
|
||||
lastSpawnAtAirbase[coalitionKey] = lastSpawnAtAirbase[coalitionKey] or {}
|
||||
local lastSpawnTime = lastSpawnAtAirbase[coalitionKey][origin]
|
||||
if lastSpawnTime and (timer.getTime() - lastSpawnTime) < AIRBASE_SPAWN_THROTTLE then
|
||||
log("Skipping dispatch from " .. origin .. " (spawn throttle active - preventing runway congestion)", true)
|
||||
return
|
||||
end
|
||||
|
||||
local destination = squadron.airbaseName
|
||||
local cargoTemplate = config.cargoTemplate
|
||||
-- Safety: check if destination has suitable parking for larger transports. If not, warn in log.
|
||||
@@ -489,7 +508,13 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
end
|
||||
end
|
||||
|
||||
log("RAT spawned cargo aircraft group: " .. tostring(spawnedGroup:GetName()))
|
||||
mission.spawnPos = spawnPos
|
||||
mission.spawnTime = timer.getTime()
|
||||
|
||||
-- Track spawn time for this airbase to enforce throttling
|
||||
lastSpawnAtAirbase[coalitionKey][origin] = timer.getTime()
|
||||
|
||||
log("RAT spawned cargo aircraft group: " .. tostring(spawnedGroup:GetName()) .. " from " .. origin)
|
||||
|
||||
-- CRITICAL FIX: Force group to start/activate immediately after spawn
|
||||
-- This addresses the MOOSE IsAlive=false issue where RAT spawns groups in inactive state
|
||||
@@ -549,9 +574,9 @@ local function dispatchCargo(squadron, coalitionKey)
|
||||
collectgarbage('step', 10) -- GC after verification
|
||||
end, {}, timer.getTime() + 2)
|
||||
|
||||
-- Temporary debug: log group state every 10s for 5 minutes to trace landing/parking behavior
|
||||
local debugChecks = 30 -- 30 * 10s = 5 minutes (reduced from 10 minutes to limit memory impact)
|
||||
local checkInterval = 10
|
||||
-- Debug logging: log group state every 20s for 12 minutes (600s takeoff window + 2 min buffer) to trace taxi/takeoff/landing behavior
|
||||
local debugChecks = 36 -- 36 * 20s = 12 minutes
|
||||
local checkInterval = 20
|
||||
local function debugLogState(iter)
|
||||
if iter > debugChecks then
|
||||
collectgarbage('step', 20) -- Final cleanup after debug sequence
|
||||
@@ -734,6 +759,35 @@ local function monitorCargoMissions()
|
||||
log("DEBUG: Mission appears to still have DCS units despite IsAlive=false; skipping failure for " .. tostring(mission.destination), true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for stuck aircraft (increased to 10 minutes to allow for long taxi distances)
|
||||
if mission.status == "enroute" and mission.group and mission.group:IsAlive() and mission.spawnTime then
|
||||
local timeSinceSpawn = timer.getTime() - mission.spawnTime
|
||||
if timeSinceSpawn > 600 then -- Check after 10 minutes
|
||||
local dcsGroup = mission.group:GetDCSObject()
|
||||
if dcsGroup then
|
||||
local units = dcsGroup:getUnits()
|
||||
if units and #units > 0 then
|
||||
local unit = units[1]
|
||||
if not unit:inAir() then
|
||||
-- Aircraft is stuck, not airborne after 10 minutes
|
||||
local vel = (unit.getVelocity and unit:getVelocity()) or {x=0,y=0,z=0}
|
||||
local speed = math.sqrt((vel.x or 0)^2 + (vel.y or 0)^2 + (vel.z or 0)^2)
|
||||
log("Cargo aircraft failed to take off from " .. tostring(mission.origin) .. " after 10 minutes: " .. tostring(mission.group:GetName()) .. " (speed: " .. string.format("%.1f", speed) .. " m/s)")
|
||||
mission.group:Destroy()
|
||||
mission.status = "failed"
|
||||
stuckCounts[coalitionKey][mission.origin] = (stuckCounts[coalitionKey][mission.origin] or 0) + 1
|
||||
local count = stuckCounts[coalitionKey][mission.origin]
|
||||
if count >= 3 then
|
||||
MESSAGE:New("WARNING: Airbase '" .. tostring(mission.origin) .. "' has caused " .. tostring(count) .. " cargo aircraft to fail takeoff after 10 minutes. Mission maker: reconfigure cargo operations to avoid this airbase or check for parking/runway issues.", 60):ToAll()
|
||||
end
|
||||
else
|
||||
log("Cargo aircraft from " .. tostring(mission.origin) .. " successfully airborne: " .. tostring(mission.group:GetName()), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
cleanupCargoMissions()
|
||||
|
||||
+30
-5
@@ -1385,8 +1385,8 @@ end
|
||||
-- Monitor for stuck aircraft at airbases
|
||||
local function monitorStuckAircraft()
|
||||
local currentTime = timer.getTime()
|
||||
local stuckThreshold = 300 -- 5 minutes before considering aircraft stuck
|
||||
local movementThreshold = 50 -- meters - aircraft must move at least this far to not be considered stuck
|
||||
local stuckThreshold = 900 -- 15 minutes before considering aircraft stuck (increased from 300)
|
||||
local movementThreshold = 500 -- meters - aircraft must move at least this far to not be considered stuck (increased from 50)
|
||||
|
||||
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||
local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
|
||||
@@ -1860,7 +1860,7 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
||||
log("Tracking spawn position for " .. interceptorName .. " at " .. squadron.airbaseName, true)
|
||||
end
|
||||
|
||||
-- Emergency cleanup (safety net)
|
||||
-- Emergency cleanup (safety net) - only cleanup if aircraft is no longer engaged
|
||||
local cleanupTime = (coalitionSettings and coalitionSettings.emergencyCleanupTime) or 7200
|
||||
SCHEDULER:New(nil, function()
|
||||
local name = nil
|
||||
@@ -1869,8 +1869,33 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
||||
if ok then name = value end
|
||||
end
|
||||
if name and activeInterceptors[coalitionKey][name] then
|
||||
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
|
||||
destroyInterceptorGroup(interceptor, coalitionKey, 0)
|
||||
-- Check if aircraft is still assigned to any threats (still in combat)
|
||||
local stillEngaged = false
|
||||
if assignedThreats[coalitionKey] then
|
||||
for threatName, interceptors in pairs(assignedThreats[coalitionKey]) do
|
||||
if type(interceptors) == 'table' then
|
||||
for _, assignedInterceptor in ipairs(interceptors) do
|
||||
local assignedName = nil
|
||||
if assignedInterceptor and assignedInterceptor.GetName then
|
||||
local ok, value = pcall(function() return assignedInterceptor:GetName() end)
|
||||
if ok and value == name then
|
||||
stillEngaged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if stillEngaged then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- Only cleanup if not engaged and still exists
|
||||
if not stillEngaged then
|
||||
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (completed mission or timeout)")
|
||||
destroyInterceptorGroup(interceptor, coalitionKey, 0)
|
||||
else
|
||||
log("Skipping emergency cleanup of " .. coalitionName .. " " .. name .. " (still engaged in combat)", true)
|
||||
end
|
||||
end
|
||||
end, {}, cleanupTime)
|
||||
end
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user