Performance enhancments.

This commit is contained in:
iTracerFacer 2025-11-12 08:51:49 -06:00
parent f169d825d9
commit f2a88f85e9
4 changed files with 178 additions and 48 deletions

View File

@ -236,12 +236,32 @@ log("[DEBUG] Alakurtti zone initialization complete")
-- Global cached unit set - created once and maintained automatically by MOOSE -- Global cached unit set - created once and maintained automatically by MOOSE
local CachedUnitSet = nil local CachedUnitSet = nil
-- Utility to guard point-in-zone checks that may throw when objects despawn mid-loop
local function IsUnitInZone(unit, zone)
if not unit or not zone then return false end
local ok, point = pcall(function()
return unit:GetPointVec3()
end)
if not ok or not point then
return false
end
local inZone = false
pcall(function()
inZone = zone:IsPointVec3InZone(point)
end)
return inZone
end
-- Initialize the cached unit set once -- Initialize the cached unit set once
local function InitializeCachedUnitSet() local function InitializeCachedUnitSet()
if not CachedUnitSet then if not CachedUnitSet then
CachedUnitSet = SET_UNIT:New() CachedUnitSet = SET_UNIT:New()
:FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types :FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types
:FilterOnce() -- Don't filter continuously, we'll use the live set :FilterStart() -- Keep the set updated by MOOSE without recreating it
log("[PERFORMANCE] Initialized cached unit set for zone scanning") log("[PERFORMANCE] Initialized cached unit set for zone scanning")
end end
end end
@ -260,14 +280,12 @@ local function GetZoneForceStrengths(ZoneCapture)
local blueCount = 0 local blueCount = 0
local neutralCount = 0 local neutralCount = 0
-- Get all units in the zone using MOOSE's zone scanning -- Ensure the cached set exists before scanning
local unitsInZone = SET_UNIT:New() InitializeCachedUnitSet()
:FilterZones({zone})
:FilterOnce() if CachedUnitSet then
CachedUnitSet:ForEachUnit(function(unit)
if unitsInZone then if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
unitsInZone:ForEachUnit(function(unit)
if unit and unit:IsAlive() then
local unitCoalition = unit:GetCoalition() local unitCoalition = unit:GetCoalition()
if unitCoalition == coalition.side.RED then if unitCoalition == coalition.side.RED then
redCount = redCount + 1 redCount = redCount + 1
@ -302,50 +320,48 @@ local function GetRedUnitMGRSCoords(ZoneCapture)
local coords = {} local coords = {}
-- Get all units in the zone using MOOSE's zone scanning -- Ensure the cached set exists before scanning
local unitsInZone = SET_UNIT:New() InitializeCachedUnitSet()
:FilterZones({zone})
:FilterOnce()
local totalUnits = 0 local totalUnits = 0
local redUnits = 0 local redUnits = 0
local unitsWithCoords = 0 local unitsWithCoords = 0
if unitsInZone then if CachedUnitSet then
unitsInZone:ForEachUnit(function(unit) CachedUnitSet:ForEachUnit(function(unit)
totalUnits = totalUnits + 1 if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then
if unit and unit:IsAlive() then totalUnits = totalUnits + 1
local unitCoalition = unit:GetCoalition() local unitCoalition = unit:GetCoalition()
-- Only process RED units -- Only process RED units
if unitCoalition == coalition.side.RED then if unitCoalition == coalition.side.RED then
redUnits = redUnits + 1 redUnits = redUnits + 1
local coord = unit:GetCoordinate() local coord = unit:GetCoordinate()
if coord then if coord then
-- Try multiple methods to get coordinates -- Try multiple methods to get coordinates
local mgrs = nil local mgrs = nil
local success_mgrs = false local success_mgrs = false
-- Method 1: Try ToStringMGRS -- Method 1: Try ToStringMGRS
success_mgrs, mgrs = pcall(function() success_mgrs, mgrs = pcall(function()
return coord:ToStringMGRS(5) return coord:ToStringMGRS(5)
end) end)
-- Method 2: Try ToStringMGRS without precision parameter -- Method 2: Try ToStringMGRS without precision parameter
if not success_mgrs or not mgrs then if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function() success_mgrs, mgrs = pcall(function()
return coord:ToStringMGRS() return coord:ToStringMGRS()
end) end)
end end
-- Method 3: Try ToMGRS -- Method 3: Try ToMGRS
if not success_mgrs or not mgrs then if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function() success_mgrs, mgrs = pcall(function()
return coord:ToMGRS() return coord:ToMGRS()
end) end)
end end
-- Method 4: Fallback to Lat/Long -- Method 4: Fallback to Lat/Long
if not success_mgrs or not mgrs then if not success_mgrs or not mgrs then
success_mgrs, mgrs = pcall(function() success_mgrs, mgrs = pcall(function()
@ -353,7 +369,7 @@ local function GetRedUnitMGRSCoords(ZoneCapture)
return string.format("N%s E%s", lat, lon) return string.format("N%s E%s", lat, lon)
end) end)
end end
if success_mgrs and mgrs then if success_mgrs and mgrs then
unitsWithCoords = unitsWithCoords + 1 unitsWithCoords = unitsWithCoords + 1
local unitType = unit:GetTypeName() or "Unknown" local unitType = unit:GetTypeName() or "Unknown"

View File

@ -5,7 +5,8 @@
DESCRIPTION: DESCRIPTION:
This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them. It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system. This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them.
It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system.
CONFIGURATION: CONFIGURATION:
- Update static templates and airfield lists as needed for your mission. - Update static templates and airfield lists as needed for your mission.
@ -271,9 +272,9 @@ local function cleanupCargoMissions()
for _, coalitionKey in ipairs({"red", "blue"}) do for _, coalitionKey in ipairs({"red", "blue"}) do
for i = #cargoMissions[coalitionKey], 1, -1 do for i = #cargoMissions[coalitionKey], 1, -1 do
local m = cargoMissions[coalitionKey][i] local m = cargoMissions[coalitionKey][i]
if m.status == "failed" then if m.status == "failed" or m.status == "completed" then
if not (m.group and m.group:IsAlive()) then if not (m.group and m.group:IsAlive()) then
log("Cleaning up failed cargo mission: " .. (m.group and m.group:GetName() or "nil group") .. " status: failed") log("Cleaning up " .. m.status .. " cargo mission: " .. (m.group and m.group:GetName() or "nil group"))
table.remove(cargoMissions[coalitionKey], i) table.remove(cargoMissions[coalitionKey], i)
end end
end end
@ -451,6 +452,8 @@ local function dispatchCargo(squadron, coalitionKey)
rat:SetDeparture(origin) rat:SetDeparture(origin)
rat:SetDestination(destination) rat:SetDestination(destination)
rat:NoRespawn() rat:NoRespawn()
rat:InitUnControlled(false) -- force departing transports to spawn in a controllable state
rat:InitLateActivated(false)
rat:SetSpawnLimit(1) rat:SetSpawnLimit(1)
rat:SetSpawnDelay(1) rat:SetSpawnDelay(1)

View File

@ -276,6 +276,105 @@ local airbaseHealthStatus = {
blue = {} blue = {}
} }
local function coalitionKeyFromSide(side)
if side == coalition.side.RED then return "red" end
if side == coalition.side.BLUE then return "blue" end
return nil
end
local function cleanupInterceptorEntry(interceptorName, coalitionKey)
if not interceptorName or not coalitionKey then return end
if activeInterceptors[coalitionKey] then
activeInterceptors[coalitionKey][interceptorName] = nil
end
if aircraftSpawnTracking[coalitionKey] then
aircraftSpawnTracking[coalitionKey][interceptorName] = nil
end
end
local function destroyInterceptorGroup(interceptor, coalitionKey, delaySeconds)
if not interceptor then return end
local name = nil
if interceptor.GetName then
local ok, value = pcall(function() return interceptor:GetName() end)
if ok then name = value end
end
local resolvedKey = coalitionKey
if not resolvedKey and interceptor.GetCoalition then
local ok, side = pcall(function() return interceptor:GetCoalition() end)
if ok then
resolvedKey = coalitionKeyFromSide(side)
end
end
local function doDestroy()
if interceptor and interceptor.IsAlive and interceptor:IsAlive() then
pcall(function() interceptor:Destroy() end)
end
if name and resolvedKey then
cleanupInterceptorEntry(name, resolvedKey)
end
end
if delaySeconds and delaySeconds > 0 then
timer.scheduleFunction(function()
doDestroy()
return
end, {}, timer.getTime() + delaySeconds)
else
doDestroy()
end
end
local function finalizeCargoMission(cargoGroup, squadron, coalitionKey)
if not cargoMissions or not coalitionKey or not squadron or not squadron.airbaseName then
return
end
local coalitionBucket = cargoMissions[coalitionKey]
if type(coalitionBucket) ~= "table" then
return
end
local groupName = nil
if cargoGroup and cargoGroup.GetName then
local ok, value = pcall(function() return cargoGroup:GetName() end)
if ok then groupName = value end
end
for idx = #coalitionBucket, 1, -1 do
local mission = coalitionBucket[idx]
if mission and mission.destination == squadron.airbaseName then
local missionGroupName = nil
if mission.group and mission.group.GetName then
local ok, value = pcall(function() return mission.group:GetName() end)
if ok then missionGroupName = value end
end
if not groupName or missionGroupName == groupName then
mission.status = "completed"
mission.completedAt = timer.getTime()
if mission.group and mission.group.Destroy then
local targetGroup = mission.group
timer.scheduleFunction(function()
pcall(function()
if targetGroup and targetGroup.IsAlive and targetGroup:IsAlive() then
targetGroup:Destroy()
end
end)
return
end, {}, timer.getTime() + 90)
end
table.remove(coalitionBucket, idx)
end
end
end
end
-- Logging function -- Logging function
local function log(message, detailed) local function log(message, detailed)
if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then
@ -709,6 +808,8 @@ local function processCargoDelivery(cargoGroup, squadron, coalitionSide, coaliti
MESSAGE:New(msg, 10):ToCoalition(coalitionSide) MESSAGE:New(msg, 10):ToCoalition(coalitionSide)
USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide) USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
end end
finalizeCargoMission(cargoGroup, squadron, coalitionKey)
end end
-- Event handler for cargo aircraft landing (backup for actual landings) -- Event handler for cargo aircraft landing (backup for actual landings)
@ -1271,10 +1372,9 @@ local function monitorStuckAircraft()
-- Mark airbase as having stuck aircraft -- Mark airbase as having stuck aircraft
airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft" airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft"
-- Remove the stuck aircraft -- Remove the stuck aircraft and clear tracking
trackingData.group:Destroy() pcall(function() trackingData.group:Destroy() end)
activeInterceptors[coalitionKey][aircraftName] = nil cleanupInterceptorEntry(aircraftName, coalitionKey)
aircraftSpawnTracking[coalitionKey][aircraftName] = nil
-- Reassign squadron to alternative airbase -- Reassign squadron to alternative airbase
reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey) reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey)
@ -1342,9 +1442,14 @@ local function sendInterceptorHome(interceptor, coalitionSide)
SCHEDULER:New(nil, function() SCHEDULER:New(nil, function()
local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
if activeInterceptors[coalitionKey][interceptor:GetName()] then local name = nil
activeInterceptors[coalitionKey][interceptor:GetName()] = nil if interceptor and interceptor.GetName then
log("Cleaned up " .. coalitionName .. " " .. interceptor:GetName() .. " after RTB", true) local ok, value = pcall(function() return interceptor:GetName() end)
if ok then name = value end
end
if name and activeInterceptors[coalitionKey][name] then
destroyInterceptorGroup(interceptor, coalitionKey, 0)
log("Cleaned up " .. coalitionName .. " " .. name .. " after RTB", true)
end end
end, {}, flightTime) end, {}, flightTime)
else else
@ -1616,6 +1721,7 @@ local function launchInterceptor(threatGroup, coalitionSide)
log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName) log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName)
return return
end end
spawn:InitCleanUp(900)
local interceptors = {} local interceptors = {}
@ -1674,11 +1780,14 @@ local function launchInterceptor(threatGroup, coalitionSide)
-- Emergency cleanup (safety net) -- Emergency cleanup (safety net)
SCHEDULER:New(nil, function() SCHEDULER:New(nil, function()
if activeInterceptors[coalitionKey][interceptor:GetName()] then local name = nil
log("Emergency cleanup of " .. coalitionName .. " " .. interceptor:GetName() .. " (should have RTB'd)") if interceptor and interceptor.GetName then
activeInterceptors[coalitionKey][interceptor:GetName()] = nil local ok, value = pcall(function() return interceptor:GetName() end)
-- Also clean up spawn tracking if ok then name = value end
aircraftSpawnTracking[coalitionKey][interceptor:GetName()] = nil end
if name and activeInterceptors[coalitionKey][name] then
log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
destroyInterceptorGroup(interceptor, coalitionKey, 0)
end end
end, {}, coalitionSettings.emergencyCleanupTime) end, {}, coalitionSettings.emergencyCleanupTime)
end end
@ -2351,15 +2460,15 @@ local menuAdminRed = MENU_COALITION:New(coalition.side.RED, "Admin / Debug", men
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Emergency Cleanup Interceptors", menuAdminBlue, function() MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Emergency Cleanup Interceptors", menuAdminBlue, function()
local cleaned = 0 local cleaned = 0
for _, interceptors in pairs(activeInterceptors.red) do for name, interceptors in pairs(activeInterceptors.red) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil cleanupInterceptorEntry(name, "red")
cleaned = cleaned + 1 cleaned = cleaned + 1
end end
end end
for _, interceptors in pairs(activeInterceptors.blue) do for name, interceptors in pairs(activeInterceptors.blue) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil cleanupInterceptorEntry(name, "blue")
cleaned = cleaned + 1 cleaned = cleaned + 1
end end
end end
@ -2368,15 +2477,15 @@ end)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Emergency Cleanup Interceptors", menuAdminRed, function() MENU_COALITION_COMMAND:New(coalition.side.RED, "Emergency Cleanup Interceptors", menuAdminRed, function()
local cleaned = 0 local cleaned = 0
for _, interceptors in pairs(activeInterceptors.red) do for name, interceptors in pairs(activeInterceptors.red) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil cleanupInterceptorEntry(name, "red")
cleaned = cleaned + 1 cleaned = cleaned + 1
end end
end end
for _, interceptors in pairs(activeInterceptors.blue) do for name, interceptors in pairs(activeInterceptors.blue) do
if interceptors and interceptors.group and not interceptors.group:IsAlive() then if interceptors and interceptors.group and not interceptors.group:IsAlive() then
interceptors.group = nil cleanupInterceptorEntry(name, "blue")
cleaned = cleaned + 1 cleaned = cleaned + 1
end end
end end

View File

@ -444,6 +444,8 @@ local function dispatchCargo(squadron, coalitionKey)
rat:SetDeparture(origin) rat:SetDeparture(origin)
rat:SetDestination(destination) rat:SetDestination(destination)
rat:NoRespawn() rat:NoRespawn()
rat:InitUnControlled(false) -- ensure template-level 'Uncontrolled' flag does not leave transports parked
rat:InitLateActivated(false)
rat:SetSpawnLimit(1) rat:SetSpawnLimit(1)
rat:SetSpawnDelay(1) rat:SetSpawnDelay(1)
-- Ensure RAT takes off immediately from the runway (hot start) instead of staying parked -- Ensure RAT takes off immediately from the runway (hot start) instead of staying parked