From b79a8f9b0a320e747c1f607cf1a02ffd297c0bdb Mon Sep 17 00:00:00 2001 From: iTracerFacer <134304944+iTracerFacer@users.noreply.github.com> Date: Sat, 15 Nov 2025 18:05:37 -0600 Subject: [PATCH] Created DynamicGroundBattle_Plugin - a fork from my DynamicGroundBattle script. Plugin as it's name implies plugs into the DualCoalitionZoneCapture scirpt seemlessly using the zones as the rally points for the dynamic ground battle. --- .../Moose_DynamicGroundBattle.lua | 45 +- .../Moose_DynamicGroundBattle_Plugin.lua | 502 ++++++++++++++++++ Moose_DynamicGroundBattle/PLUGIN_README.md | 224 ++++++++ 3 files changed, 758 insertions(+), 13 deletions(-) create mode 100644 Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua create mode 100644 Moose_DynamicGroundBattle/PLUGIN_README.md diff --git a/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle.lua b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle.lua index f302633..34469a8 100644 --- a/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle.lua +++ b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle.lua @@ -217,6 +217,9 @@ local blueArmorTemplates = { -- DO NOT EDIT BELOW THIS LINE ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Track active markers to prevent memory leaks +local activeMarkers = {} + -- Function to add mark points on the map for each warehouse in the provided list local function addMarkPoints(warehouses, coalition) for _, warehouse in ipairs(warehouses) do @@ -239,7 +242,7 @@ local function addMarkPoints(warehouses, coalition) local coordinate = COORDINATE:NewFromVec3(warehousePos) local marker = MARKER:New(coordinate, details):ToCoalition(coalition):ReadOnly() - marker:Remove(UPDATE_MARK_POINTS_SCHED) + table.insert(activeMarkers, marker) else env.info("addMarkPoints: Warehouse not found or is nil") end @@ -247,6 +250,14 @@ local function addMarkPoints(warehouses, coalition) end local function updateMarkPoints() + -- Clean up old markers first + for _, marker in ipairs(activeMarkers) do + if marker then + marker:Remove() + end + end + activeMarkers = {} + addMarkPoints(redWarehouses, 2) -- Blue coalition sees red warehouses as targets addMarkPoints(blueWarehouses, 2) -- Blue coalition sees blue warehouses as needing protection addMarkPoints(redWarehouses, 1) -- Red coalition sees red warehouses as needing protection @@ -261,6 +272,15 @@ end -- Table to keep track of zones and their statuses local zoneStatuses = {} +-- Reusable SET_GROUP to prevent memory leaks from repeated creation +local cachedAllGroups = nil +local function getAllGroups() + if not cachedAllGroups then + cachedAllGroups = SET_GROUP:New():FilterActive():FilterStart() + end + return cachedAllGroups +end + -- Function to create a capture zone local function CreateCaptureZone(zone, coalition) local captureZone = ZONE_CAPTURE_COALITION:New(zone, coalition) @@ -516,7 +536,7 @@ local function CheckZoneStates() env.info("Processing " .. zoneType) env.info("Number of zones: " .. #zones) - local allGroups = SET_GROUP:New():FilterActive():FilterStart() + local allGroups = getAllGroups() for _, zone in ipairs(zones) do if zone then @@ -671,7 +691,7 @@ end local function AssignTasksToGroups() env.info("AssignTasksToGroups: Starting task assignments") local zoneStates = CheckZoneStates() - local allGroups = SET_GROUP:New():FilterActive():FilterStart() + local allGroups = getAllGroups() local function processZone(zone, zoneColor) if zone then @@ -871,6 +891,7 @@ local function MonitorWarehouses() end -- Function to check the wincondition. If either side owns all zones, mission ends. +local winConditionScheduler = nil local function checkWinCondition() local blueOwned = true local redOwned = true @@ -887,26 +908,24 @@ local function checkWinCondition() if blueOwned then MESSAGE:New("Blue side wins! They own all the capture zones.", 60):ToAll() SOUND:New("UsaTheme.ogg"):ToAll() + if winConditionScheduler then + winConditionScheduler:Stop() + end return true elseif redOwned then MESSAGE:New("Red side wins! They own all the capture zones.", 60):ToAll() SOUND:New("MotherRussia.ogg"):ToAll() + if winConditionScheduler then + winConditionScheduler:Stop() + end return true end return false end - -- Timer function to periodically check the win condition - local function monitorWinCondition() - if not checkWinCondition() then - -- Schedule the next check in 60 seconds - TIMER:New(monitorWinCondition):Start(60) - end - end - - -- Start monitoring the win condition - monitorWinCondition() + -- Start monitoring the win condition with SCHEDULER instead of recursive TIMER + winConditionScheduler = SCHEDULER:New(nil, checkWinCondition, {}, 60, 60) -- Scheduler to monitor warehouses every 120 seconds SCHEDULER:New(nil, MonitorWarehouses, {}, 0, 120) diff --git a/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua new file mode 100644 index 0000000..a606a40 --- /dev/null +++ b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua @@ -0,0 +1,502 @@ +--[[ + Script: Moose_DynamicGroundBattle_Plugin.lua + Written by: [F99th-TracerFacer] + Version: 1.0.0 + Date: 15 November 2024 + Description: Warehouse-driven ground unit spawning system that works as a plugin + with Moose_DualCoalitionZoneCapture.lua + + This script handles: + - Warehouse-based reinforcement system + - Dynamic spawn frequency based on warehouse survival + - Automated AI tasking to patrol nearest enemy zones + - Optional infantry patrol control + - Warehouse status intel markers + - CTLD troop integration + + What this script DOES NOT do: + - Zone capture logic (handled by Moose_DualCoalitionZoneCapture.lua) + - Win conditions (handled by Moose_DualCoalitionZoneCapture.lua) + - Zone coloring/messaging (handled by Moose_DualCoalitionZoneCapture.lua) + + Load Order (in Mission Editor Triggers): + 1. DO SCRIPT FILE Moose_.lua + 2. DO SCRIPT FILE Moose_DualCoalitionZoneCapture.lua + 3. DO SCRIPT FILE Moose_DynamicGroundBattle_Plugin.lua <-- This file + 4. DO SCRIPT FILE CTLD.lua (optional) + 5. DO SCRIPT FILE CSAR.lua (optional) + + Requirements: + - MOOSE framework must be loaded first + - Moose_DualCoalitionZoneCapture.lua must be loaded BEFORE this script + - Zone configuration comes from DualCoalitionZoneCapture's ZONE_CONFIG + - Groups and warehouses must exist in mission editor (see below) + + Warehouse System & Spawn Frequency Behavior: + 1. Each side has warehouses defined in `redWarehouses` and `blueWarehouses` tables + 2. Spawn frequency dynamically adjusts based on alive warehouses: + - 100% alive = 100% spawn rate (base frequency) + - 50% alive = 50% spawn rate (2x delay) + - 0% alive = no spawns (critical attrition) + 3. Map markers show warehouse locations and nearby units + 4. Updated every UPDATE_MARK_POINTS_SCHED seconds + + AI Task Assignment: + - Groups spawn in friendly zones, then patrol toward nearest enemy zone + - Reassignment occurs every ASSIGN_TASKS_SCHED seconds + - Only stationary units get new orders (moving units are left alone) + - CTLD-dropped troops automatically integrate + + Groups to Create in Mission Editor (all LATE ACTIVATE): + RED SIDE: + - Infantry Templates: RedInfantry1, RedInfantry2, RedInfantry3, RedInfantry4, RedInfantry5, RedInfantry6 + - Armor Templates: RedArmor1, RedArmor2, RedArmor3, RedArmor4, RedArmor5, RedArmor6 + - Warehouses (Static Objects): RedWarehouse1-1, RedWarehouse2-1, RedWarehouse3-1, etc. + + BLUE SIDE: + - Infantry Templates: BlueInfantry1, BlueInfantry2, BlueInfantry3, BlueInfantry4, BlueInfantry5, BlueInfantry6 + - Armor Templates: BlueArmor1, BlueArmor2, BlueArmor3, BlueArmor4, BlueArmor5 + - Warehouses (Static Objects): BlueWarehouse1-1, BlueWarehouse2-1, BlueWarehouse3-1, etc. + + NOTE: Warehouse names use the static "Unit Name" in mission editor, not the "Name" field! + + Integration with DualCoalitionZoneCapture: + - This script reads zoneCaptureObjects and zoneNames from DualCoalitionZoneCapture + - Spawns occur in zones controlled by the appropriate coalition + - AI tasks units to patrol zones from DualCoalitionZoneCapture's ZONE_CONFIG +--]] + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- USER CONFIGURATION SECTION +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- Infantry Patrol Settings +local MOVING_INFANTRY_PATROLS = false -- Set to false to disable infantry movement (they spawn and hold position) + +-- Warehouse Marker Settings +local ENABLE_WAREHOUSE_MARKERS = true -- Enable/disable warehouse map markers (disabled by default if you have other marker systems) +local UPDATE_MARK_POINTS_SCHED = 300 -- Update warehouse markers every 300 seconds (5 minutes) +local MAX_WAREHOUSE_UNIT_LIST_DISTANCE = 5000 -- Max distance to search for units near warehouses for markers + +-- Spawn Frequency and Limits +local INIT_RED_INFANTRY = 5 -- Initial number of Red Infantry groups +local MAX_RED_INFANTRY = 100 -- Maximum number of Red Infantry groups +local SPAWN_SCHED_RED_INFANTRY = 1800 -- Base spawn frequency for Red Infantry (seconds) + +local INIT_RED_ARMOR = 25 -- Initial number of Red Armor groups +local MAX_RED_ARMOR = 200 -- Maximum number of Red Armor groups +local SPAWN_SCHED_RED_ARMOR = 300 -- Base spawn frequency for Red Armor (seconds) + +local INIT_BLUE_INFANTRY = 5 -- Initial number of Blue Infantry groups +local MAX_BLUE_INFANTRY = 100 -- Maximum number of Blue Infantry groups +local SPAWN_SCHED_BLUE_INFANTRY = 1800 -- Base spawn frequency for Blue Infantry (seconds) + +local INIT_BLUE_ARMOR = 25 -- Initial number of Blue Armor groups +local MAX_BLUE_ARMOR = 200 -- Maximum number of Blue Armor groups +local SPAWN_SCHED_BLUE_ARMOR = 300 -- Base spawn frequency for Blue Armor (seconds) + +local ASSIGN_TASKS_SCHED = 600 -- How often to reassign tasks to idle groups (seconds) + +-- Define warehouses for each side +local redWarehouses = { + STATIC:FindByName("RedWarehouse1-1"), + STATIC:FindByName("RedWarehouse2-1"), + STATIC:FindByName("RedWarehouse3-1"), + STATIC:FindByName("RedWarehouse4-1"), + STATIC:FindByName("RedWarehouse5-1"), + STATIC:FindByName("RedWarehouse6-1") +} + +local blueWarehouses = { + STATIC:FindByName("BlueWarehouse1-1"), + STATIC:FindByName("BlueWarehouse2-1"), + STATIC:FindByName("BlueWarehouse3-1"), + STATIC:FindByName("BlueWarehouse4-1"), + STATIC:FindByName("BlueWarehouse5-1"), + STATIC:FindByName("BlueWarehouse6-1") +} + +-- Define unit templates (these groups must exist in mission editor as LATE ACTIVATE) +local redInfantryTemplates = { + "RedInfantry1", + "RedInfantry2", + "RedInfantry3", + "RedInfantry4", + "RedInfantry5", + "RedInfantry6" +} + +local redArmorTemplates = { + "RedArmor1", + "RedArmor2", + "RedArmor3", + "RedArmor4", + "RedArmor5", + "RedArmor6" +} + +local blueInfantryTemplates = { + "BlueInfantry1", + "BlueInfantry2", + "BlueInfantry3", + "BlueInfantry4", + "BlueInfantry5", + "BlueInfantry6" +} + +local blueArmorTemplates = { + "BlueArmor1", + "BlueArmor2", + "BlueArmor3", + "BlueArmor4", + "BlueArmor5" +} + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DO NOT EDIT BELOW THIS LINE +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +env.info("[DGB PLUGIN] Dynamic Ground Battle Plugin initializing...") + +-- Validate that DualCoalitionZoneCapture is loaded +if not zoneCaptureObjects or not zoneNames then + env.error("[DGB PLUGIN] ERROR: Moose_DualCoalitionZoneCapture.lua must be loaded BEFORE this plugin!") + env.error("[DGB PLUGIN] Make sure zoneCaptureObjects and zoneNames are available.") + return +end + +env.info("[DGB PLUGIN] Found " .. #zoneCaptureObjects .. " zones from DualCoalitionZoneCapture") + +-- Track active markers to prevent memory leaks +local activeMarkers = {} + +-- Reusable SET_GROUP to prevent memory leaks from repeated creation +local cachedAllGroups = nil +local function getAllGroups() + if not cachedAllGroups then + cachedAllGroups = SET_GROUP:New():FilterActive():FilterStart() + env.info("[DGB PLUGIN] Created cached SET_GROUP for performance") + end + return cachedAllGroups +end + +-- Function to get zones controlled by a specific coalition +local function GetZonesByCoalition(targetCoalition) + local zones = {} + + for idx, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture and zoneCapture:GetCoalition() == targetCoalition then + local zone = zoneCapture:GetZone() + if zone then + table.insert(zones, zone) + end + end + end + + env.info(string.format("[DGB PLUGIN] Found %d zones for coalition %d", #zones, targetCoalition)) + return zones +end + +-- Function to calculate spawn frequency based on warehouse survival +local function CalculateSpawnFrequency(warehouses, baseFrequency) + local totalWarehouses = #warehouses + local aliveWarehouses = 0 + + for _, warehouse in ipairs(warehouses) do + if warehouse then + local life = warehouse:GetLife() + if life and life > 0 then + aliveWarehouses = aliveWarehouses + 1 + end + end + end + + if totalWarehouses == 0 or aliveWarehouses == 0 then + return math.huge -- Stop spawning if no warehouses remain + end + + local frequency = baseFrequency * (totalWarehouses / aliveWarehouses) + return frequency +end + +-- Function to calculate spawn frequency as a percentage +local function CalculateSpawnFrequencyPercentage(warehouses) + local totalWarehouses = #warehouses + local aliveWarehouses = 0 + + for _, warehouse in ipairs(warehouses) do + if warehouse then + local life = warehouse:GetLife() + if life and life > 0 then + aliveWarehouses = aliveWarehouses + 1 + end + end + end + + if totalWarehouses == 0 then + return 0 + end + + local percentage = (aliveWarehouses / totalWarehouses) * 100 + return math.floor(percentage) +end + +-- Function to add warehouse markers on the map +local function addMarkPoints(warehouses, coalition) + for _, warehouse in ipairs(warehouses) do + if warehouse then + local warehousePos = warehouse:GetVec3() + local details + + if coalition == 2 then -- Blue viewing + if warehouse:GetCoalition() == 2 then + details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\n" + else + details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n" + end + elseif coalition == 1 then -- Red viewing + if warehouse:GetCoalition() == 1 then + details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\n" + else + details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n" + end + end + + local coordinate = COORDINATE:NewFromVec3(warehousePos) + local marker = MARKER:New(coordinate, details):ToCoalition(coalition):ReadOnly() + table.insert(activeMarkers, marker) + end + end +end + +-- Function to update warehouse markers +local function updateMarkPoints() + -- Clean up old markers first + for _, marker in ipairs(activeMarkers) do + if marker then + marker:Remove() + end + end + activeMarkers = {} + + addMarkPoints(redWarehouses, 2) -- Blue coalition sees red warehouses + addMarkPoints(blueWarehouses, 2) -- Blue coalition sees blue warehouses + addMarkPoints(redWarehouses, 1) -- Red coalition sees red warehouses + addMarkPoints(blueWarehouses, 1) -- Red coalition sees blue warehouses + + env.info("[DGB PLUGIN] Updated warehouse markers") +end + +-- Function to check if a group contains infantry units +local function IsInfantryGroup(group) + for _, unit in ipairs(group:GetUnits()) do + local unitTypeName = unit:GetTypeName() + if unitTypeName:find("Infantry") or unitTypeName:find("Soldier") or unitTypeName:find("Paratrooper") then + return true + end + end + return false +end + +-- Function to assign tasks to a group +local function AssignTasks(group) + if not group or not group.GetCoalition or not group.GetCoordinate or not group.GetVelocityVec3 then + return + end + + -- Don't reassign if already moving + local velocity = group:GetVelocityVec3() + local speed = math.sqrt(velocity.x^2 + velocity.y^2 + velocity.z^2) + if speed > 0.5 then + return + end + + local groupCoalition = group:GetCoalition() + local groupCoordinate = group:GetCoordinate() + local closestZone = nil + local closestDistance = math.huge + + -- Find nearest enemy zone + for idx, zoneCapture in ipairs(zoneCaptureObjects) do + local zoneCoalition = zoneCapture:GetCoalition() + + if zoneCoalition ~= groupCoalition and zoneCoalition ~= coalition.side.NEUTRAL then + local zone = zoneCapture:GetZone() + if zone then + local zoneCoordinate = zone:GetCoordinate() + local distance = groupCoordinate:Get2DDistance(zoneCoordinate) + + if distance < closestDistance then + closestDistance = distance + closestZone = zone + end + end + end + end + + if closestZone then + env.info(string.format("[DGB PLUGIN] %s patrolling %s", group:GetName(), closestZone:GetName())) + group:PatrolZones({closestZone}, 20, "Cone", 30, 60) + end +end + +-- Function to assign tasks to all groups +local function AssignTasksToGroups() + env.info("[DGB PLUGIN] Starting task assignment cycle...") + local allGroups = getAllGroups() + local tasksAssigned = 0 + + allGroups:ForEachGroup(function(group) + if group and group:IsAlive() then + -- Check if group is in a friendly zone + local groupCoalition = group:GetCoalition() + local inFriendlyZone = false + + for idx, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture:GetCoalition() == groupCoalition then + local zone = zoneCapture:GetZone() + if zone and group:IsCompletelyInZone(zone) then + inFriendlyZone = true + break + end + end + end + + if inFriendlyZone then + -- Skip infantry if movement is disabled + if IsInfantryGroup(group) and not MOVING_INFANTRY_PATROLS then + return + end + + AssignTasks(group) + tasksAssigned = tasksAssigned + 1 + end + end + end) + + env.info(string.format("[DGB PLUGIN] Task assignment complete. %d groups tasked.", tasksAssigned)) +end + +-- Function to monitor and announce warehouse status +local function MonitorWarehouses() + local blueWarehousesAlive = 0 + local redWarehousesAlive = 0 + + for _, warehouse in ipairs(blueWarehouses) do + if warehouse and warehouse:IsAlive() then + blueWarehousesAlive = blueWarehousesAlive + 1 + end + end + + for _, warehouse in ipairs(redWarehouses) do + if warehouse and warehouse:IsAlive() then + redWarehousesAlive = redWarehousesAlive + 1 + end + end + + local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses) + local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses) + + local msg = "[Warehouse Status]\n" + msg = msg .. "Red warehouses alive: " .. redWarehousesAlive .. " Reinforcements: " .. redSpawnFrequencyPercentage .. "%\n" + msg = msg .. "Blue warehouses alive: " .. blueWarehousesAlive .. " Reinforcements: " .. blueSpawnFrequencyPercentage .. "%\n" + MESSAGE:New(msg, 30):ToAll() + + env.info(string.format("[DGB PLUGIN] Warehouse status - Red: %d/%d (%d%%), Blue: %d/%d (%d%%)", + redWarehousesAlive, #redWarehouses, redSpawnFrequencyPercentage, + blueWarehousesAlive, #blueWarehouses, blueSpawnFrequencyPercentage)) +end + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- INITIALIZATION +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- Get initial zone lists for each coalition +local redZones = GetZonesByCoalition(coalition.side.RED) +local blueZones = GetZonesByCoalition(coalition.side.BLUE) + +-- Calculate initial spawn frequencies +local redInfantrySpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_INFANTRY) +local redArmorSpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_ARMOR) +local blueInfantrySpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY) +local blueArmorSpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_ARMOR) + +-- Calculate and display initial spawn frequency percentages +local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses) +local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses) + +MESSAGE:New("Red reinforcement capacity: " .. redSpawnFrequencyPercentage .. "%", 30):ToRed() +MESSAGE:New("Blue reinforcement capacity: " .. blueSpawnFrequencyPercentage .. "%", 30):ToBlue() + +-- Initialize spawners +env.info("[DGB PLUGIN] Initializing spawn systems...") + +-- Note: Spawn zones will be dynamically updated based on zone capture states +-- We'll use a function to get current friendly zones on each spawn +local function GetRedZones() + return GetZonesByCoalition(coalition.side.RED) +end + +local function GetBlueZones() + return GetZonesByCoalition(coalition.side.BLUE) +end + +-- Red Infantry Spawner +redInfantrySpawn = SPAWN:New("RedInfantryGroup") + :InitRandomizeTemplate(redInfantryTemplates) + :InitLimit(INIT_RED_INFANTRY, MAX_RED_INFANTRY) + +-- Red Armor Spawner +redArmorSpawn = SPAWN:New("RedArmorGroup") + :InitRandomizeTemplate(redArmorTemplates) + :InitLimit(INIT_RED_ARMOR, MAX_RED_ARMOR) + +-- Blue Infantry Spawner +blueInfantrySpawn = SPAWN:New("BlueInfantryGroup") + :InitRandomizeTemplate(blueInfantryTemplates) + :InitLimit(INIT_BLUE_INFANTRY, MAX_BLUE_INFANTRY) + +-- Blue Armor Spawner +blueArmorSpawn = SPAWN:New("BlueArmorGroup") + :InitRandomizeTemplate(blueArmorTemplates) + :InitLimit(INIT_BLUE_ARMOR, MAX_BLUE_ARMOR) + +-- Custom spawn function that updates zones dynamically +local function SpawnWithDynamicZones() + local currentRedZones = GetRedZones() + local currentBlueZones = GetBlueZones() + + if #currentRedZones > 0 then + local randomRedZone = currentRedZones[math.random(#currentRedZones)] + redInfantrySpawn:SpawnInZone(randomRedZone, false) + redArmorSpawn:SpawnInZone(randomRedZone, false) + end + + if #currentBlueZones > 0 then + local randomBlueZone = currentBlueZones[math.random(#currentBlueZones)] + blueInfantrySpawn:SpawnInZone(randomBlueZone, false) + blueArmorSpawn:SpawnInZone(randomBlueZone, false) + end +end + +-- Schedule spawns +SCHEDULER:New(nil, SpawnWithDynamicZones, {}, 10, math.max(SPAWN_SCHED_RED_INFANTRY, SPAWN_SCHED_BLUE_INFANTRY)) + +-- Schedule warehouse marker updates +if ENABLE_WAREHOUSE_MARKERS then + SCHEDULER:New(nil, updateMarkPoints, {}, 10, UPDATE_MARK_POINTS_SCHED) +end + +-- Schedule warehouse monitoring +SCHEDULER:New(nil, MonitorWarehouses, {}, 30, 120) + +-- Schedule task assignments +SCHEDULER:New(nil, AssignTasksToGroups, {}, 120, ASSIGN_TASKS_SCHED) + +-- Add F10 menu for manual checks +local missionMenu = MENU_MISSION:New("Ground Battle") +MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses) + +env.info("[DGB PLUGIN] Dynamic Ground Battle Plugin initialized successfully!") +env.info(string.format("[DGB PLUGIN] Infantry movement: %s", MOVING_INFANTRY_PATROLS and "ENABLED" or "DISABLED")) +env.info(string.format("[DGB PLUGIN] Warehouse markers: %s", ENABLE_WAREHOUSE_MARKERS and "ENABLED" or "DISABLED")) diff --git a/Moose_DynamicGroundBattle/PLUGIN_README.md b/Moose_DynamicGroundBattle/PLUGIN_README.md new file mode 100644 index 0000000..9aef53e --- /dev/null +++ b/Moose_DynamicGroundBattle/PLUGIN_README.md @@ -0,0 +1,224 @@ +# Dynamic Ground Battle Plugin + +## Overview + +This is a **plugin version** of the Dynamic Ground Battle system designed to work alongside `Moose_DualCoalitionZoneCapture.lua`. + +**Key difference from the standalone version:** +- ✅ Uses zones from `Moose_DualCoalitionZoneCapture.lua` (no duplicate zone management) +- ✅ Handles ONLY warehouse system + AI spawning + task assignment +- ✅ Zone capture, colors, messaging, win conditions = handled by DualCoalitionZoneCapture +- ✅ Cleaner separation of concerns + +## Load Order (Critical!) + +In your Mission Editor → Triggers → Mission Start: + +``` +1. DO SCRIPT FILE Moose_.lua +2. DO SCRIPT FILE Moose_DualCoalitionZoneCapture.lua ← Must load FIRST +3. DO SCRIPT FILE Moose_DynamicGroundBattle_Plugin.lua ← This file +4. DO SCRIPT FILE CTLD.lua (optional) +5. DO SCRIPT FILE CSAR.lua (optional) +``` + +## What This Plugin Does + +### ✅ Warehouse System +- Red/Blue warehouses affect reinforcement spawn rates +- 100% alive = 100% spawn rate +- 50% alive = 50% spawn rate (2x delay) +- 0% alive = no more spawns +- Map markers show warehouse locations and intel + +### ✅ Dynamic AI Spawning +- Spawns infantry and armor groups in **friendly zones only** +- Zones come from `DualCoalitionZoneCapture`'s `ZONE_CONFIG` +- As zones are captured/lost, spawn locations automatically update +- Configurable limits and spawn frequencies + +### ✅ AI Task Assignment +- Groups patrol toward nearest enemy zone +- Reassignment every 600s (configurable) +- Only idle units get new orders (moving units ignored) +- Infantry movement can be disabled + +### ✅ CTLD Integration +- Dropped troops automatically get AI tasking +- Works seamlessly with logistics operations + +## What This Plugin Does NOT Do + +- ❌ Zone capture logic (handled by DualCoalitionZoneCapture) +- ❌ Zone coloring/messaging (handled by DualCoalitionZoneCapture) +- ❌ Win conditions (handled by DualCoalitionZoneCapture) +- ❌ Scoring (handled by DualCoalitionZoneCapture) + +## Configuration + +Edit the top section of `Moose_DynamicGroundBattle_Plugin.lua`: + +### Infantry Movement +```lua +local MOVING_INFANTRY_PATROLS = false -- Set true to enable infantry patrols +``` + +### Warehouse Markers +```lua +local ENABLE_WAREHOUSE_MARKERS = true +local UPDATE_MARK_POINTS_SCHED = 300 -- Update every 5 minutes +``` + +### Spawn Settings +```lua +-- Red Side +local INIT_RED_INFANTRY = 5 +local MAX_RED_INFANTRY = 100 +local SPAWN_SCHED_RED_INFANTRY = 1800 -- Every 30 minutes + +local INIT_RED_ARMOR = 25 +local MAX_RED_ARMOR = 200 +local SPAWN_SCHED_RED_ARMOR = 300 -- Every 5 minutes + +-- Blue Side +local INIT_BLUE_INFANTRY = 5 +local MAX_BLUE_INFANTRY = 100 +local SPAWN_SCHED_BLUE_INFANTRY = 1800 + +local INIT_BLUE_ARMOR = 25 +local MAX_BLUE_ARMOR = 200 +local SPAWN_SCHED_BLUE_ARMOR = 300 + +-- Task Assignment +local ASSIGN_TASKS_SCHED = 600 -- Reassign idle units every 10 minutes +``` + +### Warehouses +```lua +local redWarehouses = { + STATIC:FindByName("RedWarehouse1-1"), + STATIC:FindByName("RedWarehouse2-1"), + -- Add more as needed +} + +local blueWarehouses = { + STATIC:FindByName("BlueWarehouse1-1"), + STATIC:FindByName("BlueWarehouse2-1"), + -- Add more as needed +} +``` + +### Unit Templates +```lua +local redInfantryTemplates = { + "RedInfantry1", + "RedInfantry2", + -- Add more for variety +} + +local redArmorTemplates = { + "RedArmor1", + "RedArmor2", + -- Add more for variety +} + +-- Same for blue side... +``` + +## Mission Editor Setup + +### Required Groups (All LATE ACTIVATE) + +**Red Side:** +- Infantry: `RedInfantry1`, `RedInfantry2`, `RedInfantry3`, `RedInfantry4`, `RedInfantry5`, `RedInfantry6` +- Armor: `RedArmor1`, `RedArmor2`, `RedArmor3`, `RedArmor4`, `RedArmor5`, `RedArmor6` + +**Blue Side:** +- Infantry: `BlueInfantry1`, `BlueInfantry2`, `BlueInfantry3`, `BlueInfantry4`, `BlueInfantry5`, `BlueInfantry6` +- Armor: `BlueArmor1`, `BlueArmor2`, `BlueArmor3`, `BlueArmor4`, `BlueArmor5` + +### Required Static Objects (Warehouses) + +**Red Side:** +- Static objects with **Unit Name** (not Name field!): `RedWarehouse1-1`, `RedWarehouse2-1`, `RedWarehouse3-1`, etc. + +**Blue Side:** +- Static objects with **Unit Name**: `BlueWarehouse1-1`, `BlueWarehouse2-1`, `BlueWarehouse3-1`, etc. + +⚠️ **Important:** Warehouses use the "Unit Name" field in the static object properties, not the "Name" field! + +### Zone Configuration + +Zones come from `Moose_DualCoalitionZoneCapture.lua`. Edit that file's `ZONE_CONFIG`: + +```lua +local ZONE_CONFIG = { + RED = { + "Capture Zone-1", + "Capture Zone-2", + "Capture Zone-3", + }, + + BLUE = { + "Capture Zone-4", + "Capture Zone-5", + "Capture Zone-6", + }, + + NEUTRAL = {} +} +``` + +## F10 Menu + +- **Ground Battle → Check Warehouse Status** - Shows current warehouse count and reinforcement capacity + +## Integration Points + +### With DualCoalitionZoneCapture +- Reads `zoneCaptureObjects` and `zoneNames` arrays +- Dynamically spawns in zones controlled by the appropriate coalition +- AI routes to enemy zones based on current ownership + +### With CTLD +- Any troops dropped via CTLD in friendly zones will automatically receive AI tasking +- They'll patrol toward nearest enemy zone like spawned units + +## Performance & Memory Safety + +This plugin version includes all the memory leak fixes from the standalone version: + +✅ Marker cleanup prevents orphaning +✅ Reuses SET_GROUP instead of recreating +✅ Uses SCHEDULER instead of recursive TIMERs +✅ Efficient zone polling + +## Troubleshooting + +### "ERROR: Moose_DualCoalitionZoneCapture.lua must be loaded BEFORE this plugin!" +- Check your trigger load order +- DualCoalitionZoneCapture must be loaded in an earlier trigger + +### Units not spawning +- Check `dcs.log` for errors +- Verify warehouse static objects exist with correct **Unit Names** +- Verify template groups exist and are set to LATE ACTIVATE +- Check that friendly zones exist (units only spawn in controlled zones) + +### Units not moving +- If infantry: check `MOVING_INFANTRY_PATROLS` setting +- Verify enemy zones exist for AI to target +- Check `dcs.log` for task assignment messages (`[DGB PLUGIN]`) + +### Warehouse markers not showing +- Set `ENABLE_WAREHOUSE_MARKERS = true` +- Check warehouse static objects are alive +- Markers update every `UPDATE_MARK_POINTS_SCHED` seconds + +## Version History + +- **1.0.0** (2024-11-15) - Initial plugin version + - Extracted from standalone Dynamic Ground Battle + - Integrated with DualCoalitionZoneCapture + - Added memory leak fixes + - Dynamic zone-based spawning