9 Commits

Author SHA1 Message Date
iTracerFacer 66632775a0 Add dcs.log to .gitignore 2025-12-13 15:37:46 -06:00
iTracerFacer 32aa359ac3 Merge branch 'main' of https://github.com/iTracerFacer/Moose_TADC 2025-12-13 15:33:18 -06:00
Tracer Facer f22955c47c Delete Moose_TADC_v1.0.1.zip 2025-12-13 15:32:42 -06:00
iTracerFacer da8ea23809 Merge branch 'main' of https://github.com/iTracerFacer/Moose_TADC 2025-12-13 15:31:57 -06:00
iTracerFacer 0f40d7d1fb a 2025-12-13 15:28:24 -06:00
iTracerFacer ec1d880d60 he emergency cleanup system now checks if aircraft are still actively engaged in combat before destroying them. Here's what changed:
What the fix does:
Before cleanup: Checks if the aircraft is still assigned to any active threats
If still engaged: Skips cleanup and logs "still engaged in combat"
If not engaged: Proceeds with cleanup normally
How it works:
When the 2-hour timer fires, the system now:

 Checks the assignedThreats table to see if this aircraft is still hunting enemies
 Only destroys aircraft that have completed their mission or are no longer assigned
 Keeps aircraft alive if they're still actively dogfighting
Result:
RED FW190s chasing you won't vanish mid-combat anymore
Aircraft will only be cleaned up after they're done fighting
The safety net still exists for stuck/orphaned aircraft that never RTB
This preserves the emergency cleanup functionality while preventing the frustrating mid-combat disappearances you experienced. The aircraft will now fight until the threat is eliminated, then clean up naturally through the RTB system.
2025-12-13 15:25:12 -06:00
iTracerFacer 3dbe7fd66d cleaning up old files. 2025-12-09 21:56:38 -06:00
iTracerFacer 48c56398ac he cargo dispatcher will now:
Give aircraft 10 full minutes to taxi and take off before declaring them stuck
Prevent multiple aircraft from spawning simultaneously at the same airbase
Provide detailed diagnostic information about stuck aircraft (including speed)
Log when aircraft successfully take off for better visibility
Issue warnings after 3 failures at any specific airbase
2025-12-09 21:39:41 -06:00
iTracerFacer 5377738925 Extra checking for stuck aircraft. Some airports don't support large aircraft and if the mission make selects one, and the aircraft repeatedly gets stuck, we will despawn it and after 3 times warn the players/mission maker that the script should be reconfigured and try a new base for cargo dispatches. 2025-12-07 23:06:18 -06:00
5 changed files with 116 additions and 33 deletions
+4
View File
@@ -0,0 +1,4 @@
# DCS log files
dcs.log
*.log
+82 -28
View File
@@ -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
View File
@@ -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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB