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:
|
REQUIRES:
|
||||||
- MOOSE framework (for SPAWN, AIRBASE, etc.)
|
- MOOSE framework (for SPAWN, AIRBASE, etc.)
|
||||||
|
- Optional: MIST for deep copy of templates
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
]]
|
]]
|
||||||
---@diagnostic disable: undefined-global, lowercase-global
|
---@diagnostic disable: undefined-global, lowercase-global
|
||||||
-- MOOSE framework globals are defined at runtime by DCS World
|
-- MOOSE framework globals are defined at runtime by DCS World
|
||||||
|
|
||||||
|
|
||||||
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
|
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
|
||||||
if _G.__TDAC_DISPATCHER_RUNNING then
|
if _G.__TDAC_DISPATCHER_RUNNING then
|
||||||
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
|
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
|
||||||
@@ -27,6 +30,32 @@ if _G.__TDAC_DISPATCHER_RUNNING then
|
|||||||
end
|
end
|
||||||
_G.__TDAC_DISPATCHER_RUNNING = true
|
_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
|
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.
|
Advanced logging configuration and helper function for debug output.
|
||||||
]]
|
]]
|
||||||
local ADVANCED_LOGGING = {
|
local ADVANCED_LOGGING = {
|
||||||
enableDetailedLogging = false,
|
enableDetailedLogging = true,
|
||||||
logPrefix = "[TADC Cargo]"
|
logPrefix = "[TADC Cargo]"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +141,10 @@ end
|
|||||||
local CARGO_DISPATCH_COOLDOWN = DISPATCHER_CONFIG and DISPATCHER_CONFIG.cooldown or 300 -- default 5 minutes
|
local CARGO_DISPATCH_COOLDOWN = DISPATCHER_CONFIG and DISPATCHER_CONFIG.cooldown or 300 -- default 5 minutes
|
||||||
local lastDispatchAttempt = { red = {}, blue = {} }
|
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)
|
local function getCoalitionSide(coalitionKey)
|
||||||
if coalitionKey == 'blue' then return coalition.side.BLUE end
|
if coalitionKey == 'blue' then return coalition.side.BLUE end
|
||||||
if coalitionKey == 'red' then return coalition.side.RED 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)")
|
log("No valid origin airfield found for cargo dispatch to " .. squadron.airbaseName .. " (avoiding same origin/destination)")
|
||||||
return
|
return
|
||||||
end
|
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 destination = squadron.airbaseName
|
||||||
local cargoTemplate = config.cargoTemplate
|
local cargoTemplate = config.cargoTemplate
|
||||||
-- Safety: check if destination has suitable parking for larger transports. If not, warn in log.
|
-- 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
|
||||||
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
|
-- CRITICAL FIX: Force group to start/activate immediately after spawn
|
||||||
-- This addresses the MOOSE IsAlive=false issue where RAT spawns groups in inactive state
|
-- 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
|
collectgarbage('step', 10) -- GC after verification
|
||||||
end, {}, timer.getTime() + 2)
|
end, {}, timer.getTime() + 2)
|
||||||
|
|
||||||
-- Temporary debug: log group state every 10s for 5 minutes to trace landing/parking behavior
|
-- Debug logging: log group state every 20s for 12 minutes (600s takeoff window + 2 min buffer) to trace taxi/takeoff/landing behavior
|
||||||
local debugChecks = 30 -- 30 * 10s = 5 minutes (reduced from 10 minutes to limit memory impact)
|
local debugChecks = 36 -- 36 * 20s = 12 minutes
|
||||||
local checkInterval = 10
|
local checkInterval = 20
|
||||||
local function debugLogState(iter)
|
local function debugLogState(iter)
|
||||||
if iter > debugChecks then
|
if iter > debugChecks then
|
||||||
collectgarbage('step', 20) -- Final cleanup after debug sequence
|
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)
|
log("DEBUG: Mission appears to still have DCS units despite IsAlive=false; skipping failure for " .. tostring(mission.destination), true)
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
cleanupCargoMissions()
|
cleanupCargoMissions()
|
||||||
|
|||||||
+30
-5
@@ -1385,8 +1385,8 @@ end
|
|||||||
-- Monitor for stuck aircraft at airbases
|
-- Monitor for stuck aircraft at airbases
|
||||||
local function monitorStuckAircraft()
|
local function monitorStuckAircraft()
|
||||||
local currentTime = timer.getTime()
|
local currentTime = timer.getTime()
|
||||||
local stuckThreshold = 300 -- 5 minutes before considering aircraft stuck
|
local stuckThreshold = 900 -- 15 minutes before considering aircraft stuck (increased from 300)
|
||||||
local movementThreshold = 50 -- meters - aircraft must move at least this far to not be considered stuck
|
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
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
|
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)
|
log("Tracking spawn position for " .. interceptorName .. " at " .. squadron.airbaseName, true)
|
||||||
end
|
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
|
local cleanupTime = (coalitionSettings and coalitionSettings.emergencyCleanupTime) or 7200
|
||||||
SCHEDULER:New(nil, function()
|
SCHEDULER:New(nil, function()
|
||||||
local name = nil
|
local name = nil
|
||||||
@@ -1869,8 +1869,33 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
|||||||
if ok then name = value end
|
if ok then name = value end
|
||||||
end
|
end
|
||||||
if name and activeInterceptors[coalitionKey][name] then
|
if name and activeInterceptors[coalitionKey][name] then
|
||||||
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
|
-- Check if aircraft is still assigned to any threats (still in combat)
|
||||||
destroyInterceptorGroup(interceptor, coalitionKey, 0)
|
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
|
||||||
end, {}, cleanupTime)
|
end, {}, cleanupTime)
|
||||||
end
|
end
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user