mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Fixed a memory leak (scchulders built but not properly torn down.
This commit is contained in:
parent
a674c7a2fd
commit
924757919f
Binary file not shown.
@ -34,7 +34,10 @@ local blueCfg = {
|
||||
Zones = {
|
||||
PickupZones = { { name = 'S1', flag = 9001, activeWhen = 0 },
|
||||
{ name = "S2", flag = 9004, activeWhen = 0 },
|
||||
{ name = "S3", flag = 9005, activeWhen = 0 } },
|
||||
{ name = "S3", flag = 9005, activeWhen = 0 },
|
||||
{ name = "S4", flag = 9006, activeWhen = 0 },
|
||||
{ name = "S5", flag = 9007, activeWhen = 0 },
|
||||
{ name = "S6", flag = 9008, activeWhen = 0 } },
|
||||
--DropZones = { { name = 'BRAVO', flag = 9002, activeWhen = 0 } },
|
||||
--FOBZones = { { name = 'CHARLIE', flag = 9003, activeWhen = 0 } },
|
||||
--MASHZones = { { name = 'MASH Alpha', freq = '251.0 AM', radius = 500, flag = 9010, activeWhen = 0 } },
|
||||
@ -67,7 +70,8 @@ local redCfg = {
|
||||
{ name = "RedLoadZone2", flag = 9104, activeWhen = 0 },
|
||||
{ name = "RedLoadZone3", flag = 9105, activeWhen = 0 },
|
||||
{ name = "RedLoadZone4", flag = 9106, activeWhen = 0 },
|
||||
{ name = "RedLoadZone5", flag = 9107, activeWhen = 0 } },
|
||||
{ name = "RedLoadZone5", flag = 9107, activeWhen = 0 },
|
||||
{ name = "RedLoadZone6", flag = 9108, activeWhen = 0 } },
|
||||
--DropZones = { { name = 'ECHO', flag = 9102, activeWhen = 0 } },
|
||||
--FOBZones = { { name = 'FOXTROT', flag = 9103, activeWhen = 0 } },
|
||||
--MASHZones = { { name = 'MASH Bravo', freq = '252.0 AM', radius = 500, flag = 9111, activeWhen = 0 } },
|
||||
|
||||
334
Moose_CTLD_Pure/catalogs/Moose_CTLD_Catalog_LowCounts.lua
Normal file
334
Moose_CTLD_Pure/catalogs/Moose_CTLD_Catalog_LowCounts.lua
Normal file
@ -0,0 +1,334 @@
|
||||
-- CrateCatalog_CTLD_Extract.lua
|
||||
-- Auto-generated from CTLD.lua (Operation_Polar_Shield) spawnableCrates config
|
||||
-- Returns a table of crate definitions suitable for CTLD:MergeCatalog()
|
||||
-- Notes:
|
||||
-- - Each entry has keys: description/menu, dcsCargoType, required or requires (composite), side, category, build(point, headingDeg)
|
||||
-- - Single-unit entries spawn one unit by DCS type. Composite "SITE" entries spawn a multi-unit group approximating system components.
|
||||
|
||||
local function singleUnit(unitType)
|
||||
return function(point, headingDeg)
|
||||
local name = string.format('%s-%d', unitType, math.random(100000,999999))
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
return {
|
||||
visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={},
|
||||
units={ { type=unitType, name=name, x=point.x, y=point.z, heading=hdg } },
|
||||
name = 'CTLD_'..name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Build a single AIR unit that spawns in the air at a configured altitude/speed.
|
||||
-- Falls back gracefully to singleUnit behavior if config is unavailable/disabled.
|
||||
local function singleAirUnit(unitType)
|
||||
return function(point, headingDeg)
|
||||
local cfg = (rawget(_G, 'CTLD') and CTLD.Config and CTLD.Config.DroneAirSpawn) or nil
|
||||
if not cfg or cfg.Enabled == false then
|
||||
return singleUnit(unitType)(point, headingDeg)
|
||||
end
|
||||
|
||||
local name = string.format('%s-%d', unitType, math.random(100000,999999))
|
||||
local hdgDeg = headingDeg or 0
|
||||
local hdg = math.rad(hdgDeg)
|
||||
local alt = tonumber(cfg.AltitudeMeters) or 1200
|
||||
local spd = tonumber(cfg.SpeedMps) or 120
|
||||
|
||||
-- Create a tiny 2-point route to ensure forward flight at the chosen altitude.
|
||||
local function fwdOffset(px, pz, meters, headingRadians)
|
||||
return px + math.sin(headingRadians) * meters, pz + math.cos(headingRadians) * meters
|
||||
end
|
||||
local p1x, p1z = point.x, point.z
|
||||
local p2x, p2z = fwdOffset(point.x, point.z, 1000, hdg) -- 1 km ahead
|
||||
|
||||
local group = {
|
||||
visible=false,
|
||||
lateActivation=false,
|
||||
tasks={},
|
||||
task='CAS',
|
||||
route={
|
||||
points={
|
||||
{
|
||||
alt = alt, alt_type = 'BARO',
|
||||
type = 'Turning Point', action = 'Turning Point',
|
||||
x = p1x, y = p1z,
|
||||
speed = spd, ETA = 0, ETA_locked = false,
|
||||
task = {}
|
||||
},
|
||||
{
|
||||
alt = alt, alt_type = 'BARO',
|
||||
type = 'Turning Point', action = 'Turning Point',
|
||||
x = p2x, y = p2z,
|
||||
speed = spd, ETA = 0, ETA_locked = false,
|
||||
task = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
units={
|
||||
{
|
||||
type=unitType, name=name,
|
||||
x=p1x, y=p1z,
|
||||
heading=hdg,
|
||||
speed = spd,
|
||||
alt = alt, alt_type = 'BARO'
|
||||
}
|
||||
},
|
||||
name = 'CTLD_'..name
|
||||
}
|
||||
return group
|
||||
end
|
||||
end
|
||||
|
||||
local function multiUnits(units)
|
||||
-- units: array of { type, dx, dz }
|
||||
return function(point, headingDeg)
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
local function off(dx, dz) return { x = point.x + dx, z = point.z + dz } end
|
||||
local list = {}
|
||||
for i,u in ipairs(units) do
|
||||
local p = off(u.dx or 0, u.dz or 3*i)
|
||||
table.insert(list, {
|
||||
type = u.type, name = string.format('CTLD-%s-%d', u.type, math.random(100000,999999)),
|
||||
x = p.x, y = p.z, heading = hdg
|
||||
})
|
||||
end
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={}, units=list, name=string.format('CTLD_SITE_%d', math.random(100000,999999)) }
|
||||
end
|
||||
end
|
||||
|
||||
local BLUE = coalition.side.BLUE
|
||||
local RED = coalition.side.RED
|
||||
|
||||
local cat = {}
|
||||
|
||||
cat['BLUE_M1128_STRYKER_MGS_CRATE'] = { hidden=true, description='M1128 Stryker MGS crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M1128_STRYKER_MGS'] = { menuCategory='Combat Vehicles', menu='M1128 Stryker MGS', description='M1128 Stryker MGS', dcsCargoType='container_cargo', requires={ BLUE_M1128_STRYKER_MGS_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1128 Stryker MGS'), unitType='M1128 Stryker MGS', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['BLUE_M60A3_PATTON_CRATE'] = { hidden=true, description='M-60A3 Patton crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M60A3_PATTON'] = { menuCategory='Combat Vehicles', menu='M-60A3 Patton', description='M-60A3 Patton', dcsCargoType='container_cargo', requires={ BLUE_M60A3_PATTON_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-60'), unitType='M-60', MEDEVAC=true, salvageValue=3, crewSize=4 }
|
||||
cat['BLUE_HMMWV_TOW_CRATE'] = { hidden=true, description='Humvee - TOW crate', dcsCargoType='container_cargo', required=1, initialStock=36, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_HMMWV_TOW'] = { menuCategory='Combat Vehicles', menu='Humvee - TOW', description='Humvee - TOW', dcsCargoType='container_cargo', requires={ BLUE_HMMWV_TOW_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1045 HMMWV TOW'), unitType='M1045 HMMWV TOW', MEDEVAC=true, salvageValue=3, crewSize=2 }
|
||||
cat['BLUE_M1134_STRYKER_ATGM_CRATE']= { hidden=true, description='M1134 Stryker ATGM crate', dcsCargoType='container_cargo', required=1, initialStock=24, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M1134_STRYKER_ATGM'] = { menuCategory='Combat Vehicles', menu='M1134 Stryker ATGM', description='M1134 Stryker ATGM', dcsCargoType='container_cargo', requires={ BLUE_M1134_STRYKER_ATGM_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1134 Stryker ATGM'), unitType='M1134 Stryker ATGM', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['BLUE_LAV25_CRATE'] = { hidden=true, description='LAV-25 crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_LAV25'] = { menuCategory='Combat Vehicles', menu='LAV-25', description='LAV-25', dcsCargoType='container_cargo', requires={ BLUE_LAV25_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('LAV-25'), unitType='LAV-25', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['BLUE_M2A2_BRADLEY_CRATE'] = { hidden=true, description='M2A2 Bradley crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M2A2_BRADLEY'] = { menuCategory='Combat Vehicles', menu='M2A2 Bradley', description='M2A2 Bradley', dcsCargoType='container_cargo', requires={ BLUE_M2A2_BRADLEY_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-2 Bradley'), unitType='M-2 Bradley', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['BLUE_VAB_MEPHISTO_CRATE'] = { hidden=true, description='ATGM VAB Mephisto crate', dcsCargoType='container_cargo', required=1, initialStock=24, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_VAB_MEPHISTO'] = { menuCategory='Combat Vehicles', menu='ATGM VAB Mephisto', description='ATGM VAB Mephisto', dcsCargoType='container_cargo', requires={ BLUE_VAB_MEPHISTO_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('VAB_Mephisto'), unitType='VAB_Mephisto', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['BLUE_M1A2C_ABRAMS_CRATE'] = { hidden=true, description='M1A2C Abrams crate', dcsCargoType='container_cargo', required=1, initialStock=24, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M1A2C_ABRAMS'] = { menuCategory='Combat Vehicles', menu='M1A2C Abrams', description='M1A2C Abrams', dcsCargoType='container_cargo', requires={ BLUE_M1A2C_ABRAMS_CRATE=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1A2C_SEP_V3'), unitType='M1A2C_SEP_V3', MEDEVAC=true, salvageValue=3, crewSize=4 }
|
||||
|
||||
-- Combat Vehicles (RED)
|
||||
cat['RED_BTR82A_CRATE'] = { hidden=true, description='BTR-82A crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BTR82A'] = { menuCategory='Combat Vehicles', menu='BTR-82A', description='BTR-82A', dcsCargoType='container_cargo', requires={ RED_BTR82A_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR-82A'), unitType='BTR-82A', MEDEVAC=true, salvageValue=2, crewSize=3 }
|
||||
cat['RED_BRDM2_CRATE'] = { hidden=true, description='BRDM-2 crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BRDM2'] = { menuCategory='Combat Vehicles', menu='BRDM-2', description='BRDM-2', dcsCargoType='container_cargo', requires={ RED_BRDM2_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BRDM-2'), unitType='BRDM-2', MEDEVAC=true, salvageValue=2, crewSize=2 }
|
||||
cat['RED_BMP3_CRATE'] = { hidden=true, description='BMP-3 crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BMP3'] = { menuCategory='Combat Vehicles', menu='BMP-3', description='BMP-3', dcsCargoType='container_cargo', requires={ RED_BMP3_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BMP-3'), unitType='BMP-3', MEDEVAC=true, salvageValue=2, crewSize=3 }
|
||||
cat['RED_BMP2_CRATE'] = { hidden=true, description='BMP-2 crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BMP2'] = { menuCategory='Combat Vehicles', menu='BMP-2', description='BMP-2', dcsCargoType='container_cargo', requires={ RED_BMP2_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BMP-2'), unitType='BMP-2', MEDEVAC=true, salvageValue=2, crewSize=3 }
|
||||
cat['RED_BTR80_CRATE'] = { hidden=true, description='BTR-80 crate', dcsCargoType='container_cargo', required=1, initialStock=30, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BTR80'] = { menuCategory='Combat Vehicles', menu='BTR-80', description='BTR-80', dcsCargoType='container_cargo', requires={ RED_BTR80_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR-80'), unitType='BTR-80', MEDEVAC=true, salvageValue=2, crewSize=3 }
|
||||
cat['RED_T72B3_CRATE'] = { hidden=true, description='T-72B3 crate', dcsCargoType='container_cargo', required=1, initialStock=24, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_T72B3'] = { menuCategory='Combat Vehicles', menu='T-72B3', description='T-72B3', dcsCargoType='container_cargo', requires={ RED_T72B3_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('T-72B3'), unitType='T-72B3', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
cat['RED_T90M_CRATE'] = { hidden=true, description='T-90M crate', dcsCargoType='container_cargo', required=1, initialStock=24, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_T90M'] = { menuCategory='Combat Vehicles', menu='T-90M', description='T-90M', dcsCargoType='container_cargo', requires={ RED_T90M_CRATE=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('CHAP_T90M'), unitType='CHAP_T90M', MEDEVAC=true, salvageValue=3, crewSize=3 }
|
||||
|
||||
-- Support (BLUE)
|
||||
cat['BLUE_MRAP_JTAC'] = { menuCategory='Support', menu='MRAP - JTAC', description='JTAC MRAP', dcsCargoType='container_cargo', required=1, initialStock=12, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MaxxPro_MRAP'), MEDEVAC=true, salvageValue=1, crewSize=4, roles={'JTAC'}, jtac={ platform='ground' } }
|
||||
cat['BLUE_M818_AMMO'] = { menuCategory='Support', menu='M-818 Ammo Truck', description='M-818 Ammo Truck', dcsCargoType='container_cargo', required=1, initialStock=12, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M 818'), salvageValue=1, crewSize=2 }
|
||||
cat['BLUE_M978_TANKER'] = { menuCategory='Support', menu='M-978 Tanker', description='M-978 Tanker', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M978 HEMTT Tanker'), salvageValue=1, crewSize=2 }
|
||||
cat['BLUE_EWR_FPS117'] = { menuCategory='Support', menu='EWR Radar FPS-117', description='EWR Radar FPS-117', dcsCargoType='container_cargo', required=1, initialStock=6, side=BLUE, category=Group.Category.GROUND, build=singleUnit('FPS-117'), salvageValue=1, crewSize=3 }
|
||||
|
||||
-- Support (RED)
|
||||
cat['RED_TIGR_JTAC'] = { menuCategory='Support', menu='Tigr - JTAC', description='JTAC Tigr', dcsCargoType='container_cargo', required=1, initialStock=12, side=RED, category=Group.Category.GROUND, build=singleUnit('Tigr_233036'), MEDEVAC=true, salvageValue=1, crewSize=4, roles={'JTAC'}, jtac={ platform='ground' } }
|
||||
cat['RED_URAL4320_AMMO'] = { menuCategory='Support', menu='Ural-4320-31 Ammo Truck', description='Ural-4320-31 Ammo Truck', dcsCargoType='container_cargo', required=1, initialStock=12, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-4320-31'), salvageValue=1, crewSize=2 }
|
||||
cat['RED_ATZ10_TANKER'] = { menuCategory='Support', menu='ATZ-10 Refueler', description='ATZ-10 Refueler', dcsCargoType='container_cargo', required=1, initialStock=10, side=RED, category=Group.Category.GROUND, build=singleUnit('ATZ-10'), salvageValue=1, crewSize=2 }
|
||||
cat['RED_EWR_1L13'] = { menuCategory='Support', menu='EWR Radar 1L13', description='EWR Radar 1L13', dcsCargoType='container_cargo', required=1, initialStock=6, side=RED, category=Group.Category.GROUND, build=singleUnit('1L13 EWR'), salvageValue=1, crewSize=3 }
|
||||
|
||||
-- Artillery (BLUE)
|
||||
cat['BLUE_MLRS_CRATE'] = { hidden=true, description='MLRS crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_MLRS'] = { menuCategory='Artillery', menu='MLRS', description='MLRS', dcsCargoType='container_cargo', requires={ BLUE_MLRS_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MLRS'), salvageValue=2, crewSize=3 }
|
||||
cat['BLUE_SMERCH_CM_CRATE'] = { hidden=true, description='Smerch (CM) crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_SMERCH_CM'] = { menuCategory='Artillery', menu='Smerch_CM', description='Smerch (CM)', dcsCargoType='container_cargo', requires={ BLUE_SMERCH_CM_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch'), salvageValue=2, crewSize=3 }
|
||||
cat['BLUE_L118_105MM'] = { menuCategory='Artillery', menu='L118 Light Artillery 105mm', description='L118 105mm', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('L118_Unit'), salvageValue=1, crewSize=5 }
|
||||
cat['BLUE_SMERCH_HE_CRATE'] = { hidden=true, description='Smerch (HE) crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_SMERCH_HE'] = { menuCategory='Artillery', menu='Smerch_HE', description='Smerch (HE)', dcsCargoType='container_cargo', requires={ BLUE_SMERCH_HE_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch_HE'), salvageValue=2, crewSize=3 }
|
||||
cat['BLUE_M109_CRATE'] = { hidden=true, description='M-109 crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M109'] = { menuCategory='Artillery', menu='M-109', description='M-109', dcsCargoType='container_cargo', requires={ BLUE_M109_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-109'), salvageValue=2, crewSize=4 }
|
||||
|
||||
-- Artillery (RED)
|
||||
cat['RED_GVOZDIKA_CRATE'] = { hidden=true, description='SAU Gvozdika crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_GVOZDika'] = { menuCategory='Artillery', menu='SAU Gvozdika', description='SAU Gvozdika', dcsCargoType='container_cargo', requires={ RED_GVOZDIKA_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Gvozdika'), salvageValue=2, crewSize=3 }
|
||||
cat['RED_2S19_MSTA_CRATE'] = { hidden=true, description='SPH 2S19 Msta crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_2S19_MSTA'] = { menuCategory='Artillery', menu='SPH 2S19 Msta', description='SPH 2S19 Msta', dcsCargoType='container_cargo', requires={ RED_2S19_MSTA_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Msta'), salvageValue=2, crewSize=4 }
|
||||
cat['RED_URAGAN_BM27_CRATE'] = { hidden=true, description='Uragan BM-27 crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_URAGAN_BM27'] = { menuCategory='Artillery', menu='Uragan_BM-27', description='Uragan BM-27', dcsCargoType='container_cargo', requires={ RED_URAGAN_BM27_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('Uragan_BM-27'), salvageValue=2, crewSize=3 }
|
||||
cat['RED_BM21_GRAD_CRATE'] = { hidden=true, description='BM-21 Grad crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_BM21_GRAD'] = { menuCategory='Artillery', menu='BM-21 Grad Ural', description='BM-21 Grad Ural', dcsCargoType='container_cargo', requires={ RED_BM21_GRAD_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('Grad-URAL'), salvageValue=2, crewSize=3 }
|
||||
cat['RED_PLZ05_CRATE'] = { hidden=true, description='PLZ-05 crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_PLZ05'] = { menuCategory='Artillery', menu='PLZ-05 Mobile Artillery', description='PLZ-05', dcsCargoType='container_cargo', requires={ RED_PLZ05_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('PLZ05'), salvageValue=2, crewSize=4 }
|
||||
|
||||
-- AAA (BLUE)
|
||||
cat['BLUE_GEPARD'] = { menuCategory='AAA', menu='Gepard AAA', description='Gepard AAA', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Gepard'), salvageValue=1, crewSize=3 }
|
||||
cat['BLUE_CRAM'] = { menuCategory='AAA', menu='LPWS C-RAM', description='LPWS C-RAM', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('HEMTT_C-RAM_Phalanx'), salvageValue=1, crewSize=2 }
|
||||
cat['BLUE_VULCAN_M163'] = { menuCategory='AAA', menu='SPAAA Vulcan M163', description='Vulcan M163', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Vulcan'), salvageValue=1, crewSize=2 }
|
||||
cat['BLUE_BOFORS40'] = { menuCategory='AAA', menu='Bofors 40mm', description='Bofors 40mm', dcsCargoType='container_cargo', required=1, initialStock=12, side=BLUE, category=Group.Category.GROUND, build=singleUnit('bofors40'), salvageValue=1, crewSize=4 }
|
||||
|
||||
-- AAA (RED)
|
||||
cat['RED_URAL_ZU23'] = { menuCategory='AAA', menu='Ural-375 ZU-23', description='Ural-375 ZU-23', dcsCargoType='container_cargo', required=1, initialStock=12, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-375 ZU-23'), salvageValue=1, crewSize=3 }
|
||||
cat['RED_SHILKA'] = { menuCategory='AAA', menu='ZSU-23-4 Shilka', description='ZSU-23-4 Shilka', dcsCargoType='container_cargo', required=1, initialStock=10, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU-23-4 Shilka'), salvageValue=1, crewSize=3 }
|
||||
cat['RED_ZSU57_2'] = { menuCategory='AAA', menu='ZSU_57_2', description='ZSU_57_2', dcsCargoType='container_cargo', required=1, initialStock=10, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU_57_2'), salvageValue=1, crewSize=3 }
|
||||
|
||||
cat['BLUE_M1097_AVENGER_CRATE'] = { hidden=true, description='M1097 Avenger crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M1097_AVENGER'] = { menuCategory='SAM short range', menu='M1097 Avenger', description='M1097 Avenger', dcsCargoType='container_cargo', requires={ BLUE_M1097_AVENGER_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1097 Avenger') }
|
||||
cat['BLUE_M48_CHAPARRAL_CRATE'] = { hidden=true, description='M48 Chaparral crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_M48_CHAPARRAL'] = { menuCategory='SAM short range', menu='M48 Chaparral', description='M48 Chaparral', dcsCargoType='container_cargo', requires={ BLUE_M48_CHAPARRAL_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M48 Chaparral') }
|
||||
cat['BLUE_ROLAND_ADS_CRATE'] = { hidden=true, description='Roland ADS crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=BLUE, category=Group.Category.GROUND }
|
||||
cat['BLUE_ROLAND_ADS'] = { menuCategory='SAM short range', menu='Roland ADS', description='Roland ADS', dcsCargoType='container_cargo', requires={ BLUE_ROLAND_ADS_CRATE=2 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Roland ADS') }
|
||||
cat['BLUE_M6_LINEBACKER'] = { menuCategory='SAM short range', menu='M6 Linebacker', description='M6 Linebacker', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M6 Linebacker') }
|
||||
cat['BLUE_RAPIER_LN'] = { menuCategory='SAM short range', menu='Rapier Launcher', description='Rapier Launcher', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_launcher') }
|
||||
cat['BLUE_RAPIER_SR'] = { menuCategory='SAM short range', menu='Rapier SR', description='Rapier SR', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_blindfire_radar') }
|
||||
cat['BLUE_RAPIER_TR'] = { menuCategory='SAM short range', menu='Rapier Tracker', description='Rapier Tracker', dcsCargoType='container_cargo', required=1, initialStock=10, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_optical_tracker_unit') }
|
||||
cat['BLUE_RAPIER_SITE'] = { menuCategory='SAM short range', menu='Rapier - All crates', description='Rapier Site', dcsCargoType='container_cargo', requires={ BLUE_RAPIER_LN=1, BLUE_RAPIER_SR=1, BLUE_RAPIER_TR=1 }, initialStock=0, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='rapier_fsa_launcher'}, {type='rapier_fsa_blindfire_radar', dx=12, dz=6}, {type='rapier_fsa_optical_tracker_unit', dx=-12, dz=6} }) }
|
||||
|
||||
-- SAM short range (RED)
|
||||
cat['RED_OSA_9K33_CRATE'] = { hidden=true, description='9K33 Osa crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_OSA_9K33'] = { menuCategory='SAM short range', menu='9K33 Osa', description='9K33 Osa', dcsCargoType='container_cargo', requires={ RED_OSA_9K33_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('Osa 9A33 ln') }
|
||||
cat['RED_STRELA1_9P31_CRATE'] = { hidden=true, description='9P31 Strela-1 crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_STRELA1_9P31'] = { menuCategory='SAM short range', menu='9P31 Strela-1', description='9P31 Strela-1', dcsCargoType='container_cargo', requires={ RED_STRELA1_9P31_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-1 9P31') }
|
||||
cat['RED_TUNGUSKA_2S6_CRATE'] = { hidden=true, description='2K22 Tunguska crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_TUNGUSKA_2S6'] = { menuCategory='SAM short range', menu='2K22 Tunguska', description='2K22 Tunguska', dcsCargoType='container_cargo', requires={ RED_TUNGUSKA_2S6_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('2S6 Tunguska') }
|
||||
cat['RED_STRELA10M3_CRATE'] = { hidden=true, description='SA-13 Strela-10M3 crate', dcsCargoType='container_cargo', required=1, initialStock=16, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_STRELA10M3'] = { menuCategory='SAM short range', menu='SA-13 Strela-10M3', description='SA-13 Strela-10M3', dcsCargoType='container_cargo', requires={ RED_STRELA10M3_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-10M3') }
|
||||
-- HQ-7 components and site
|
||||
cat['RED_HQ7_LN_CRATE'] = { hidden=true, description='HQ-7 Launcher crate', dcsCargoType='container_cargo', required=1, initialStock=20, side=RED, category=Group.Category.GROUND }
|
||||
cat['RED_HQ7_LN'] = { menuCategory='SAM short range', menu='HQ-7_Launcher', description='HQ-7 Launcher', dcsCargoType='container_cargo', requires={ RED_HQ7_LN_CRATE=2 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_LN_SP') }
|
||||
cat['RED_HQ7_STR'] = { menuCategory='SAM short range', menu='HQ-7_STR_SP', description='HQ-7 STR', dcsCargoType='container_cargo', required=1, initialStock=10, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_STR_SP') }
|
||||
cat['RED_HQ7_SITE'] = { menuCategory='SAM short range', menu='HQ-7 - All crates', description='HQ-7 Site', dcsCargoType='container_cargo', requires={ RED_HQ7_LN=1, RED_HQ7_STR=1 }, initialStock=0, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='HQ-7_LN_SP'}, {type='HQ-7_STR_SP', dx=10, dz=8} }) }
|
||||
|
||||
-- SAM mid range (BLUE) HAWK + NASAMS
|
||||
cat['BLUE_HAWK_LN'] = { menuCategory='SAM mid range', menu='HAWK Launcher', description='HAWK Launcher', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk ln') }
|
||||
cat['BLUE_HAWK_SR'] = { menuCategory='SAM mid range', menu='HAWK Search Radar', description='HAWK SR', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk sr') }
|
||||
cat['BLUE_HAWK_TR'] = { menuCategory='SAM mid range', menu='HAWK Track Radar', description='HAWK TR', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk tr') }
|
||||
cat['BLUE_HAWK_PCP'] = { menuCategory='SAM mid range', menu='HAWK PCP', description='HAWK PCP', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk pcp') }
|
||||
cat['BLUE_HAWK_CWAR'] = { menuCategory='SAM mid range', menu='HAWK CWAR', description='HAWK CWAR', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk cwar') }
|
||||
cat['BLUE_HAWK_SITE'] = { menuCategory='SAM mid range', menu='HAWK - All crates', description='HAWK Site', dcsCargoType='container_cargo', requires={ BLUE_HAWK_LN=1, BLUE_HAWK_SR=1, BLUE_HAWK_TR=1, BLUE_HAWK_PCP=1, BLUE_HAWK_CWAR=1 }, initialStock=0, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Hawk ln'}, {type='Hawk sr', dx=12, dz=8}, {type='Hawk tr', dx=-12, dz=8}, {type='Hawk pcp', dx=18, dz=12}, {type='Hawk cwar', dx=-18, dz=12} }) }
|
||||
|
||||
-- HAWK site repair/augment (adds +1 launcher, repairs site by respawn)
|
||||
cat['BLUE_HAWK_REPAIR'] = { menuCategory='SAM mid range', menu='HAWK Repair/Launcher +1', description='HAWK Repair (adds launcher)', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, isRepair=true, build=function(point, headingDeg)
|
||||
-- Build is handled specially in CTLD:BuildSpecificAtGroup for isRepair entries
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
|
||||
cat['BLUE_NASAMS_LN'] = { menuCategory='SAM mid range', menu='NASAMS Launcher 120C', description='NASAMS LN 120C', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_LN_C') }
|
||||
cat['BLUE_NASAMS_RADAR'] = { menuCategory='SAM mid range', menu='NASAMS Search/Track Radar', description='NASAMS Radar', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Radar_MPQ64F1') }
|
||||
cat['BLUE_NASAMS_CP'] = { menuCategory='SAM mid range', menu='NASAMS Command Post', description='NASAMS CP', dcsCargoType='container_cargo', required=1, initialStock=8, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Command_Post') }
|
||||
cat['BLUE_NASAMS_SITE'] = { menuCategory='SAM mid range', menu='NASAMS - All crates', description='NASAMS Site', dcsCargoType='container_cargo', requires={ BLUE_NASAMS_LN=1, BLUE_NASAMS_RADAR=1, BLUE_NASAMS_CP=1 }, initialStock=0, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='NASAMS_LN_C'}, {type='NASAMS_Radar_MPQ64F1', dx=12, dz=8}, {type='NASAMS_Command_Post', dx=-12, dz=8} }) }
|
||||
|
||||
-- SAM mid range (RED) KUB
|
||||
cat['RED_KUB_LN'] = { menuCategory='SAM mid range', menu='KUB Launcher', description='KUB Launcher', dcsCargoType='container_cargo', required=1, initialStock=8, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 2P25 ln') }
|
||||
cat['RED_KUB_RADAR'] = { menuCategory='SAM mid range', menu='KUB Radar', description='KUB Radar', dcsCargoType='container_cargo', required=1, initialStock=8, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 1S91 str') }
|
||||
cat['RED_KUB_SITE'] = { menuCategory='SAM mid range', menu='KUB - All crates', description='KUB Site', dcsCargoType='container_cargo', requires={ RED_KUB_LN=1, RED_KUB_RADAR=1 }, initialStock=0, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Kub 2P25 ln'}, {type='Kub 1S91 str', dx=12, dz=8} }) }
|
||||
|
||||
-- KUB site repair/augment (adds +1 launcher, repairs site by respawn)
|
||||
cat['RED_KUB_REPAIR'] = { menuCategory='SAM mid range', menu='KUB Repair/Launcher +1', description='KUB Repair (adds launcher)', dcsCargoType='container_cargo', required=1, initialStock=8, side=RED, category=Group.Category.GROUND, isRepair=true, build=function(point, headingDeg)
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
|
||||
-- SAM long range (BLUE) Patriot
|
||||
cat['BLUE_PATRIOT_LN'] = { menuCategory='SAM long range', menu='Patriot Launcher', description='Patriot Launcher', dcsCargoType='container_cargo', required=1, initialStock=6, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ln') }
|
||||
cat['BLUE_PATRIOT_RADAR'] = { menuCategory='SAM long range', menu='Patriot Radar', description='Patriot Radar', dcsCargoType='container_cargo', required=1, initialStock=6, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot str') }
|
||||
cat['BLUE_PATRIOT_ECS'] = { menuCategory='SAM long range', menu='Patriot ECS', description='Patriot ECS', dcsCargoType='container_cargo', required=1, initialStock=6, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ECS') }
|
||||
cat['BLUE_PATRIOT_SITE'] = { menuCategory='SAM long range', menu='Patriot - All crates', description='Patriot Site', dcsCargoType='container_cargo', requires={ BLUE_PATRIOT_LN=1, BLUE_PATRIOT_RADAR=1, BLUE_PATRIOT_ECS=1 }, initialStock=0, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Patriot ln'}, {type='Patriot str', dx=14, dz=10}, {type='Patriot ECS', dx=-14, dz=10} }) }
|
||||
|
||||
-- Patriot site repair/augment (adds +1 launcher, repairs site by respawn)
|
||||
cat['BLUE_PATRIOT_REPAIR'] = { menuCategory='SAM long range', menu='Patriot Repair/Launcher +1', description='Patriot Repair (adds launcher)', dcsCargoType='container_cargo', required=1, initialStock=6, side=BLUE, category=Group.Category.GROUND, isRepair=true, build=function(point, headingDeg)
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
|
||||
-- SAM long range (RED) BUK
|
||||
cat['RED_BUK_LN'] = { menuCategory='SAM long range', menu='BUK Launcher', description='BUK Launcher', dcsCargoType='container_cargo', required=1, initialStock=6, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk LN 9A310M1') }
|
||||
cat['RED_BUK_SR'] = { menuCategory='SAM long range', menu='BUK Search Radar', description='BUK Search Radar', dcsCargoType='container_cargo', required=1, initialStock=6, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk SR 9S18M1') }
|
||||
cat['RED_BUK_CC'] = { menuCategory='SAM long range', menu='BUK CC Radar', description='BUK CC Radar', dcsCargoType='container_cargo', required=1, initialStock=6, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk CC 9S470M1') }
|
||||
cat['RED_BUK_SITE'] = { menuCategory='SAM long range', menu='BUK - All crates', description='BUK Site', dcsCargoType='container_cargo', requires={ RED_BUK_LN=1, RED_BUK_SR=1, RED_BUK_CC=1 }, initialStock=0, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='SA-11 Buk LN 9A310M1'}, {type='SA-11 Buk SR 9S18M1', dx=12, dz=8}, {type='SA-11 Buk CC 9S470M1', dx=-12, dz=8} }) }
|
||||
|
||||
-- BUK site repair/augment (adds +1 launcher, repairs site by respawn)
|
||||
cat['RED_BUK_REPAIR'] = { menuCategory='SAM long range', menu='BUK Repair/Launcher +1', description='BUK Repair (adds launcher)', dcsCargoType='container_cargo', required=1, initialStock=6, side=RED, category=Group.Category.GROUND, isRepair=true, build=function(point, headingDeg)
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
|
||||
-- Drones (JTAC)
|
||||
cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTAC', description='MQ-9 JTAC', dcsCargoType='container_cargo', required=1, initialStock=3, side=BLUE, category=Group.Category.AIRPLANE, build=singleAirUnit('MQ-9 Reaper'), roles={'JTAC'}, jtac={ platform='air' } }
|
||||
cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, initialStock=3, side=RED, category=Group.Category.AIRPLANE, build=singleAirUnit('WingLoong-I'), roles={'JTAC'}, jtac={ platform='air' } }
|
||||
|
||||
-- FOB crates (Support) — three small crates build a FOB site
|
||||
cat['FOB_SMALL'] = { hidden=true, description='FOB small crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
|
||||
-- spawns a harmless placeholder truck for visibility; consumed by FOB_SITE build
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
cat['FOB_SITE'] = { menuCategory='Support', menu='FOB Crates - All', description='FOB Site', isFOB=true, dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, initialStock=0, side=nil, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='HEMTT TFFT'}, {type='Ural-375 PBU', dx=10, dz=8}, {type='Ural-375', dx=-10, dz=8} }) }
|
||||
|
||||
-- Mobile MASH (Support) — three crates build a Mobile MASH unit
|
||||
cat['MOBILE_MASH_SMALL'] = { hidden=true, description='Mobile MASH crate', dcsCargoType='container_cargo', required=1, initialStock=6, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
|
||||
-- spawns placeholder truck for visibility; consumed by MOBILE_MASH build
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
cat['BLUE_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Blue Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-113') }
|
||||
cat['RED_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Red Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR_D') }
|
||||
|
||||
-- =========================
|
||||
-- Troop Type Definitions
|
||||
-- =========================
|
||||
-- These define the composition of troop squads for Load/Unload Troops (NOT crates)
|
||||
-- Structure: { label, size, unitsBlue, unitsRed, units (fallback) }
|
||||
local troops = {}
|
||||
|
||||
-- Assault Squad: general-purpose rifles/MG
|
||||
troops['AS'] = {
|
||||
label = 'Assault Squad',
|
||||
size = 8,
|
||||
unitsBlue = { 'Soldier M4', 'Soldier M249' },
|
||||
unitsRed = { 'Infantry AK', 'Infantry AK ver3' },
|
||||
units = { 'Infantry AK' },
|
||||
}
|
||||
|
||||
-- MANPADS Team: Anti-air element
|
||||
troops['AA'] = {
|
||||
label = 'MANPADS Team',
|
||||
size = 4,
|
||||
unitsBlue = { 'Soldier stinger', 'Stinger comm' },
|
||||
unitsRed = { 'SA-18 Igla-S manpad', 'SA-18 Igla comm' },
|
||||
units = { 'Infantry AK' },
|
||||
}
|
||||
|
||||
-- AT Team: Anti-tank element
|
||||
troops['AT'] = {
|
||||
label = 'AT Team',
|
||||
size = 4,
|
||||
unitsBlue = { 'Soldier RPG', 'Soldier RPG' },
|
||||
unitsRed = { 'Soldier RPG', 'Soldier RPG' },
|
||||
units = { 'Infantry AK' },
|
||||
}
|
||||
|
||||
-- Mortar Team: Indirect fire element
|
||||
troops['AR'] = {
|
||||
label = 'Mortar Team',
|
||||
size = 4,
|
||||
unitsBlue = { '2B11 mortar' },
|
||||
unitsRed = { '2B11 mortar' },
|
||||
units = { '2B11 mortar' },
|
||||
}
|
||||
|
||||
-- Export troop types
|
||||
_CTLD_TROOP_TYPES = troops
|
||||
|
||||
-- Also export as a global for mission setups that load via DO SCRIPT FILE (no return capture)
|
||||
_CTLD_EXTRACTED_CATALOG = cat
|
||||
return cat
|
||||
@ -9,6 +9,7 @@
|
||||
- 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
|
||||
@ -41,8 +42,12 @@
|
||||
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
|
||||
- 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
|
||||
|
||||
@ -72,6 +77,10 @@
|
||||
-- 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)
|
||||
|
||||
@ -80,24 +89,28 @@ local ENABLE_WAREHOUSE_MARKERS = true -- Enable/disable warehouse map markers (
|
||||
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 = 1800 -- Base spawn frequency for Red Infantry (seconds)
|
||||
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 = 300 -- Base spawn frequency for Red Armor (seconds)
|
||||
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 = 1800 -- Base spawn frequency for Blue Infantry (seconds)
|
||||
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 = 300 -- Base spawn frequency for Blue Armor (seconds)
|
||||
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)
|
||||
|
||||
@ -264,6 +277,14 @@ env.info("[DGB PLUGIN] Found " .. #zoneCaptureObjects .. " zones from DualCoalit
|
||||
-- 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()
|
||||
@ -395,11 +416,172 @@ local function IsInfantryGroup(group)
|
||||
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)
|
||||
@ -411,9 +593,23 @@ local function AssignTasks(group, currentZoneCapture)
|
||||
local groupCoordinate = group:GetCoordinate()
|
||||
local currentZone = currentZoneCapture and currentZoneCapture:GetZone() or nil
|
||||
|
||||
-- If the group is sitting inside a friendly zone that is currently under attack,
|
||||
-- keep them local so they fight for the objective instead of leaving it exposed.
|
||||
if currentZoneCapture and currentZone and currentZoneCapture.GetCoalition and currentZoneCapture:GetCoalition() == groupCoalition then
|
||||
-- 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()))
|
||||
@ -454,6 +650,7 @@ 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
|
||||
@ -474,18 +671,23 @@ local function AssignTasksToGroups()
|
||||
end
|
||||
|
||||
if inFriendlyZone then
|
||||
-- Skip infantry if movement is disabled
|
||||
if IsInfantryGroup(group) and not MOVING_INFANTRY_PATROLS 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.", tasksAssigned))
|
||||
env.info(string.format("[DGB PLUGIN] Task assignment complete. %d groups tasked (%d defenders).", tasksAssigned, defendersActive))
|
||||
end
|
||||
|
||||
-- Function to monitor and announce warehouse status
|
||||
@ -496,16 +698,184 @@ local function MonitorWarehouses()
|
||||
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()
|
||||
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
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -573,38 +943,55 @@ blueArmorSpawn = SPAWN:New(BLUE_ARMOR_SPAWN_GROUP)
|
||||
|
||||
-- Helper to schedule spawns per category so each uses its intended cadence.
|
||||
local function ScheduleSpawner(spawnObject, getZonesFn, warehouses, baseFrequency, label, cadenceScalar)
|
||||
local scheduler
|
||||
local lastSpawnTime = 0
|
||||
local checkInterval = 10 -- Check every 10 seconds if it's time to spawn
|
||||
|
||||
local function spawnCycle()
|
||||
local nextInterval = CalculateSpawnFrequency(warehouses, baseFrequency, cadenceScalar)
|
||||
local function spawnCheck()
|
||||
local currentTime = timer.getTime()
|
||||
local spawnInterval = CalculateSpawnFrequency(warehouses, baseFrequency, cadenceScalar)
|
||||
|
||||
if not nextInterval then
|
||||
env.info(string.format("[DGB PLUGIN] %s spawn paused (no warehouses alive)", label))
|
||||
if scheduler then
|
||||
scheduler:Stop()
|
||||
scheduler:Start(NO_WAREHOUSE_RECHECK_DELAY, NO_WAREHOUSE_RECHECK_DELAY)
|
||||
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
|
||||
|
||||
local friendlyZones = getZonesFn()
|
||||
local zonesAvailable = #friendlyZones
|
||||
-- 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)]
|
||||
spawnObject:SpawnInZone(chosenZone, false)
|
||||
else
|
||||
env.info(string.format("[DGB PLUGIN] %s spawn skipped (no friendly zones)", label))
|
||||
end
|
||||
|
||||
if scheduler then
|
||||
scheduler:Stop()
|
||||
scheduler:Start(nextInterval, nextInterval)
|
||||
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
|
||||
|
||||
local initialFrequency = baseFrequency * (cadenceScalar or 1)
|
||||
scheduler = SCHEDULER:New(nil, spawnCycle, {}, math.random(5, 15), initialFrequency)
|
||||
-- Single scheduler that runs continuously at fixed check interval
|
||||
local scheduler = SCHEDULER:New(nil, spawnCheck, {}, math.random(5, 15), checkInterval)
|
||||
return scheduler
|
||||
end
|
||||
|
||||
@ -620,7 +1007,9 @@ if ENABLE_WAREHOUSE_MARKERS then
|
||||
end
|
||||
|
||||
-- Schedule warehouse monitoring
|
||||
SCHEDULER:New(nil, MonitorWarehouses, {}, 30, 120)
|
||||
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)
|
||||
@ -630,15 +1019,29 @@ 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"))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user