mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
1048 lines
43 KiB
Lua
1048 lines
43 KiB
Lua
--[[
|
|
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
|
|
- Zone garrison system (defenders stay in captured 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
|
|
- Each zone maintains a minimum garrison (defenders) that patrol only their zone
|
|
- Non-defender groups patrol toward nearest enemy zone
|
|
- Election system assigns defenders automatically based on zone needs
|
|
- Defenders are never reassigned and stay permanently in their zone
|
|
- Reassignment occurs every ASSIGN_TASKS_SCHED seconds for non-defenders only
|
|
- 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
|
|
- Spawn Groups: Names defined by RED_INFANTRY_SPAWN_GROUP and RED_ARMOR_SPAWN_GROUP variables (default: RedInfantryGroup, RedArmorGroup)
|
|
- 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
|
|
- Spawn Groups: Names defined by BLUE_INFANTRY_SPAWN_GROUP and BLUE_ARMOR_SPAWN_GROUP variables (default: BlueInfantryGroup, BlueArmorGroup)
|
|
- 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!
|
|
NOTE: Spawn groups should be simple groups set to LATE ACTIVATE. You can customize their names in the USER CONFIGURATION section.
|
|
|
|
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
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
-- Zone Garrison (Defender) Settings
|
|
local DEFENDERS_PER_ZONE = 2 -- Minimum number of groups that will garrison each friendly zone (recommended: 2)
|
|
local ALLOW_DEFENDER_ROTATION = true -- If true, fresh units can replace existing defenders when zone is over-garrisoned
|
|
|
|
-- Infantry Patrol Settings
|
|
local MOVING_INFANTRY_PATROLS = true -- 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
|
|
|
|
-- Warehouse Status Message Settings
|
|
local ENABLE_WAREHOUSE_STATUS_MESSAGES = true -- Enable/disable periodic warehouse status announcements
|
|
local WAREHOUSE_STATUS_MESSAGE_FREQUENCY = 1800 -- How often to announce warehouse status (seconds, default: 1800 = 30 minutes)
|
|
|
|
-- Spawn Frequency and Limits
|
|
-- Red Side Settings
|
|
local INIT_RED_INFANTRY = 25 -- 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 = 500 -- Maximum number of Red Armor groups
|
|
local SPAWN_SCHED_RED_ARMOR = 200 -- Base spawn frequency for Red Armor (seconds)
|
|
|
|
-- Blue Side Settings
|
|
local INIT_BLUE_INFANTRY = 25 -- Initial number of Blue Infantry groups
|
|
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 = 500 -- Maximum number of Blue Armor groups
|
|
local SPAWN_SCHED_BLUE_ARMOR = 200 -- Base spawn frequency for Blue Armor (seconds)
|
|
|
|
local ASSIGN_TASKS_SCHED = 600 -- How often to reassign tasks to idle groups (seconds)
|
|
|
|
-- Per-side cadence scalars (tune to make one side faster/slower without touching base frequencies)
|
|
local RED_INFANTRY_CADENCE_SCALAR = 1.0
|
|
local RED_ARMOR_CADENCE_SCALAR = 1.0
|
|
local BLUE_INFANTRY_CADENCE_SCALAR = 1.0
|
|
local BLUE_ARMOR_CADENCE_SCALAR = 1.0
|
|
|
|
-- When a side loses every warehouse we pause spawning and re-check after this delay
|
|
local NO_WAREHOUSE_RECHECK_DELAY = 180
|
|
|
|
-- 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"
|
|
}
|
|
|
|
-- Spawn Group Names (these are the base groups SPAWN:New() uses for spawning)
|
|
local RED_INFANTRY_SPAWN_GROUP = "RedInfantryGroup"
|
|
local RED_ARMOR_SPAWN_GROUP = "RedArmorGroup"
|
|
local BLUE_INFANTRY_SPAWN_GROUP = "BlueInfantryGroup"
|
|
local BLUE_ARMOR_SPAWN_GROUP = "BlueArmorGroup"
|
|
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- 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
|
|
|
|
-- Validate warehouses exist
|
|
local function ValidateWarehouses(warehouses, label)
|
|
local foundCount = 0
|
|
local missingCount = 0
|
|
|
|
for i, wh in ipairs(warehouses) do
|
|
if wh then
|
|
foundCount = foundCount + 1
|
|
env.info(string.format("[DGB PLUGIN] %s warehouse %d: %s (OK)", label, i, wh:GetName()))
|
|
else
|
|
missingCount = missingCount + 1
|
|
env.warning(string.format("[DGB PLUGIN] %s warehouse at index %d NOT FOUND in mission editor!", label, i))
|
|
end
|
|
end
|
|
|
|
env.info(string.format("[DGB PLUGIN] %s warehouses: %d found, %d missing", label, foundCount, missingCount))
|
|
return foundCount > 0
|
|
end
|
|
|
|
-- Validate unit templates exist
|
|
local function ValidateTemplates(templates, label)
|
|
local foundCount = 0
|
|
local missingCount = 0
|
|
|
|
for i, templateName in ipairs(templates) do
|
|
local group = GROUP:FindByName(templateName)
|
|
if group then
|
|
foundCount = foundCount + 1
|
|
env.info(string.format("[DGB PLUGIN] %s template %d: %s (OK)", label, i, templateName))
|
|
else
|
|
missingCount = missingCount + 1
|
|
env.warning(string.format("[DGB PLUGIN] %s template '%s' NOT FOUND in mission editor!", label, templateName))
|
|
end
|
|
end
|
|
|
|
env.info(string.format("[DGB PLUGIN] %s templates: %d found, %d missing", label, foundCount, missingCount))
|
|
return foundCount > 0
|
|
end
|
|
|
|
env.info("[DGB PLUGIN] Validating configuration...")
|
|
|
|
-- Validate all warehouses
|
|
local redWarehousesValid = ValidateWarehouses(redWarehouses, "Red")
|
|
local blueWarehousesValid = ValidateWarehouses(blueWarehouses, "Blue")
|
|
|
|
if not redWarehousesValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Red warehouses found! Red spawning will be disabled.")
|
|
end
|
|
|
|
if not blueWarehousesValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Blue warehouses found! Blue spawning will be disabled.")
|
|
end
|
|
|
|
-- Validate all templates
|
|
local redInfantryValid = ValidateTemplates(redInfantryTemplates, "Red Infantry")
|
|
local redArmorValid = ValidateTemplates(redArmorTemplates, "Red Armor")
|
|
local blueInfantryValid = ValidateTemplates(blueInfantryTemplates, "Blue Infantry")
|
|
local blueArmorValid = ValidateTemplates(blueArmorTemplates, "Blue Armor")
|
|
|
|
if not redInfantryValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Red Infantry templates found! Red Infantry spawning will fail.")
|
|
end
|
|
|
|
if not redArmorValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Red Armor templates found! Red Armor spawning will fail.")
|
|
end
|
|
|
|
if not blueInfantryValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Blue Infantry templates found! Blue Infantry spawning will fail.")
|
|
end
|
|
|
|
if not blueArmorValid then
|
|
env.warning("[DGB PLUGIN] WARNING: No valid Blue Armor templates found! Blue Armor spawning will fail.")
|
|
end
|
|
|
|
env.info("[DGB PLUGIN] Found " .. #zoneCaptureObjects .. " zones from DualCoalitionZoneCapture")
|
|
|
|
-- Track active markers to prevent memory leaks
|
|
local activeMarkers = {}
|
|
|
|
-- Zone Garrison Tracking System
|
|
-- Structure: zoneGarrisons[zoneName] = { defenders = {groupName1, groupName2, ...}, lastUpdate = timestamp }
|
|
local zoneGarrisons = {}
|
|
|
|
-- Group garrison assignments
|
|
-- Structure: groupGarrisonAssignments[groupName] = zoneName (or nil if not a defender)
|
|
local groupGarrisonAssignments = {}
|
|
|
|
-- 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
|
|
|
|
-- Helper to count warehouse availability
|
|
local function GetWarehouseStats(warehouses)
|
|
local alive = 0
|
|
local total = 0
|
|
|
|
for _, warehouse in ipairs(warehouses) do
|
|
if warehouse then
|
|
total = total + 1
|
|
local life = warehouse:GetLife()
|
|
if life and life > 0 then
|
|
alive = alive + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return alive, total
|
|
end
|
|
|
|
-- Function to calculate spawn frequency based on warehouse survival
|
|
local function CalculateSpawnFrequency(warehouses, baseFrequency, cadenceScalar)
|
|
local aliveWarehouses, totalWarehouses = GetWarehouseStats(warehouses)
|
|
cadenceScalar = cadenceScalar or 1
|
|
|
|
if totalWarehouses == 0 then
|
|
return baseFrequency * cadenceScalar
|
|
end
|
|
|
|
if aliveWarehouses == 0 then
|
|
return nil -- Pause spawning until logistics return
|
|
end
|
|
|
|
local frequency = baseFrequency * cadenceScalar * (totalWarehouses / aliveWarehouses)
|
|
return frequency
|
|
end
|
|
|
|
-- Function to calculate spawn frequency as a percentage
|
|
local function CalculateSpawnFrequencyPercentage(warehouses)
|
|
local aliveWarehouses, totalWarehouses = GetWarehouseStats(warehouses)
|
|
|
|
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 check if a group is assigned as a zone defender
|
|
local function IsDefender(group)
|
|
if not group then return false end
|
|
local groupName = group:GetName()
|
|
return groupGarrisonAssignments[groupName] ~= nil
|
|
end
|
|
|
|
-- Function to get garrison info for a zone
|
|
local function GetZoneGarrison(zoneName)
|
|
if not zoneGarrisons[zoneName] then
|
|
zoneGarrisons[zoneName] = {
|
|
defenders = {},
|
|
lastUpdate = timer.getTime()
|
|
}
|
|
end
|
|
return zoneGarrisons[zoneName]
|
|
end
|
|
|
|
-- Function to count alive defenders in a zone
|
|
local function CountAliveDefenders(zoneName)
|
|
local garrison = GetZoneGarrison(zoneName)
|
|
local aliveCount = 0
|
|
local deadDefenders = {}
|
|
|
|
for _, groupName in ipairs(garrison.defenders) do
|
|
local group = GROUP:FindByName(groupName)
|
|
if group and group:IsAlive() then
|
|
aliveCount = aliveCount + 1
|
|
else
|
|
-- Mark for cleanup
|
|
table.insert(deadDefenders, groupName)
|
|
end
|
|
end
|
|
|
|
-- Clean up dead defenders
|
|
for _, deadGroupName in ipairs(deadDefenders) do
|
|
for i, groupName in ipairs(garrison.defenders) do
|
|
if groupName == deadGroupName then
|
|
table.remove(garrison.defenders, i)
|
|
groupGarrisonAssignments[deadGroupName] = nil
|
|
env.info(string.format("[DGB PLUGIN] Removed destroyed defender %s from zone %s", deadGroupName, zoneName))
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return aliveCount
|
|
end
|
|
|
|
-- Function to elect a group as a zone defender
|
|
local function ElectDefender(group, zone, reason)
|
|
if not group or not zone then return false end
|
|
|
|
local groupName = group:GetName()
|
|
local zoneName = zone:GetName()
|
|
|
|
-- Check if already a defender
|
|
if IsDefender(group) then
|
|
return false
|
|
end
|
|
|
|
local garrison = GetZoneGarrison(zoneName)
|
|
|
|
-- Add to garrison
|
|
table.insert(garrison.defenders, groupName)
|
|
groupGarrisonAssignments[groupName] = zoneName
|
|
garrison.lastUpdate = timer.getTime()
|
|
|
|
-- Assign patrol task for the zone
|
|
group:PatrolZones({zone}, 20, "Cone", 30, 60)
|
|
|
|
env.info(string.format("[DGB PLUGIN] Elected %s as defender of zone %s (%s)", groupName, zoneName, reason))
|
|
return true
|
|
end
|
|
|
|
-- Function to check if a zone needs more defenders
|
|
local function ZoneNeedsDefenders(zoneName)
|
|
local aliveDefenders = CountAliveDefenders(zoneName)
|
|
return aliveDefenders < DEFENDERS_PER_ZONE
|
|
end
|
|
|
|
-- Function to handle defender rotation (replace old defender with fresh unit)
|
|
local function TryDefenderRotation(group, zone)
|
|
if not ALLOW_DEFENDER_ROTATION then return false end
|
|
|
|
local zoneName = zone:GetName()
|
|
local garrison = GetZoneGarrison(zoneName)
|
|
|
|
-- Count idle groups in zone (including current group)
|
|
local idleGroups = {}
|
|
local allGroups = getAllGroups()
|
|
|
|
allGroups:ForEachGroup(function(g)
|
|
if g and g:IsAlive() and g:GetCoalition() == group:GetCoalition() then
|
|
if g:IsCompletelyInZone(zone) then
|
|
local velocity = g:GetVelocityVec3()
|
|
local speed = math.sqrt(velocity.x^2 + velocity.y^2 + velocity.z^2)
|
|
if speed <= 0.5 then
|
|
table.insert(idleGroups, g)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Only rotate if we have more than DEFENDERS_PER_ZONE idle units
|
|
if #idleGroups > DEFENDERS_PER_ZONE then
|
|
-- Find oldest defender to replace
|
|
local oldestDefender = nil
|
|
local oldestDefenderGroup = nil
|
|
|
|
for _, defenderName in ipairs(garrison.defenders) do
|
|
local defenderGroup = GROUP:FindByName(defenderName)
|
|
if defenderGroup and defenderGroup:IsAlive() then
|
|
if not oldestDefender then
|
|
oldestDefender = defenderName
|
|
oldestDefenderGroup = defenderGroup
|
|
end
|
|
break -- Just take the first one for rotation
|
|
end
|
|
end
|
|
|
|
if oldestDefender and oldestDefenderGroup:GetName() ~= group:GetName() then
|
|
-- Remove old defender
|
|
for i, defenderName in ipairs(garrison.defenders) do
|
|
if defenderName == oldestDefender then
|
|
table.remove(garrison.defenders, i)
|
|
groupGarrisonAssignments[oldestDefender] = nil
|
|
env.info(string.format("[DGB PLUGIN] Rotated out defender %s from zone %s", oldestDefender, zoneName))
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Elect new defender
|
|
ElectDefender(group, zone, "rotation")
|
|
|
|
-- Old defender becomes mobile force
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function AssignTasks(group, currentZoneCapture)
|
|
if not group or not group.GetCoalition or not group.GetCoordinate or not group.GetVelocityVec3 then
|
|
return
|
|
end
|
|
|
|
-- GARRISON SYSTEM: Defenders never leave their zone
|
|
if IsDefender(group) then
|
|
local assignedZoneName = groupGarrisonAssignments[group:GetName()]
|
|
if assignedZoneName then
|
|
-- Find the zone object
|
|
for idx, zoneCapture in ipairs(zoneCaptureObjects) do
|
|
local zone = zoneCapture:GetZone()
|
|
if zone and zone:GetName() == assignedZoneName then
|
|
-- Keep patrolling home zone
|
|
group:PatrolZones({zone}, 20, "Cone", 30, 60)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
-- If we get here, the defender's zone was lost or not found, but they still stay put
|
|
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 currentZone = currentZoneCapture and currentZoneCapture:GetZone() or nil
|
|
|
|
-- GARRISON SYSTEM: Check if current zone needs defenders
|
|
if currentZoneCapture and currentZone and currentZoneCapture:GetCoalition() == groupCoalition then
|
|
local zoneName = currentZone:GetName()
|
|
|
|
-- Try to elect as defender if zone needs one
|
|
if ZoneNeedsDefenders(zoneName) then
|
|
if ElectDefender(group, currentZone, "zone under-garrisoned") then
|
|
return
|
|
end
|
|
else
|
|
-- Try rotation if enabled
|
|
if TryDefenderRotation(group, currentZone) then
|
|
return
|
|
end
|
|
end
|
|
|
|
-- If the zone is under attack, all units help defend (even non-defenders)
|
|
local zoneState = currentZoneCapture.GetCurrentState and currentZoneCapture:GetCurrentState() or nil
|
|
if zoneState == "Attacked" then
|
|
env.info(string.format("[DGB PLUGIN] %s defending contested zone %s", group:GetName(), currentZone:GetName()))
|
|
group:PatrolZones({ currentZone }, 20, "Cone", 30, 60)
|
|
return
|
|
end
|
|
end
|
|
|
|
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
|
|
local defendersActive = 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
|
|
local currentZoneCapture = nil
|
|
|
|
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
|
|
currentZoneCapture = zoneCapture
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if inFriendlyZone then
|
|
-- Skip infantry if movement is disabled (unless they're defenders)
|
|
if IsInfantryGroup(group) and not MOVING_INFANTRY_PATROLS and not IsDefender(group) then
|
|
return
|
|
end
|
|
|
|
-- Count defenders
|
|
if IsDefender(group) then
|
|
defendersActive = defendersActive + 1
|
|
end
|
|
|
|
AssignTasks(group, currentZoneCapture)
|
|
tasksAssigned = tasksAssigned + 1
|
|
end
|
|
end
|
|
end)
|
|
|
|
env.info(string.format("[DGB PLUGIN] Task assignment complete. %d groups tasked (%d defenders).", tasksAssigned, defendersActive))
|
|
end
|
|
|
|
-- Function to monitor and announce warehouse status
|
|
local function MonitorWarehouses()
|
|
local blueWarehousesAlive, blueWarehouseTotal = GetWarehouseStats(blueWarehouses)
|
|
local redWarehousesAlive, redWarehouseTotal = GetWarehouseStats(redWarehouses)
|
|
|
|
local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses)
|
|
local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses)
|
|
|
|
if ENABLE_WAREHOUSE_STATUS_MESSAGES then
|
|
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()
|
|
end
|
|
|
|
env.info(string.format("[DGB PLUGIN] Warehouse status - Red: %d/%d (%d%%), Blue: %d/%d (%d%%)",
|
|
redWarehousesAlive, redWarehouseTotal, redSpawnFrequencyPercentage,
|
|
blueWarehousesAlive, blueWarehouseTotal, blueSpawnFrequencyPercentage))
|
|
end
|
|
|
|
-- Function to count active units by coalition and type
|
|
local function CountActiveUnits(targetCoalition)
|
|
local infantry = 0
|
|
local armor = 0
|
|
local total = 0
|
|
local defenders = 0
|
|
local mobile = 0
|
|
|
|
local allGroups = getAllGroups()
|
|
|
|
allGroups:ForEachGroup(function(group)
|
|
if group and group:IsAlive() and group:GetCoalition() == targetCoalition then
|
|
total = total + 1
|
|
|
|
if IsDefender(group) then
|
|
defenders = defenders + 1
|
|
else
|
|
mobile = mobile + 1
|
|
end
|
|
|
|
if IsInfantryGroup(group) then
|
|
infantry = infantry + 1
|
|
else
|
|
armor = armor + 1
|
|
end
|
|
end
|
|
end)
|
|
|
|
return {
|
|
total = total,
|
|
infantry = infantry,
|
|
armor = armor,
|
|
defenders = defenders,
|
|
mobile = mobile
|
|
}
|
|
end
|
|
|
|
-- Function to get garrison status across all zones
|
|
local function GetGarrisonStatus(targetCoalition)
|
|
local garrisonedZones = 0
|
|
local underGarrisonedZones = 0
|
|
local totalFriendlyZones = 0
|
|
|
|
for idx, zoneCapture in ipairs(zoneCaptureObjects) do
|
|
if zoneCapture:GetCoalition() == targetCoalition then
|
|
totalFriendlyZones = totalFriendlyZones + 1
|
|
local zone = zoneCapture:GetZone()
|
|
if zone then
|
|
local zoneName = zone:GetName()
|
|
local defenderCount = CountAliveDefenders(zoneName)
|
|
|
|
if defenderCount >= DEFENDERS_PER_ZONE then
|
|
garrisonedZones = garrisonedZones + 1
|
|
else
|
|
underGarrisonedZones = underGarrisonedZones + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return {
|
|
totalZones = totalFriendlyZones,
|
|
garrisoned = garrisonedZones,
|
|
underGarrisoned = underGarrisonedZones
|
|
}
|
|
end
|
|
|
|
-- Function to display comprehensive system statistics
|
|
local function ShowSystemStatistics(playerCoalition)
|
|
-- Get warehouse stats
|
|
local redWarehousesAlive, redWarehouseTotal = GetWarehouseStats(redWarehouses)
|
|
local blueWarehousesAlive, blueWarehouseTotal = GetWarehouseStats(blueWarehouses)
|
|
|
|
-- Get unit counts
|
|
local redUnits = CountActiveUnits(coalition.side.RED)
|
|
local blueUnits = CountActiveUnits(coalition.side.BLUE)
|
|
|
|
-- Get garrison info
|
|
local redGarrison = GetGarrisonStatus(coalition.side.RED)
|
|
local blueGarrison = GetGarrisonStatus(coalition.side.BLUE)
|
|
|
|
-- Get spawn frequencies
|
|
local redSpawnFreqPct = CalculateSpawnFrequencyPercentage(redWarehouses)
|
|
local blueSpawnFreqPct = CalculateSpawnFrequencyPercentage(blueWarehouses)
|
|
|
|
-- Calculate actual spawn intervals
|
|
local redInfantryInterval = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_INFANTRY, RED_INFANTRY_CADENCE_SCALAR)
|
|
local redArmorInterval = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_ARMOR, RED_ARMOR_CADENCE_SCALAR)
|
|
local blueInfantryInterval = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY, BLUE_INFANTRY_CADENCE_SCALAR)
|
|
local blueArmorInterval = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_ARMOR, BLUE_ARMOR_CADENCE_SCALAR)
|
|
|
|
-- Build comprehensive report
|
|
local msg = "═══════════════════════════════════════\n"
|
|
msg = msg .. "DYNAMIC GROUND BATTLE - SYSTEM STATUS\n"
|
|
msg = msg .. "═══════════════════════════════════════\n\n"
|
|
|
|
-- Configuration Section
|
|
msg = msg .. "【CONFIGURATION】\n"
|
|
msg = msg .. " Defenders per Zone: " .. DEFENDERS_PER_ZONE .. "\n"
|
|
msg = msg .. " Defender Rotation: " .. (ALLOW_DEFENDER_ROTATION and "ENABLED" or "DISABLED") .. "\n"
|
|
msg = msg .. " Infantry Movement: " .. (MOVING_INFANTRY_PATROLS and "ENABLED" or "DISABLED") .. "\n"
|
|
msg = msg .. " Task Reassignment: Every " .. ASSIGN_TASKS_SCHED .. "s\n"
|
|
msg = msg .. " Warehouse Markers: " .. (ENABLE_WAREHOUSE_MARKERS and "ENABLED" or "DISABLED") .. "\n\n"
|
|
|
|
-- Spawn Limits Section
|
|
msg = msg .. "【SPAWN LIMITS】\n"
|
|
msg = msg .. " Red Infantry: " .. INIT_RED_INFANTRY .. "/" .. MAX_RED_INFANTRY .. "\n"
|
|
msg = msg .. " Red Armor: " .. INIT_RED_ARMOR .. "/" .. MAX_RED_ARMOR .. "\n"
|
|
msg = msg .. " Blue Infantry: " .. INIT_BLUE_INFANTRY .. "/" .. MAX_BLUE_INFANTRY .. "\n"
|
|
msg = msg .. " Blue Armor: " .. INIT_BLUE_ARMOR .. "/" .. MAX_BLUE_ARMOR .. "\n\n"
|
|
|
|
-- Red Coalition Section
|
|
msg = msg .. "【RED COALITION】\n"
|
|
msg = msg .. " Warehouses: " .. redWarehousesAlive .. "/" .. redWarehouseTotal .. " (" .. redSpawnFreqPct .. "%)\n"
|
|
msg = msg .. " Active Units: " .. redUnits.total .. " (" .. redUnits.infantry .. " inf, " .. redUnits.armor .. " armor)\n"
|
|
msg = msg .. " Defenders: " .. redUnits.defenders .. " | Mobile: " .. redUnits.mobile .. "\n"
|
|
msg = msg .. " Controlled Zones: " .. redGarrison.totalZones .. "\n"
|
|
msg = msg .. " - Garrisoned: " .. redGarrison.garrisoned .. "\n"
|
|
msg = msg .. " - Under-Garrisoned: " .. redGarrison.underGarrisoned .. "\n"
|
|
|
|
if redInfantryInterval then
|
|
msg = msg .. " Infantry Spawn: " .. math.floor(redInfantryInterval) .. "s\n"
|
|
else
|
|
msg = msg .. " Infantry Spawn: PAUSED (no warehouses)\n"
|
|
end
|
|
|
|
if redArmorInterval then
|
|
msg = msg .. " Armor Spawn: " .. math.floor(redArmorInterval) .. "s\n\n"
|
|
else
|
|
msg = msg .. " Armor Spawn: PAUSED (no warehouses)\n\n"
|
|
end
|
|
|
|
-- Blue Coalition Section
|
|
msg = msg .. "【BLUE COALITION】\n"
|
|
msg = msg .. " Warehouses: " .. blueWarehousesAlive .. "/" .. blueWarehouseTotal .. " (" .. blueSpawnFreqPct .. "%)\n"
|
|
msg = msg .. " Active Units: " .. blueUnits.total .. " (" .. blueUnits.infantry .. " inf, " .. blueUnits.armor .. " armor)\n"
|
|
msg = msg .. " Defenders: " .. blueUnits.defenders .. " | Mobile: " .. blueUnits.mobile .. "\n"
|
|
msg = msg .. " Controlled Zones: " .. blueGarrison.totalZones .. "\n"
|
|
msg = msg .. " - Garrisoned: " .. blueGarrison.garrisoned .. "\n"
|
|
msg = msg .. " - Under-Garrisoned: " .. blueGarrison.underGarrisoned .. "\n"
|
|
|
|
if blueInfantryInterval then
|
|
msg = msg .. " Infantry Spawn: " .. math.floor(blueInfantryInterval) .. "s\n"
|
|
else
|
|
msg = msg .. " Infantry Spawn: PAUSED (no warehouses)\n"
|
|
end
|
|
|
|
if blueArmorInterval then
|
|
msg = msg .. " Armor Spawn: " .. math.floor(blueArmorInterval) .. "s\n\n"
|
|
else
|
|
msg = msg .. " Armor Spawn: PAUSED (no warehouses)\n\n"
|
|
end
|
|
|
|
-- System Info
|
|
msg = msg .. "【SYSTEM INFO】\n"
|
|
msg = msg .. " Total Zones: " .. #zoneCaptureObjects .. "\n"
|
|
msg = msg .. " Active Garrisons: " .. (redGarrison.garrisoned + blueGarrison.garrisoned) .. "\n"
|
|
msg = msg .. " Total Active Units: " .. (redUnits.total + blueUnits.total) .. "\n\n"
|
|
|
|
msg = msg .. "═══════════════════════════════════════"
|
|
|
|
MESSAGE:New(msg, 45):ToCoalition(playerCoalition)
|
|
|
|
env.info("[DGB PLUGIN] System statistics displayed to coalition " .. playerCoalition)
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- INITIALIZATION
|
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
-- Get initial zone lists for each coalition
|
|
local redZones = GetZonesByCoalition(coalition.side.RED)
|
|
local blueZones = GetZonesByCoalition(coalition.side.BLUE)
|
|
|
|
-- 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
|
|
|
|
-- Validate spawn groups exist before creating spawners
|
|
local spawnGroups = {
|
|
{name = RED_INFANTRY_SPAWN_GROUP, label = "Red Infantry Spawn Group"},
|
|
{name = RED_ARMOR_SPAWN_GROUP, label = "Red Armor Spawn Group"},
|
|
{name = BLUE_INFANTRY_SPAWN_GROUP, label = "Blue Infantry Spawn Group"},
|
|
{name = BLUE_ARMOR_SPAWN_GROUP, label = "Blue Armor Spawn Group"}
|
|
}
|
|
|
|
for _, spawnGroup in ipairs(spawnGroups) do
|
|
local group = GROUP:FindByName(spawnGroup.name)
|
|
if group then
|
|
env.info(string.format("[DGB PLUGIN] %s '%s' found (OK)", spawnGroup.label, spawnGroup.name))
|
|
else
|
|
env.error(string.format("[DGB PLUGIN] ERROR: %s '%s' NOT FOUND! Create this group in mission editor as LATE ACTIVATE.", spawnGroup.label, spawnGroup.name))
|
|
end
|
|
end
|
|
|
|
-- Red Infantry Spawner
|
|
redInfantrySpawn = SPAWN:New(RED_INFANTRY_SPAWN_GROUP)
|
|
:InitRandomizeTemplate(redInfantryTemplates)
|
|
:InitLimit(INIT_RED_INFANTRY, MAX_RED_INFANTRY)
|
|
|
|
-- Red Armor Spawner
|
|
redArmorSpawn = SPAWN:New(RED_ARMOR_SPAWN_GROUP)
|
|
:InitRandomizeTemplate(redArmorTemplates)
|
|
:InitLimit(INIT_RED_ARMOR, MAX_RED_ARMOR)
|
|
|
|
-- Blue Infantry Spawner
|
|
blueInfantrySpawn = SPAWN:New(BLUE_INFANTRY_SPAWN_GROUP)
|
|
:InitRandomizeTemplate(blueInfantryTemplates)
|
|
:InitLimit(INIT_BLUE_INFANTRY, MAX_BLUE_INFANTRY)
|
|
|
|
-- Blue Armor Spawner
|
|
blueArmorSpawn = SPAWN:New(BLUE_ARMOR_SPAWN_GROUP)
|
|
:InitRandomizeTemplate(blueArmorTemplates)
|
|
:InitLimit(INIT_BLUE_ARMOR, MAX_BLUE_ARMOR)
|
|
|
|
-- Helper to schedule spawns per category so each uses its intended cadence.
|
|
local function ScheduleSpawner(spawnObject, getZonesFn, warehouses, baseFrequency, label, cadenceScalar)
|
|
local lastSpawnTime = 0
|
|
local checkInterval = 10 -- Check every 10 seconds if it's time to spawn
|
|
|
|
local function spawnCheck()
|
|
local currentTime = timer.getTime()
|
|
local spawnInterval = CalculateSpawnFrequency(warehouses, baseFrequency, cadenceScalar)
|
|
|
|
if not spawnInterval then
|
|
-- No warehouses alive - use recheck delay
|
|
spawnInterval = NO_WAREHOUSE_RECHECK_DELAY
|
|
if currentTime - lastSpawnTime >= spawnInterval then
|
|
env.info(string.format("[DGB PLUGIN] %s spawn paused (no warehouses alive, will recheck)", label))
|
|
lastSpawnTime = currentTime
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Check if enough time has passed to spawn
|
|
if currentTime - lastSpawnTime >= spawnInterval then
|
|
local friendlyZones = getZonesFn()
|
|
local zonesAvailable = #friendlyZones
|
|
|
|
if zonesAvailable > 0 then
|
|
local chosenZone = friendlyZones[math.random(zonesAvailable)]
|
|
local spawnedGroup = spawnObject:SpawnInZone(chosenZone, false)
|
|
|
|
-- Check if the spawn zone needs defenders and auto-elect if so
|
|
if spawnedGroup then
|
|
local zoneName = chosenZone:GetName()
|
|
if ZoneNeedsDefenders(zoneName) then
|
|
SCHEDULER:New(nil, function()
|
|
local grp = GROUP:FindByName(spawnedGroup:GetName())
|
|
if grp and grp:IsAlive() then
|
|
ElectDefender(grp, chosenZone, "spawn in under-garrisoned zone")
|
|
end
|
|
end, {}, 2) -- Delay 2 seconds to ensure group is fully initialized
|
|
end
|
|
end
|
|
|
|
lastSpawnTime = currentTime
|
|
else
|
|
env.info(string.format("[DGB PLUGIN] %s spawn skipped (no friendly zones)", label))
|
|
lastSpawnTime = currentTime -- Reset timer even if no zones available
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Single scheduler that runs continuously at fixed check interval
|
|
local scheduler = SCHEDULER:New(nil, spawnCheck, {}, math.random(5, 15), checkInterval)
|
|
return scheduler
|
|
end
|
|
|
|
-- Schedule spawns (each spawner now runs at its own configured cadence)
|
|
ScheduleSpawner(redInfantrySpawn, GetRedZones, redWarehouses, SPAWN_SCHED_RED_INFANTRY, "Red Infantry", RED_INFANTRY_CADENCE_SCALAR)
|
|
ScheduleSpawner(redArmorSpawn, GetRedZones, redWarehouses, SPAWN_SCHED_RED_ARMOR, "Red Armor", RED_ARMOR_CADENCE_SCALAR)
|
|
ScheduleSpawner(blueInfantrySpawn, GetBlueZones, blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY, "Blue Infantry", BLUE_INFANTRY_CADENCE_SCALAR)
|
|
ScheduleSpawner(blueArmorSpawn, GetBlueZones, blueWarehouses, SPAWN_SCHED_BLUE_ARMOR, "Blue Armor", BLUE_ARMOR_CADENCE_SCALAR)
|
|
|
|
-- Schedule warehouse marker updates
|
|
if ENABLE_WAREHOUSE_MARKERS then
|
|
SCHEDULER:New(nil, updateMarkPoints, {}, 10, UPDATE_MARK_POINTS_SCHED)
|
|
end
|
|
|
|
-- Schedule warehouse monitoring
|
|
if ENABLE_WAREHOUSE_STATUS_MESSAGES then
|
|
SCHEDULER:New(nil, MonitorWarehouses, {}, 30, WAREHOUSE_STATUS_MESSAGE_FREQUENCY)
|
|
end
|
|
|
|
-- Schedule task assignments
|
|
SCHEDULER:New(nil, AssignTasksToGroups, {}, 120, ASSIGN_TASKS_SCHED)
|
|
|
|
-- Add F10 menu for manual checks (using MenuManager if available)
|
|
if MenuManager then
|
|
-- Create coalition-specific menus under Mission Options
|
|
local blueMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Ground Battle")
|
|
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Check Warehouse Status", blueMenu, MonitorWarehouses)
|
|
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show System Statistics", blueMenu, function()
|
|
ShowSystemStatistics(coalition.side.BLUE)
|
|
end)
|
|
|
|
local redMenu = MenuManager.CreateCoalitionMenu(coalition.side.RED, "Ground Battle")
|
|
MENU_COALITION_COMMAND:New(coalition.side.RED, "Check Warehouse Status", redMenu, MonitorWarehouses)
|
|
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show System Statistics", redMenu, function()
|
|
ShowSystemStatistics(coalition.side.RED)
|
|
end)
|
|
else
|
|
-- Fallback to root-level mission menu
|
|
local missionMenu = MENU_MISSION:New("Ground Battle")
|
|
MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses)
|
|
MENU_MISSION_COMMAND:New("Show Blue Statistics", missionMenu, function()
|
|
ShowSystemStatistics(coalition.side.BLUE)
|
|
end)
|
|
MENU_MISSION_COMMAND:New("Show Red Statistics", missionMenu, function()
|
|
ShowSystemStatistics(coalition.side.RED)
|
|
end)
|
|
end
|
|
|
|
env.info("[DGB PLUGIN] Dynamic Ground Battle Plugin initialized successfully!")
|
|
env.info(string.format("[DGB PLUGIN] Zone garrison system: %d defenders per zone", DEFENDERS_PER_ZONE))
|
|
env.info(string.format("[DGB PLUGIN] Defender rotation: %s", ALLOW_DEFENDER_ROTATION and "ENABLED" or "DISABLED"))
|
|
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"))
|