mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Added a guarded _safeRemoveMenu helper in Moose_CTLD_FAC.lua, so menu cleanup now verifies registration and logs stale handles instead of triggering BASE menu errors.
Introduced a safeCoordinate utility and threaded it through the stuck-aircraft monitor, RTB routing, and interceptor launch flow in Moose_TADC_Load2nd.lua, wrapping all coordinate math/tasking calls with pcall and emitting clear diagnostics when data vanishes mid-mission. Normalized interceptor name/coordinate tracking to reuse the same safely-fetched identifiers, preventing future nil dereferences when RTB or cleanup runs after units despawn.
This commit is contained in:
parent
dddd39407a
commit
88c159d52a
@ -2375,9 +2375,33 @@ function CTLD:_cancelSchedule(key)
|
||||
end
|
||||
|
||||
local function _removeMenuHandle(menu)
|
||||
if not menu then return end
|
||||
if type(menu) ~= 'table' then return end
|
||||
if menu.Remove then pcall(function() menu:Remove() end) end
|
||||
if not menu or type(menu) ~= 'table' then return end
|
||||
|
||||
local function _menuIsRegistered(m)
|
||||
if not MENU_INDEX then return true end
|
||||
if not m.Group or not m.MenuText then return true end
|
||||
local okPath, path = pcall(function()
|
||||
return MENU_INDEX:ParentPath(m.ParentMenu, m.MenuText)
|
||||
end)
|
||||
if not okPath or not path then
|
||||
return false
|
||||
end
|
||||
local okHas, registered = pcall(function()
|
||||
return MENU_INDEX:HasGroupMenu(m.Group, path)
|
||||
end)
|
||||
if not okHas then
|
||||
return false
|
||||
end
|
||||
return registered == m
|
||||
end
|
||||
|
||||
if menu.Remove and _menuIsRegistered(menu) then
|
||||
local ok, err = pcall(function() menu:Remove() end)
|
||||
if not ok then
|
||||
_logVerbose(string.format('[MenuCleanup] Failed to remove menu %s: %s', tostring(menu.MenuText), tostring(err)))
|
||||
end
|
||||
end
|
||||
|
||||
if menu.Destroy then pcall(function() menu:Destroy() end) end
|
||||
if menu.Delete then pcall(function() menu:Delete() end) end
|
||||
end
|
||||
@ -9165,7 +9189,7 @@ function CTLD:_SpawnMEDEVACCrew(eventData, catalogEntry)
|
||||
salvageValue = salvageValue,
|
||||
originalHeading = heading,
|
||||
requestTime = nil, -- Will be set after announcement delay
|
||||
warningsSent = 0,
|
||||
warningsSent = {},
|
||||
invulnerable = false,
|
||||
invulnerableUntil = 0,
|
||||
greetingSent = false
|
||||
@ -9302,6 +9326,9 @@ function CTLD:_CheckMEDEVACTimeouts()
|
||||
local requestTime = data.requestTime
|
||||
if requestTime then -- Only check after crew has requested pickup
|
||||
local elapsed = now - requestTime
|
||||
if type(data.warningsSent) ~= 'table' then
|
||||
data.warningsSent = {}
|
||||
end
|
||||
local remaining = (cfg.CrewTimeout or 3600) - elapsed
|
||||
|
||||
-- Check for approaching rescue helos (pop smoke and send greeting with cooldown)
|
||||
|
||||
@ -454,13 +454,44 @@ end
|
||||
-- #endregion Event wiring
|
||||
|
||||
-- #region Housekeeping
|
||||
function FAC:_safeRemoveMenu(menu, reason)
|
||||
if not menu or type(menu) ~= 'table' then return end
|
||||
|
||||
local shouldRemove = true
|
||||
if MENU_INDEX and menu.Group and menu.MenuText then
|
||||
local okPath, path = pcall(function()
|
||||
return MENU_INDEX:ParentPath(menu.ParentMenu, menu.MenuText)
|
||||
end)
|
||||
if okPath and path then
|
||||
local okHas, registered = pcall(function()
|
||||
return MENU_INDEX:HasGroupMenu(menu.Group, path)
|
||||
end)
|
||||
if not okHas or registered ~= menu then
|
||||
shouldRemove = false
|
||||
end
|
||||
else
|
||||
shouldRemove = false
|
||||
end
|
||||
end
|
||||
|
||||
if shouldRemove and menu.Remove then
|
||||
local ok, err = pcall(function() menu:Remove() end)
|
||||
if not ok and err then
|
||||
_log(self, LOG_VERBOSE, string.format('Failed removing menu (%s): %s', tostring(reason or menu.MenuText or 'unknown'), tostring(err)))
|
||||
end
|
||||
elseif not shouldRemove then
|
||||
_log(self, LOG_DEBUG, string.format('Skip stale menu removal (%s)', tostring(reason or menu.MenuText or 'unknown')))
|
||||
end
|
||||
|
||||
if menu.Destroy then pcall(function() menu:Destroy() end) end
|
||||
if menu.Delete then pcall(function() menu:Delete() end) end
|
||||
end
|
||||
|
||||
function FAC:_cleanupMenuForGroup(gname)
|
||||
local menuSet = self._menus[gname]
|
||||
if not menuSet then return end
|
||||
for _,menu in pairs(menuSet) do
|
||||
if menu and menu.Remove then
|
||||
pcall(function() menu:Remove() end)
|
||||
end
|
||||
self:_safeRemoveMenu(menu, gname)
|
||||
end
|
||||
self._menus[gname] = nil
|
||||
self._menuLastSeen[gname] = nil
|
||||
|
||||
@ -382,6 +382,17 @@ local function log(message, detailed)
|
||||
end
|
||||
end
|
||||
|
||||
local function safeCoordinate(object)
|
||||
if not object or type(object) ~= "table" or not object.GetCoordinate then
|
||||
return nil
|
||||
end
|
||||
local ok, coord = pcall(function() return object:GetCoordinate() end)
|
||||
if ok and coord then
|
||||
return coord
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Performance optimization: Cache SET_GROUP objects to avoid repeated creation
|
||||
local cachedSets = {
|
||||
redCargo = nil,
|
||||
@ -1360,9 +1371,18 @@ local function monitorStuckAircraft()
|
||||
|
||||
-- Only check aircraft that have been spawned for at least the threshold time
|
||||
if timeSinceSpawn >= stuckThreshold then
|
||||
local currentPos = trackingData.group:GetCoordinate()
|
||||
if currentPos and trackingData.spawnPos then
|
||||
local distanceMoved = trackingData.spawnPos:Get2DDistance(currentPos)
|
||||
local currentPos = safeCoordinate(trackingData.group)
|
||||
local spawnPos = trackingData.spawnPos
|
||||
local distanceMoved = nil
|
||||
|
||||
if currentPos and spawnPos and type(spawnPos) == "table" and spawnPos.Get2DDistance then
|
||||
local okDist, dist = pcall(function() return spawnPos:Get2DDistance(currentPos) end)
|
||||
if okDist and dist then
|
||||
distanceMoved = dist
|
||||
end
|
||||
end
|
||||
|
||||
if distanceMoved then
|
||||
|
||||
-- Check if aircraft has moved less than threshold (stuck)
|
||||
if distanceMoved < movementThreshold then
|
||||
@ -1385,6 +1405,9 @@ local function monitorStuckAircraft()
|
||||
log("Aircraft " .. aircraftName .. " has moved " .. math.floor(distanceMoved) .. "m - removing from stuck monitoring", true)
|
||||
aircraftSpawnTracking[coalitionKey][aircraftName] = nil
|
||||
end
|
||||
else
|
||||
log("Stuck monitor: no coordinate data for " .. aircraftName .. "; removing from tracking", true)
|
||||
aircraftSpawnTracking[coalitionKey][aircraftName] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -1402,12 +1425,13 @@ local function sendInterceptorHome(interceptor, coalitionSide)
|
||||
end
|
||||
|
||||
-- Find nearest friendly airbase
|
||||
local interceptorCoord = interceptor:GetCoordinate()
|
||||
local interceptorCoord = safeCoordinate(interceptor)
|
||||
if not interceptorCoord then
|
||||
log("ERROR: Could not get interceptor coordinates for RTB", true)
|
||||
return
|
||||
end
|
||||
local nearestAirbase = nil
|
||||
local nearestAirbaseCoord = nil
|
||||
local shortestDistance = math.huge
|
||||
local squadronConfig = getSquadronConfig(coalitionSide)
|
||||
|
||||
@ -1415,26 +1439,48 @@ local function sendInterceptorHome(interceptor, coalitionSide)
|
||||
for _, squadron in pairs(squadronConfig) do
|
||||
local airbase = AIRBASE:FindByName(squadron.airbaseName)
|
||||
if airbase and airbase:GetCoalition() == coalitionSide and airbase:IsAlive() then
|
||||
local airbaseCoord = airbase:GetCoordinate()
|
||||
local distance = interceptorCoord:Get2DDistance(airbaseCoord)
|
||||
if distance < shortestDistance then
|
||||
local airbaseCoord = safeCoordinate(airbase)
|
||||
if airbaseCoord then
|
||||
local okDist, distance = pcall(function() return interceptorCoord:Get2DDistance(airbaseCoord) end)
|
||||
if okDist and distance and distance < shortestDistance then
|
||||
shortestDistance = distance
|
||||
nearestAirbase = airbase
|
||||
nearestAirbaseCoord = airbaseCoord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if nearestAirbase then
|
||||
local airbaseCoord = nearestAirbase:GetCoordinate()
|
||||
if nearestAirbase and nearestAirbaseCoord then
|
||||
local airbaseName = "airbase"
|
||||
local okABName, fetchedABName = pcall(function() return nearestAirbase:GetName() end)
|
||||
if okABName and fetchedABName then
|
||||
airbaseName = fetchedABName
|
||||
end
|
||||
|
||||
local rtbAltitude = ADVANCED_SETTINGS.rtbAltitude * 0.3048 -- Convert feet to meters
|
||||
local rtbCoord = airbaseCoord:SetAltitude(rtbAltitude)
|
||||
local okRtb, rtbCoord = pcall(function() return nearestAirbaseCoord:SetAltitude(rtbAltitude) end)
|
||||
if not okRtb or not rtbCoord then
|
||||
log("ERROR: Failed to compute RTB coordinate for " .. airbaseName, true)
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear current tasks and route home
|
||||
interceptor:ClearTasks()
|
||||
interceptor:RouteAirTo(rtbCoord, ADVANCED_SETTINGS.rtbSpeed * 0.5144, "BARO") -- Convert knots to m/s
|
||||
pcall(function() interceptor:ClearTasks() end)
|
||||
local routeOk, routeErr = pcall(function() interceptor:RouteAirTo(rtbCoord, ADVANCED_SETTINGS.rtbSpeed * 0.5144, "BARO") end)
|
||||
|
||||
local _, coalitionName = getCoalitionSettings(coalitionSide)
|
||||
log("Sending " .. coalitionName .. " " .. interceptor:GetName() .. " back to " .. nearestAirbase:GetName(), true)
|
||||
local interceptorName = "interceptor"
|
||||
local okName, fetchedName = pcall(function() return interceptor:GetName() end)
|
||||
if okName and fetchedName then
|
||||
interceptorName = fetchedName
|
||||
end
|
||||
|
||||
if not routeOk and routeErr then
|
||||
log("ERROR: Failed to assign RTB route for " .. interceptorName .. " -> " .. airbaseName .. ": " .. tostring(routeErr), true)
|
||||
else
|
||||
log("Sending " .. coalitionName .. " " .. interceptorName .. " back to " .. airbaseName, true)
|
||||
end
|
||||
|
||||
-- Schedule cleanup after they should have landed
|
||||
local coalitionSettings = getCoalitionSettings(coalitionSide)
|
||||
@ -1739,10 +1785,16 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
||||
interceptor:OptionROTVertical()
|
||||
|
||||
-- Route to threat
|
||||
local currentThreatCoord = threatGroup:GetCoordinate()
|
||||
local currentThreatCoord = safeCoordinate(threatGroup)
|
||||
if currentThreatCoord then
|
||||
local interceptCoord = currentThreatCoord:SetAltitude(squadron.altitude * 0.3048) -- Convert feet to meters
|
||||
interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO") -- Convert knots to m/s
|
||||
local okIntercept, interceptCoord = pcall(function()
|
||||
return currentThreatCoord:SetAltitude(squadron.altitude * 0.3048)
|
||||
end)
|
||||
if okIntercept and interceptCoord then
|
||||
pcall(function()
|
||||
interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO")
|
||||
end)
|
||||
end
|
||||
|
||||
-- Attack the threat
|
||||
local attackTask = {
|
||||
@ -1760,22 +1812,28 @@ local function launchInterceptor(threatGroup, coalitionSide)
|
||||
end, {}, 3)
|
||||
|
||||
-- Track the interceptor with squadron info
|
||||
activeInterceptors[coalitionKey][interceptor:GetName()] = {
|
||||
local interceptorName = "interceptor"
|
||||
local okName, fetchedName = pcall(function() return interceptor:GetName() end)
|
||||
if okName and fetchedName then
|
||||
interceptorName = fetchedName
|
||||
end
|
||||
|
||||
activeInterceptors[coalitionKey][interceptorName] = {
|
||||
group = interceptor,
|
||||
squadron = squadron.templateName,
|
||||
displayName = squadron.displayName
|
||||
}
|
||||
|
||||
-- Track spawn position for stuck aircraft detection
|
||||
local spawnPos = interceptor:GetCoordinate()
|
||||
local spawnPos = safeCoordinate(interceptor)
|
||||
if spawnPos then
|
||||
aircraftSpawnTracking[coalitionKey][interceptor:GetName()] = {
|
||||
aircraftSpawnTracking[coalitionKey][interceptorName] = {
|
||||
spawnPos = spawnPos,
|
||||
spawnTime = timer.getTime(),
|
||||
squadron = squadron,
|
||||
airbase = squadron.airbaseName
|
||||
}
|
||||
log("Tracking spawn position for " .. interceptor:GetName() .. " at " .. squadron.airbaseName, true)
|
||||
log("Tracking spawn position for " .. interceptorName .. " at " .. squadron.airbaseName, true)
|
||||
end
|
||||
|
||||
-- Emergency cleanup (safety net)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user