Updated all scripts.

This commit is contained in:
iTracerFacer 2025-12-02 20:31:27 -06:00
parent fd8dcfab73
commit 2bce81c559
6 changed files with 113 additions and 53 deletions

View File

@ -6,7 +6,20 @@
-- @module BOMBER_ESCORT
-- @author F99th-TracerFacer
-- @copyright 2025
--Naming Convention - Make sure these groups exist in the mission editor and are spelled EXACTLY as shown.
--
--B-17G -> Template name: BOMBER_B17G
--B-24J -> Template name: BOMBER_B24J
--B-52H -> Template name: BOMBER_B52H
--B-1B -> Template name: BOMBER_B1B
--Tu-95MS -> Template name: BOMBER_TU95
--Tu-142 -> Template name: BOMBER_TU142
--Tu-22M3 -> Template name: BOMBER_TU22
--Tu-160 -> Template name: BOMBER_TU160
--
---@diagnostic disable: undefined-global, lowercase-global
-- MOOSE framework globals are defined at runtime by DCS World
-- Global spawn counter to ensure unique MOOSE spawn indices
if not _BOMBER_GLOBAL_SPAWN_COUNTER then
@ -23,17 +36,6 @@ if not _ACTIVE_MISSION_IDS then
_ACTIVE_MISSION_IDS = {}
end
--Naming Convention:
--
--B-17G -> Template name: BOMBER_B17G
--B-24J -> Template name: BOMBER_B24J
--B-52H -> Template name: BOMBER_B52H
--B-1B -> Template name: BOMBER_B1B
--Tu-95MS -> Template name: BOMBER_TU95
--Tu-142 -> Template name: BOMBER_TU142
--Tu-22M3 -> Template name: BOMBER_TU22M3
--Tu-160 -> Template name: BOMBER_TU160
---
-- LOGGING SYSTEM
-- Central logging function with configurable log levels
@ -132,8 +134,8 @@ BOMBER_ESCORT_CONFIG = {
-- Escort Requirements
RequireEscort = true, -- Bombers require player escort to proceed with mission (default: true, set false to allow solo bomber missions)
EscortAirborneJoinGrace = 120, -- Seconds of grace after liftoff before escort warnings begin (default: 90)
EscortFormUpAnnouncementInterval = 60, -- Seconds between "need escort" calls during form-up (default: 60)
EscortAirborneJoinGrace = 300, -- Seconds of grace after liftoff before escort warnings begin (default: 90)
EscortFormUpAnnouncementInterval = 120, -- Seconds between "need escort" calls during form-up (default: 60)
EscortFormUpMaxAnnouncements = 5, -- Number of calls before aborting form-up (default: 5)
EscortLossAnnouncementInterval = 60, -- Seconds between in-flight escort loss warnings (default: 60)
EscortLossMaxAnnouncements = 5, -- Number of warnings before aborting due to no escort (default: 5)
@ -150,8 +152,9 @@ BOMBER_ESCORT_CONFIG = {
EscortFormationComplimentInterval = 180, -- Seconds between formation flying compliments (default: 180s = 3 minutes)
-- Threat Detection
SAMThreatDistance = 100000, -- Meters - SAM detection range (default: 100km - extended for early avoidance)
FighterThreatDistance = 100000, -- Meters - Fighter detection range (default: 100km - extended for escort positioning time)
-- OPTIMIZATION: Reduced from 100km to 80km to lower pathfinding memory usage
SAMThreatDistance = 80000, -- Meters - SAM detection range (default: 80km - optimized for memory)
FighterThreatDistance = 80000, -- Meters - Fighter detection range (default: 80km - optimized for memory)
ThreatCheckInterval = 10, -- Seconds between threat scans (default: 10)
-- SAM Warning System
@ -163,7 +166,7 @@ BOMBER_ESCORT_CONFIG = {
EnableSAMAvoidance = true, -- Enable SAM threat detection and mission abort (default: true)
SAMAvoidanceBuffer = 25000, -- Meters - Buffer added to SAM range for threat assessment (default: 25km)
SAMCorridorBuffer = 10000, -- Meters - Smaller buffer for corridor finding (pre-planning can be more aggressive, default: 10km)
SAMRouteLookAhead = 150000, -- Meters - Check route this far ahead for SAMs (default: 150km)
SAMRouteLookAhead = 120000, -- Meters - Check route this far ahead for SAMs (OPTIMIZED: reduced from 150km to 120km for memory)
SAMAvoidOnlyIfCanEngage = true, -- Only abort for SAMs that can engage at current altitude (default: true)
SAMRerouteCheckInterval = 10, -- Seconds between route threat checks during flight (default: 10s)
@ -198,6 +201,9 @@ BOMBER_ESCORT_CONFIG = {
-- Debug/Instrumentation
EnableRouteDebugSnapshots = true, -- Dump controller route tables after each route apply (set false to disable heavy TRACE logs)
RouteSnapshotDelaySeconds = 0.75, -- Delay before sampling controller route (allows DCS AI to register the new plan)
-- Memory Management
GarbageCollectionInterval = 600, -- Seconds between forced Lua garbage collection cycles (default: 600 = 10 minutes)
}
---
@ -944,10 +950,14 @@ function BOMBER_MARKER:_ExecuteMultiMissions(missions, coalitionSide)
-- Always show what was parsed
if hasSpawn then
feedbackMsg = feedbackMsg .. string.format("[OK] SPAWN: %s\n", mission.spawn.markerText)
feedbackMsg = feedbackMsg .. string.format(" Type: %s, Size: %s, Alt: %s, Speed: %s\n",
params.type or "[MISSING]", tostring(params.size or "[DEFAULT]"), tostring(params.altitude or "[DEFAULT]"), tostring(params.speed or "[DEFAULT]"))
if not params.type then
feedbackMsg = feedbackMsg .. " [X] Bomber type missing or malformed!\n"
if params then
feedbackMsg = feedbackMsg .. string.format(" Type: %s, Size: %s, Alt: %s, Speed: %s\n",
params.type or "[MISSING]", tostring(params.size or "[DEFAULT]"), tostring(params.altitude or "[DEFAULT]"), tostring(params.speed or "[DEFAULT]"))
if not params.type then
feedbackMsg = feedbackMsg .. " [X] Bomber type missing or malformed!\n"
end
else
feedbackMsg = feedbackMsg .. " [X] Failed to parse spawn marker parameters!\n"
end
else
feedbackMsg = feedbackMsg .. "[X] SPAWN: NONE (required)\n"
@ -1222,10 +1232,10 @@ end
function BOMBER_MARKER:_GetNextAvailableMissionNumber()
local maxNum = 0
for missionId in pairs(_ACTIVE_MISSION_IDS) do
local num = string.match(missionId, "BOMBER(%d+)")
if num then
num = tonumber(num)
if num > maxNum then
local numStr = string.match(missionId, "BOMBER(%d+)")
if numStr then
local num = tonumber(numStr)
if num and num > maxNum then
maxNum = num
end
end
@ -1356,6 +1366,10 @@ function BOMBER_ESCORT_MONITOR:Stop()
self.EscortUnits = {}
end
-- Force two-pass garbage collection for thorough cleanup
collectgarbage("collect")
collectgarbage("collect")
return self
end
@ -2022,6 +2036,9 @@ function BOMBER_THREAT_MANAGER:Stop()
self.ThreatHistory = {}
end
-- Force garbage collection after clearing tables
collectgarbage("collect")
BOMBER_LOGGER:Info("THREAT", "ThreatManager:Stop() completed")
return self
end
@ -3239,6 +3256,10 @@ function BOMBER_MISSION_MANAGER:UnregisterMission(mission)
if pruneCount > 0 then
BOMBER_LOGGER:Debug("MISSION", "Pruned %d unused SPAWN objects from global cache", pruneCount)
end
-- Force two-pass garbage collection after cleanup
collectgarbage("collect")
collectgarbage("collect")
end
end
@ -3444,8 +3465,8 @@ function BOMBER_MISSION:_BuildRoute()
end
-- Waypoint 1: Start (takeoff)
local cruiseAlt = self.CruiseAlt or profile.CruiseAlt
local cruiseSpeed = self.CruiseSpeed or profile.CruiseSpeed
local cruiseAlt = self.CruiseAlt or (profile and profile.CruiseAlt) or BOMBER_ESCORT_CONFIG.DefaultAltitude
local cruiseSpeed = self.CruiseSpeed or (profile and profile.CruiseSpeed) or BOMBER_ESCORT_CONFIG.DefaultSpeed
local cruiseAltMeters = cruiseAlt * 0.3048 -- Convert feet to meters
local cruiseSpeedMPS = cruiseSpeed * 0.514444 -- Convert knots to m/s
@ -4384,9 +4405,10 @@ function BOMBER_MISSION:_PlayerRequestSpeedUp()
local profile = self.Bomber.Profile
local currentSpeed = self.Bomber.Group:GetVelocityKNOTS()
if currentSpeed < profile.MaxSpeed - 10 then
local maxSpeed = (profile and profile.MaxSpeed) or 500 -- Default max speed if not defined
if currentSpeed < maxSpeed - 10 then
self.Bomber:_BroadcastMessage(string.format("%s: Increasing speed.", self.Callsign))
local newSpeed = math.min(currentSpeed + 20, profile.MaxSpeed)
local newSpeed = math.min(currentSpeed + 20, maxSpeed)
self.Bomber.Group:SetSpeed(newSpeed * 0.514444) -- Convert to m/s
else
self.Bomber:_BroadcastMessage(string.format("%s: Negative, already at max speed.", self.Callsign))
@ -4400,10 +4422,11 @@ function BOMBER_MISSION:_PlayerRequestSlowDown()
if self.Bomber and self.Bomber:IsAlive() then
local profile = self.Bomber.Profile
local currentSpeed = self.Bomber.Group:GetVelocityKNOTS()
if currentSpeed > profile.MinSpeed + 10 then
local minSpeed = (profile and profile.MinSpeed) or 200 -- Default min speed if not defined
if currentSpeed > minSpeed + 10 then
self.Bomber:_BroadcastMessage(string.format("%s: Reducing speed.", self.Callsign))
local newSpeed = math.max(currentSpeed - 20, profile.MinSpeed)
local newSpeed = math.max(currentSpeed - 20, minSpeed)
self.Bomber.Group:SetSpeed(newSpeed * 0.514444)
self.Bomber.Group:SetSpeed(newSpeed * 0.514444)
else
self.Bomber:_BroadcastMessage(string.format("%s: Negative, already at minimum speed.", self.Callsign))
@ -6646,7 +6669,7 @@ function BOMBER:_StartRTBMonitor()
self.LandingMonitor = nil
end
self:Destroy(1)
self:Destroy()
return
end
else
@ -8386,7 +8409,7 @@ function BOMBER:_UpdateSAMStatusSummary()
end
elseif threatCount > 1 and primaryThreat then
local closestNm = math.floor(closestDist / 1852)
local closestBearing = math.floor(closest.Bearing)
local closestBearing = closest and closest.Bearing and math.floor(closest.Bearing) or 0
local primaryType = primaryThreat.SAMType or "Unknown"
local canEngage = primaryThreat.CanEngage
@ -9960,6 +9983,16 @@ function BOMBER_ESCORT_INIT(options)
BOMBER_LOGGER:Info("INIT", "==============================================")
BOMBER_LOGGER:Info("INIT", "For complete documentation, see MARKER_GUIDE.md")
BOMBER_LOGGER:Info("INIT", "==============================================")
-- Schedule periodic garbage collection to prevent memory buildup
if BOMBER_ESCORT_CONFIG.GarbageCollectionInterval and BOMBER_ESCORT_CONFIG.GarbageCollectionInterval > 0 then
SCHEDULER:New(nil, function()
collectgarbage("collect")
local memKB = collectgarbage("count")
BOMBER_LOGGER:Debug("MEMORY", "Garbage collection complete. Current Lua memory: %.1f MB", memKB / 1024)
end, {}, 120, BOMBER_ESCORT_CONFIG.GarbageCollectionInterval)
BOMBER_LOGGER:Info("INIT", "Memory Management: Garbage collection scheduled every %d seconds", BOMBER_ESCORT_CONFIG.GarbageCollectionInterval)
end
return _BOMBER_MARKER_SYSTEM

View File

@ -1,4 +1,9 @@
-- Refactored version with configurable zone ownership
---@diagnostic disable: undefined-global, lowercase-global
-- MOOSE framework globals are defined at runtime by DCS World
-- **Author**: F99th-TracerFacer
-- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.)
-- ==========================================
-- MESSAGE AND TIMING CONFIGURATION
@ -13,7 +18,8 @@ local MESSAGE_CONFIG = {
STATUS_MESSAGE_DURATION = 15, -- How long general status messages stay onscreen
VICTORY_MESSAGE_DURATION = 300, -- How long victory/defeat alerts stay onscreen
CAPTURE_MESSAGE_DURATION = 15, -- Duration for capture/guard/empty notices
ATTACK_MESSAGE_DURATION = 15 -- Duration for attack alerts
ATTACK_MESSAGE_DURATION = 15, -- Duration for attack alerts
GARBAGE_COLLECTION_FREQUENCY = 600 -- Lua garbage collection cadence (seconds) - helps prevent memory buildup
}
-- ==========================================
@ -225,7 +231,8 @@ end
-- NOTE: These are exported as globals for plugin compatibility (e.g., Moose_DynamicGroundBattle_Plugin.lua)
zoneCaptureObjects = {} -- Global: accessible by other scripts
zoneNames = {} -- Global: accessible by other scripts
local zoneMetadata = {} -- Stores coalition ownership info
local zoneMetadata = {} -- Stores coalition ownership info
local activeTacticalMarkers = {} -- Track tactical markers to prevent memory leaks
-- Function to initialize all zones from configuration
local function InitializeZones()
@ -481,16 +488,16 @@ local function CreateTacticalInfoMarker(ZoneCapture)
text = text .. string.format(" C:%d", forces.neutral)
end
-- Append TGTS for the enemy of the viewer, capped at 10 units
-- Append TGTS for the enemy of the viewer, capped at 5 units (reduced from 10 to lower memory usage)
local enemyCoalition = (viewerCoalition == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
local enemyCount = (enemyCoalition == coalition.side.RED) and (forces.red or 0) or (forces.blue or 0)
if enemyCount > 0 and enemyCount <= 10 then
if enemyCount > 0 and enemyCount <= 8 then -- Only process if 8 or fewer enemies (reduced from 10)
local enemyCoords = GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition)
log(string.format("[TACTICAL DEBUG] Building marker text for %s viewer: %d enemy units", (viewerCoalition==coalition.side.BLUE and "BLUE" or "RED"), #enemyCoords))
if #enemyCoords > 0 then
text = text .. "\nTGTS:"
for i, unit in ipairs(enemyCoords) do
if i <= 10 then
if i <= 5 then -- Reduced from 10 to 5 to save memory
local shortType = (unit.type or "Unknown"):gsub("^%w+%-", ""):gsub("%s.*", "")
local cleanMgrs = (unit.mgrs or ""):gsub("^MGRS%s+", ""):gsub("%s+", " ")
if i == 1 then
@ -500,8 +507,8 @@ local function CreateTacticalInfoMarker(ZoneCapture)
end
end
end
if #enemyCoords > 10 then
text = text .. string.format(" (+%d)", #enemyCoords - 10)
if #enemyCoords > 5 then -- Updated threshold
text = text .. string.format(" (+%d)", #enemyCoords - 5)
end
end
end
@ -967,6 +974,13 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
end, {}, MESSAGE_CONFIG.STATUS_BROADCAST_START_DELAY, MESSAGE_CONFIG.STATUS_BROADCAST_FREQUENCY )
-- Periodic garbage collection to prevent Lua memory buildup
SCHEDULER:New( nil, function()
collectgarbage("collect")
local memKB = collectgarbage("count")
log(string.format("[MEMORY] Lua garbage collection complete. Current usage: %.1f MB", memKB / 1024))
end, {}, 120, MESSAGE_CONFIG.GARBAGE_COLLECTION_FREQUENCY )
-- Periodic zone color verification system (every 2 minutes)
local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
log("[ZONE COLORS] Running periodic zone color verification...")

View File

@ -1,4 +1,7 @@
--[[
- **Author**: F99th-TracerFacer
- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.)
Script: Moose_DynamicGroundBattle_Plugin.lua
Written by: [F99th-TracerFacer]
Version: 1.0.0
@ -72,6 +75,8 @@
- Spawns occur in zones controlled by the appropriate coalition
- AI tasks units to patrol zones from DualCoalitionZoneCapture's ZONE_CONFIG
--]]
---@diagnostic disable: undefined-global, lowercase-global
-- MOOSE framework globals are defined at runtime by DCS World
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- USER CONFIGURATION SECTION
@ -100,8 +105,8 @@ local INIT_RED_INFANTRY = 15 -- Initial number of Red Infantry groups
local MAX_RED_INFANTRY = 100 -- Maximum number of Red Infantry groups
local SPAWN_SCHED_RED_INFANTRY = 1200 -- Base spawn frequency for Red Infantry (seconds)
local INIT_RED_ARMOR = 25 -- Initial number of Red Armor groups
local MAX_RED_ARMOR = 100 -- Maximum number of Red Armor groups
local INIT_RED_ARMOR = 30 -- Initial number of Red Armor groups
local MAX_RED_ARMOR = 500 -- Maximum number of Red Armor groups
local SPAWN_SCHED_RED_ARMOR = 200 -- Base spawn frequency for Red Armor (seconds)
-- Blue Side Settings
@ -109,8 +114,8 @@ local INIT_BLUE_INFANTRY = 15 -- Initial number of Blue Infantry group
local MAX_BLUE_INFANTRY = 100 -- Maximum number of Blue Infantry groups
local SPAWN_SCHED_BLUE_INFANTRY = 1200 -- Base spawn frequency for Blue Infantry (seconds)
local INIT_BLUE_ARMOR = 25 -- Initial number of Blue Armor groups
local MAX_BLUE_ARMOR = 100 -- Maximum number of Blue Armor groups
local INIT_BLUE_ARMOR = 30 -- Initial number of Blue Armor groups
local MAX_BLUE_ARMOR = 500 -- Maximum number of Blue Armor groups
local SPAWN_SCHED_BLUE_ARMOR = 200 -- Base spawn frequency for Blue Armor (seconds)
local ASSIGN_TASKS_SCHED = 900 -- How often to reassign tasks to idle groups (seconds)
@ -133,7 +138,8 @@ local BLUE_ARMOR_SPAWN_GROUP = "BlueArmorGroup"
-- AI Tasking Behavior
-- Note: DCS engine can crash with "CREATING PATH MAKES TOO LONG" if units try to path too far
-- Keep these values conservative to reduce pathfinding load and avoid server crashes
local MAX_ATTACK_DISTANCE = 45000 -- Maximum distance in meters for attacking enemy zones. Units won't attack zones farther than this. (25km ≈ 13.5nm)
-- OPTIMIZATION: Reduced MAX_ATTACK_DISTANCE from 25km to 20km to reduce pathfinding complexity
local MAX_ATTACK_DISTANCE = 20000 -- Maximum distance in meters for attacking enemy zones. Units won't attack zones farther than this. (20km ≈ 10.8nm)
local ATTACK_RETRY_COOLDOWN = 1800 -- Seconds a group will wait before re-attempting an attack if no valid enemy zone was found (30 minutes)
-- Define warehouses for each side
@ -562,7 +568,7 @@ local function TryDefenderRotation(group, zone)
end
end
if oldestDefender and oldestDefenderGroup:GetName() ~= group:GetName() then
if oldestDefender and oldestDefenderGroup and oldestDefenderGroup:GetName() ~= group:GetName() then
-- Remove old defender
for i, defenderName in ipairs(garrison.defenders) do
if defenderName == oldestDefender then
@ -632,9 +638,10 @@ local function AssignTasksToGroups()
if zoneInfo and zoneInfo.zone then
env.info(string.format("[DGB PLUGIN] %s: Defender patrol in zone %s", groupName, zoneName))
-- Use simpler patrol method to reduce pathfinding memory
-- Reduced patrol radius from 0.5 to 0.3 to create simpler paths
local zoneCoord = zoneInfo.zone:GetCoordinate()
if zoneCoord then
local patrolPoint = zoneCoord:GetRandomCoordinateInRadius(zoneInfo.zone:GetRadius() * 0.5)
local patrolPoint = zoneCoord:GetRandomCoordinateInRadius(zoneInfo.zone:GetRadius() * 0.3) -- Reduced from 0.5
local speed = IsInfantryGroup(group) and 15 or 25 -- km/h - slow patrol
group:RouteGroundTo(patrolPoint, speed, "Vee", 1)
end
@ -682,7 +689,7 @@ local function AssignTasksToGroups()
end
-- 3. HANDLE GROUPS IN FRIENDLY ZONES
if currentZone and currentZoneCapture:GetCoalition() == groupCoalition then
if currentZone and currentZoneCapture and currentZoneCapture:GetCoalition() == groupCoalition then
local zoneName = currentZone:GetName()
-- PRIORITY 1: If the zone is under attack, all non-defenders should help defend it
@ -752,9 +759,10 @@ local function AssignTasksToGroups()
-- Use simpler waypoint-based routing instead of TaskRouteToZone to reduce pathfinding memory load
-- This prevents the "CREATING PATH MAKES TOO LONG" memory buildup
-- Reduced radius from 0.7 to 0.5 to create simpler, shorter paths
local zoneCoord = closestEnemyZone:GetCoordinate()
if zoneCoord then
local randomPoint = zoneCoord:GetRandomCoordinateInRadius(closestEnemyZone:GetRadius() * 0.7)
local randomPoint = zoneCoord:GetRandomCoordinateInRadius(closestEnemyZone:GetRadius() * 0.5) -- Reduced from 0.7
local speed = IsInfantryGroup(group) and 20 or 40 -- km/h
group:RouteGroundTo(randomPoint, speed, "Vee", 1)
end
@ -1189,8 +1197,10 @@ local function CleanupStaleData()
end
end
-- Force Lua garbage collection to reclaim memory
collectgarbage("collect")
-- Force aggressive Lua garbage collection to reclaim memory
-- Step-based collection helps ensure thorough cleanup
collectgarbage("collect") -- Full collection
collectgarbage("collect") -- Second pass to catch finalized objects
if cleanedGroups > 0 or cleanedCooldowns > 0 or cleanedGarrisons > 0 then
env.info(string.format("[DGB PLUGIN] Cleanup: Removed %d groups, %d cooldowns, %d garrisons",
@ -1200,10 +1210,13 @@ end
-- Optional periodic memory usage logging (Lua-only; shows in dcs.log)
local ENABLE_MEMORY_LOGGING = true
local MEMORY_LOG_INTERVAL = 900 -- seconds (15 minutes)
local CLEANUP_INTERVAL = 600 -- seconds (10 minutes)
local MEMORY_LOG_INTERVAL = 600 -- seconds (10 minutes) - reduced from 15 minutes
local CLEANUP_INTERVAL = 300 -- seconds (5 minutes) - reduced from 10 minutes for more aggressive cleanup
local function LogMemoryUsage()
-- Force garbage collection before measuring to get accurate readings
collectgarbage("collect")
local luaMemoryKB = collectgarbage("count")
local luaMemoryMB = luaMemoryKB / 1024