mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Performance enhancments.
This commit is contained in:
parent
f169d825d9
commit
f2a88f85e9
@ -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"
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user