mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Too many changes to list accross to many files. Save more often dummy. :)
This commit is contained in:
552
Moose_CTLD_Pure/Moose_CTLD.lua
Normal file
552
Moose_CTLD_Pure/Moose_CTLD.lua
Normal file
@@ -0,0 +1,552 @@
|
||||
-- Moose_CTLD.lua
|
||||
-- Pure-MOOSE, template-free CTLD-style logistics & troop transport
|
||||
-- Drop-in script: no MIST, no mission editor templates required
|
||||
-- Dependencies: Moose.lua must be loaded before this script
|
||||
-- Author: Copilot (generated)
|
||||
|
||||
-- Contract
|
||||
-- Inputs: Config table or defaults. No ME templates needed. Zones may be named ME trigger zones or provided via coordinates in config.
|
||||
-- Outputs: F10 menus for helo/transport groups; crate spawning/building; troop load/unload; optional JTAC hookup (via FAC module);
|
||||
-- Error modes: missing Moose -> abort; unknown crate key -> message; spawn blocked in enemy airbase; zone missing -> message.
|
||||
|
||||
if not _G.Moose or not _G.BASE then
|
||||
env.info('[Moose_CTLD] Moose not detected (BASE class missing). Ensure Moose.lua is loaded before Moose_CTLD.lua')
|
||||
end
|
||||
|
||||
local CTLD = {}
|
||||
CTLD.__index = CTLD
|
||||
|
||||
-- =========================
|
||||
-- Defaults and State
|
||||
-- =========================
|
||||
CTLD.Version = '0.1.0-alpha'
|
||||
|
||||
CTLD.Config = {
|
||||
CoalitionSide = coalition.side.BLUE, -- default coalition this instance serves (menus created for this side)
|
||||
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
|
||||
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','Ka-50','Ka-50_3','AH-64D_BLK_II','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
|
||||
},
|
||||
UseGroupMenus = true, -- if true, F10 menus per player group; otherwise coalition-wide
|
||||
UseCategorySubmenus = true, -- if true, organize crate requests by category submenu (menuCategory)
|
||||
BuildRadius = 60, -- meters around build point to collect crates
|
||||
CrateLifetime = 3600, -- seconds before crates auto-clean
|
||||
MessageDuration = 15, -- seconds for on-screen messages
|
||||
Debug = false,
|
||||
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
|
||||
|
||||
Zones = { -- Optional: supply by name (ME trigger zones) or define coordinates inline
|
||||
PickupZones = {
|
||||
-- examples:
|
||||
-- { name = 'PICKUP_BLUE_MAIN' },
|
||||
-- { name = 'Pickup-West', smoke = trigger.smokeColor.Green },
|
||||
-- { coord = { x = 12345, y = 0, z = 67890 }, radius = 150, name = 'ScriptPickup1' },
|
||||
},
|
||||
DropZones = {
|
||||
-- { name = 'DROP_BLUE_1' },
|
||||
},
|
||||
FOBZones = {
|
||||
-- optional: where FOB crates can unpack to spawn FARP/FOB assets
|
||||
},
|
||||
},
|
||||
|
||||
-- Crate catalog: key -> crate properties and build recipe
|
||||
-- No ME templates; unit compositions are defined directly here.
|
||||
CrateCatalog = {
|
||||
-- Example: MANPADS team requiring 2 crates
|
||||
MANPADS = {
|
||||
description = '2x Crates -> MANPADS team',
|
||||
weight = 120, -- affects sling/limits only informationally (no physics in script)
|
||||
dcsCargoType = 'uh1h_cargo', -- static cargo type (can adjust per DCS version); user-tunable
|
||||
required = 2, -- number of crates to assemble
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local u1 = { type = 'Soldier stinger', name = string.format('CTLD-MANPADS-%d', math.random(100000,999999)),
|
||||
x = point.x, y = point.z, heading = math.rad(headingDeg or 0) }
|
||||
local group = {
|
||||
visible = false, lateActivation = false, tasks = {}, task = 'Ground Nothing',
|
||||
units = { u1 }, route = { }, name = string.format('CTLD_MANPADS_%d', math.random(100000,999999))
|
||||
}
|
||||
return group
|
||||
end,
|
||||
},
|
||||
-- Example: AAA site needing 3 crates
|
||||
AAA = {
|
||||
description = '3x Crates -> ZU-23 site',
|
||||
weight = 400,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 3,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
local function offset(dx, dz) return { x = point.x + dx, z = point.z + dz } end
|
||||
local units = {
|
||||
{ type='ZU-23 Emplacement', name=string.format('CTLD-ZU23-%d', math.random(100000,999999)), x=point.x, y=point.z, heading=hdg },
|
||||
{ type='Ural-375', name=string.format('CTLD-TRK-%d', math.random(100000,999999)), x=offset(15, 12).x, y=offset(15, 12).z, heading=hdg },
|
||||
{ type='Infantry AK', name=string.format('CTLD-INF-%d', math.random(100000,999999)), x=offset(-12,-15).x, y=offset(-12,-15).z, heading=hdg },
|
||||
}
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_AAA_%d', math.random(100000,999999)) }
|
||||
end,
|
||||
},
|
||||
-- Example: FARP/FOB build from 4 crates (spawns helipads + support statics/vehicles)
|
||||
FOB = {
|
||||
description = '4x Crates -> FARP/FOB',
|
||||
weight = 500,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 4,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local heading = math.rad(headingDeg or 0)
|
||||
-- Spawn statics that provide FARP services
|
||||
local function addStatic(typeName, dx, dz, nameSuffix)
|
||||
local p = { x = point.x + dx, z = point.z + dz }
|
||||
local st = {
|
||||
name = string.format('CTLD-FOB-%s-%d', nameSuffix, math.random(100000,999999)),
|
||||
type = typeName,
|
||||
x = p.x, y = p.z,
|
||||
heading = heading,
|
||||
}
|
||||
coalition.addStaticObject(coalition.side.BLUE, st)
|
||||
end
|
||||
-- Common FARP layout
|
||||
addStatic('FARP', 0, 0, 'PAD')
|
||||
addStatic('FARP Ammo Dump Coating', 15, 10, 'AMMO')
|
||||
addStatic('FARP Fuel Depot', -15, 10, 'FUEL')
|
||||
addStatic('FARP Tent', 10, -12, 'TENT1')
|
||||
addStatic('FARP Tent', -10, -12, 'TENT2')
|
||||
-- Ground support vehicles to enable rearm/refuel/repair
|
||||
local units = {
|
||||
{ type='HEMTT TFFT', name=string.format('CTLD-FOB-FUEL-%d', math.random(100000,999999)), x=point.x + 20, y=point.z + 15, heading=heading },
|
||||
{ type='Ural-375 PBU', name=string.format('CTLD-FOB-REPAIR-%d', math.random(100000,999999)), x=point.x - 20, y=point.z + 15, heading=heading },
|
||||
{ type='Ural-375', name=string.format('CTLD-FOB-AMMO-%d', math.random(100000,999999)), x=point.x, y=point.z - 18, heading=heading },
|
||||
}
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_FOB_%d', math.random(100000,999999)) }
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- Internal state tables
|
||||
CTLD._instances = CTLD._instances or {}
|
||||
CTLD._crates = {} -- [crateName] = { key, zone, side, spawnTime, point }
|
||||
CTLD._troopsLoaded = {} -- [groupName] = { count, typeKey }
|
||||
|
||||
-- =========================
|
||||
-- Utilities
|
||||
-- =========================
|
||||
local function _isIn(list, value)
|
||||
for _,v in ipairs(list or {}) do if v == value then return true end end
|
||||
return false
|
||||
end
|
||||
|
||||
local function _msgGroup(group, text, t)
|
||||
if not group then return end
|
||||
MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToGroup(group)
|
||||
end
|
||||
|
||||
local function _msgCoalition(side, text, t)
|
||||
MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToCoalition(side)
|
||||
end
|
||||
|
||||
local function _findZone(z)
|
||||
if z.name then
|
||||
local mz = ZONE:FindByName(z.name)
|
||||
if mz then return mz end
|
||||
end
|
||||
if z.coord then
|
||||
local r = z.radius or 150
|
||||
local v = VECTOR2:New(z.coord.x, z.coord.z)
|
||||
return ZONE_RADIUS:New(z.name or ('CTLD_ZONE_'..math.random(10000,99999)), v, r)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function _getUnitType(unit)
|
||||
local ud = unit and unit:GetDesc() or nil
|
||||
return ud and ud.typeName or unit and unit:GetTypeName()
|
||||
end
|
||||
|
||||
local function _nearestZonePoint(unit, list)
|
||||
if not unit or not unit:IsAlive() then return nil end
|
||||
local p3 = unit:GetPointVec3()
|
||||
local best, bestd = nil, 1e12
|
||||
for _,z in ipairs(list or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then
|
||||
local d = p3:DistanceFromPoint(mz:GetPointVec3())
|
||||
if d < bestd then best, bestd = mz, d end
|
||||
end
|
||||
end
|
||||
return best, bestd
|
||||
end
|
||||
|
||||
local function _coalitionAddGroup(side, category, groupData)
|
||||
-- Enforce side/category in groupData just to be safe
|
||||
groupData.category = category
|
||||
return coalition.addGroup(side, category, groupData)
|
||||
end
|
||||
|
||||
local function _spawnStaticCargo(side, point, cargoType, name)
|
||||
local static = {
|
||||
name = name,
|
||||
type = cargoType,
|
||||
x = point.x,
|
||||
y = point.z,
|
||||
heading = 0,
|
||||
canCargo = true,
|
||||
}
|
||||
return coalition.addStaticObject(side, static)
|
||||
end
|
||||
|
||||
local function _vec3FromUnit(unit)
|
||||
local p = unit:GetPointVec3()
|
||||
return { x = p.x, y = p.y, z = p.z }
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Construction
|
||||
-- =========================
|
||||
function CTLD:New(cfg)
|
||||
local o = setmetatable({}, self)
|
||||
o.Config = BASE:DeepCopy(CTLD.Config)
|
||||
if cfg then o.Config = BASE:Inherit(o.Config, cfg) end
|
||||
o.Side = o.Config.CoalitionSide
|
||||
o.MenuRoots = {}
|
||||
o.MenusByGroup = {}
|
||||
o:InitZones()
|
||||
o:InitMenus()
|
||||
|
||||
-- Periodic cleanup for crates
|
||||
o.Sched = SCHEDULER:New(nil, function()
|
||||
o:CleanupCrates()
|
||||
end, {}, 60, 60)
|
||||
|
||||
table.insert(CTLD._instances, o)
|
||||
_msgCoalition(o.Side, string.format('CTLD %s initialized for coalition', CTLD.Version))
|
||||
return o
|
||||
end
|
||||
|
||||
function CTLD:InitZones()
|
||||
self.PickupZones = {}
|
||||
self.DropZones = {}
|
||||
self.FOBZones = {}
|
||||
self._ZoneDefs = { PickupZones = {}, DropZones = {}, FOBZones = {} }
|
||||
for _,z in ipairs(self.Config.Zones.PickupZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.PickupZones, mz); self._ZoneDefs.PickupZones[mz:GetName()] = z end
|
||||
end
|
||||
for _,z in ipairs(self.Config.Zones.DropZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.DropZones, mz); self._ZoneDefs.DropZones[mz:GetName()] = z end
|
||||
end
|
||||
for _,z in ipairs(self.Config.Zones.FOBZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.FOBZones, mz); self._ZoneDefs.FOBZones[mz:GetName()] = z end
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Menus
|
||||
-- =========================
|
||||
function CTLD:InitMenus()
|
||||
if self.Config.UseGroupMenus then
|
||||
self:WireBirthHandler()
|
||||
else
|
||||
self.MenuRoot = MENU_COALITION:New(self.Side, 'CTLD')
|
||||
self:BuildCoalitionMenus(self.MenuRoot)
|
||||
end
|
||||
end
|
||||
|
||||
function CTLD:WireBirthHandler()
|
||||
local handler = EVENTHANDLER:New()
|
||||
handler:HandleEvent(EVENTS.Birth)
|
||||
local selfref = self
|
||||
function handler:OnEventBirth(eventData)
|
||||
local unit = eventData.IniUnit
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
if unit:GetCoalition() ~= selfref.Side then return end
|
||||
local typ = _getUnitType(unit)
|
||||
if not _isIn(selfref.Config.AllowedAircraft, typ) then return end
|
||||
local grp = unit:GetGroup()
|
||||
if not grp then return end
|
||||
local gname = grp:GetName()
|
||||
if selfref.MenusByGroup[gname] then return end
|
||||
selfref.MenusByGroup[gname] = selfref:BuildGroupMenus(grp)
|
||||
_msgGroup(grp, 'CTLD menu available (F10)')
|
||||
end
|
||||
self.BirthHandler = handler
|
||||
end
|
||||
|
||||
function CTLD:BuildGroupMenus(group)
|
||||
local root = MENU_GROUP:New(group, 'CTLD')
|
||||
-- Request crate submenu per catalog entry
|
||||
local reqRoot = MENU_GROUP:New(group, 'Request Crate', root)
|
||||
if self.Config.UseCategorySubmenus then
|
||||
local submenus = {}
|
||||
local function getSubmenu(catLabel)
|
||||
if not submenus[catLabel] then
|
||||
submenus[catLabel] = MENU_GROUP:New(group, catLabel, reqRoot)
|
||||
end
|
||||
return submenus[catLabel]
|
||||
end
|
||||
for key,def in pairs(self.Config.CrateCatalog) do
|
||||
local label = (def and (def.menu or def.description)) or key
|
||||
local sideOk = (not def.side) or def.side == self.Side
|
||||
if sideOk then
|
||||
local catLabel = (def and def.menuCategory) or 'Other'
|
||||
local parent = getSubmenu(catLabel)
|
||||
MENU_GROUP_COMMAND:New(group, label, parent, function()
|
||||
self:RequestCrateForGroup(group, key)
|
||||
end)
|
||||
end
|
||||
end
|
||||
else
|
||||
for key,def in pairs(self.Config.CrateCatalog) do
|
||||
local label = (def and (def.menu or def.description)) or key
|
||||
local sideOk = (not def.side) or def.side == self.Side
|
||||
if sideOk then
|
||||
MENU_GROUP_COMMAND:New(group, label, reqRoot, function()
|
||||
self:RequestCrateForGroup(group, key)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Troops
|
||||
MENU_GROUP_COMMAND:New(group, 'Load Troops', root, function() self:LoadTroops(group) end)
|
||||
MENU_GROUP_COMMAND:New(group, 'Unload Troops', root, function() self:UnloadTroops(group) end)
|
||||
|
||||
-- Build
|
||||
MENU_GROUP_COMMAND:New(group, 'Build Here', root, function()
|
||||
self:BuildAtGroup(group)
|
||||
end)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
function CTLD:BuildCoalitionMenus(root)
|
||||
-- Optional: implement coalition-level crate spawns at pickup zones
|
||||
for key,_ in pairs(self.Config.CrateCatalog) do
|
||||
MENU_COALITION_COMMAND:New(self.Side, 'Spawn '..key..' at nearest Pickup Zone', root, function()
|
||||
-- Not group-context; skip here
|
||||
_msgCoalition(self.Side, 'Group menus recommended for crate requests')
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Crates
|
||||
-- =========================
|
||||
function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
local cat = self.Config.CrateCatalog[crateKey]
|
||||
if not cat then _msgGroup(group, 'Unknown crate type: '..tostring(crateKey)) return end
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local zone, dist = _nearestZonePoint(unit, self.Config.Zones.PickupZones)
|
||||
local spawnPoint
|
||||
if zone and dist < 10000 then
|
||||
spawnPoint = zone:GetPointVec3()
|
||||
-- if pickup zone has smoke configured, mark it
|
||||
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
|
||||
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
|
||||
if smokeColor then
|
||||
trigger.action.smoke({ x = spawnPoint.x, z = spawnPoint.z }, smokeColor)
|
||||
end
|
||||
else
|
||||
-- fallback: spawn near aircraft current position (safe offset)
|
||||
local p = unit:GetPointVec3()
|
||||
spawnPoint = POINT_VEC3:New(p.x + 10, p.y, p.z + 10)
|
||||
end
|
||||
local cname = string.format('CTLD_CRATE_%s_%d', crateKey, math.random(100000,999999))
|
||||
_spawnStaticCargo(self.Side, { x = spawnPoint.x, z = spawnPoint.z }, cat.dcsCargoType or 'uh1h_cargo', cname)
|
||||
CTLD._crates[cname] = {
|
||||
key = crateKey,
|
||||
side = self.Side,
|
||||
spawnTime = timer.getTime(),
|
||||
point = { x = spawnPoint.x, z = spawnPoint.z },
|
||||
}
|
||||
_msgGroup(group, string.format('Spawned crate %s at %s', crateKey, zone and zone:GetName() or 'current pos'))
|
||||
end
|
||||
|
||||
function CTLD:GetNearbyCrates(point, radius)
|
||||
local result = {}
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
local dx = (meta.point.x - point.x)
|
||||
local dz = (meta.point.z - point.z)
|
||||
local d = math.sqrt(dx*dx + dz*dz)
|
||||
if d <= radius then
|
||||
table.insert(result, { name = name, meta = meta })
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function CTLD:CleanupCrates()
|
||||
local now = timer.getTime()
|
||||
local life = self.Config.CrateLifetime
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
if now - (meta.spawnTime or now) > life then
|
||||
local obj = StaticObject.getByName(name)
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[name] = nil
|
||||
if self.Config.Debug then env.info('[CTLD] Cleaned up crate '..name) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Build logic
|
||||
-- =========================
|
||||
function CTLD:BuildAtGroup(group)
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local p = unit:GetPointVec3()
|
||||
local here = { x = p.x, z = p.z }
|
||||
local radius = self.Config.BuildRadius
|
||||
local nearby = self:GetNearbyCrates(here, radius)
|
||||
if #nearby == 0 then _msgGroup(group, 'No crates within '..radius..'m') return end
|
||||
|
||||
-- Count by key
|
||||
local counts = {}
|
||||
for _,c in ipairs(nearby) do
|
||||
counts[c.meta.key] = (counts[c.meta.key] or 0) + 1
|
||||
end
|
||||
|
||||
-- Helper to consume crates of a given key/qty
|
||||
local function consumeCrates(key, qty)
|
||||
local removed = 0
|
||||
for _,c in ipairs(nearby) do
|
||||
if removed >= qty then break end
|
||||
if c.meta.key == key then
|
||||
local obj = StaticObject.getByName(c.name)
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[c.name] = nil
|
||||
removed = removed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Try composite recipes first (requires is a map of key->qty)
|
||||
for recipeKey,cat in pairs(self.Config.CrateCatalog) do
|
||||
if type(cat.requires) == 'table' and cat.build then
|
||||
local ok = true
|
||||
for reqKey,qty in pairs(cat.requires) do
|
||||
if (counts[reqKey] or 0) < qty then ok = false; break end
|
||||
end
|
||||
if ok then
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg))
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
for reqKey,qty in pairs(cat.requires) do consumeCrates(reqKey, qty) end
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or recipeKey))
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Then single-key recipes
|
||||
for key,count in pairs(counts) do
|
||||
local cat = self.Config.CrateCatalog[key]
|
||||
if cat and cat.build and (not cat.requires) and count >= (cat.required or 1) then
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg))
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
consumeCrates(key, cat.required or 1)
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or key))
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_msgGroup(group, 'Insufficient crates to build any asset here')
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Troops
|
||||
-- =========================
|
||||
function CTLD:LoadTroops(group, opts)
|
||||
local gname = group:GetName()
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
|
||||
local capacity = 6 -- simple default; can be adjusted per type later
|
||||
CTLD._troopsLoaded[gname] = {
|
||||
count = capacity,
|
||||
typeKey = 'RIFLE',
|
||||
}
|
||||
_msgGroup(group, string.format('Loaded %d troops (virtual). Use Unload Troops to deploy.', capacity))
|
||||
end
|
||||
|
||||
function CTLD:UnloadTroops(group)
|
||||
local gname = group:GetName()
|
||||
local load = CTLD._troopsLoaded[gname]
|
||||
if not load or (load.count or 0) == 0 then _msgGroup(group, 'No troops onboard') return end
|
||||
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local p = unit:GetPointVec3()
|
||||
local here = { x = p.x, z = p.z }
|
||||
local hdg = unit:GetHeading()
|
||||
|
||||
local count = load.count
|
||||
-- Spawn a simple infantry fireteam
|
||||
local units = {}
|
||||
for i=1, math.min(count, 8) do
|
||||
table.insert(units, {
|
||||
type = 'Infantry AK', name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
||||
x = here.x + i*1.5, y = here.z + (i%2==0 and 2 or -2), heading = hdg
|
||||
})
|
||||
end
|
||||
local groupData = {
|
||||
visible=false, lateActivation=false, tasks={}, task='Ground Nothing',
|
||||
units=units, route={}, name=string.format('CTLD_TROOPS_%d', math.random(100000,999999))
|
||||
}
|
||||
local spawned = _coalitionAddGroup(self.Side, Group.Category.GROUND, groupData)
|
||||
if spawned then
|
||||
CTLD._troopsLoaded[gname] = nil
|
||||
_msgGroup(group, string.format('Deployed %d troops', #units))
|
||||
else
|
||||
_msgGroup(group, 'Deploy failed: DCS group spawn error')
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Public helpers
|
||||
-- =========================
|
||||
function CTLD:RegisterCrate(key, def)
|
||||
self.Config.CrateCatalog[key] = def
|
||||
end
|
||||
|
||||
function CTLD:MergeCatalog(tbl)
|
||||
for k,v in pairs(tbl or {}) do self.Config.CrateCatalog[k] = v end
|
||||
end
|
||||
|
||||
function CTLD:AddPickupZone(z)
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.PickupZones, mz); table.insert(self.Config.Zones.PickupZones, z) end
|
||||
end
|
||||
|
||||
function CTLD:AddDropZone(z)
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.DropZones, mz); table.insert(self.Config.Zones.DropZones, z) end
|
||||
end
|
||||
|
||||
function CTLD:SetAllowedAircraft(list)
|
||||
self.Config.AllowedAircraft = BASE:DeepCopy(list)
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Return factory
|
||||
-- =========================
|
||||
_MOOSE_CTLD = CTLD
|
||||
return CTLD
|
||||
207
Moose_CTLD_Pure/Moose_CTLD_FAC.lua
Normal file
207
Moose_CTLD_Pure/Moose_CTLD_FAC.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
-- Moose_CTLD_FAC.lua
|
||||
-- FAC/RECCE features integrated with pure-MOOSE CTLD
|
||||
-- Provides: recce zones, auto target marking (smoke/illum), JTAC auto-lase bootstrap, optional artillery mark tasks
|
||||
|
||||
if not _G.Moose or not _G.BASE then
|
||||
env.info('[Moose_CTLD_FAC] Moose not detected. Ensure Moose.lua is loaded before this script.')
|
||||
end
|
||||
|
||||
local FAC = {}
|
||||
FAC.__index = FAC
|
||||
FAC.Version = '0.1.0-alpha'
|
||||
|
||||
FAC.Config = {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
ScanInterval = 20, -- seconds between scans
|
||||
MarkSmokeColor = trigger.smokeColor.Red,
|
||||
MarkIllum = false, -- drop illumination at night if true
|
||||
MarkText = true, -- place map marks with target info
|
||||
DetectionRadius = 5000, -- meters within zone
|
||||
MinReportSeparation = 400, -- meters between subsequent marks to reduce spam
|
||||
UseGroupMenus = true,
|
||||
Debug = false,
|
||||
Arty = { -- optional artillery support
|
||||
Enabled = true,
|
||||
Groups = { -- names of friendly artillery groups to use for marking
|
||||
-- 'BLUE_ARTY_1',
|
||||
},
|
||||
Rounds = 3,
|
||||
Spread = 120, -- meters randomization around mark point
|
||||
}
|
||||
}
|
||||
|
||||
FAC._lastMarks = {} -- [zoneName] = { lastPoint = {x,z} }
|
||||
|
||||
function FAC:New(ctld, cfg)
|
||||
local o = setmetatable({}, self)
|
||||
o.CTLD = ctld
|
||||
o.Config = BASE:DeepCopy(FAC.Config)
|
||||
if cfg then o.Config = BASE:Inherit(o.Config, cfg) end
|
||||
o.Side = o.Config.CoalitionSide
|
||||
o.Zones = {}
|
||||
o.MenusByGroup = {}
|
||||
|
||||
if o.Config.UseGroupMenus then o:WireBirthHandler() end
|
||||
return o
|
||||
end
|
||||
|
||||
function FAC:WireBirthHandler()
|
||||
local handler = EVENTHANDLER:New()
|
||||
handler:HandleEvent(EVENTS.Birth)
|
||||
local selfref = self
|
||||
function handler:OnEventBirth(eventData)
|
||||
local unit = eventData.IniUnit
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
if unit:GetCoalition() ~= selfref.Side then return end
|
||||
local grp = unit:GetGroup()
|
||||
if not grp then return end
|
||||
local gname = grp:GetName()
|
||||
if selfref.MenusByGroup[gname] then return end
|
||||
-- Simple menu: FAC actions
|
||||
local root = MENU_GROUP:New(grp, 'FAC/RECCE')
|
||||
MENU_GROUP_COMMAND:New(grp, 'List Recce Zones', root, function() selfref:MenuListZones(grp) end)
|
||||
MENU_GROUP_COMMAND:New(grp, 'Mark Contacts (all zones)', root, function() selfref:ForceScanAll(grp) end)
|
||||
selfref.MenusByGroup[gname] = root
|
||||
MESSAGE:New('FAC/RECCE menu available (F10)', 10):ToGroup(grp)
|
||||
end
|
||||
self.BirthHandler = handler
|
||||
end
|
||||
|
||||
function FAC:AddRecceZone(def)
|
||||
-- def: { name='ZONE_NAME' } or { coord={x,y,z}, radius=NN, name='Recce1' }
|
||||
local z
|
||||
if def.name then
|
||||
z = ZONE:FindByName(def.name)
|
||||
end
|
||||
if not z and def.coord then
|
||||
local r = def.radius or 5000
|
||||
z = ZONE_RADIUS:New(def.name or ('FAC_ZONE_'..math.random(10000,99999)), VECTOR2:New(def.coord.x, def.coord.z), r)
|
||||
end
|
||||
if not z then return nil end
|
||||
local Z = {
|
||||
Zone = z,
|
||||
Name = z:GetName(),
|
||||
Detector = self:CreateDetector(z),
|
||||
LastScan = 0,
|
||||
}
|
||||
table.insert(self.Zones, Z)
|
||||
return Z
|
||||
end
|
||||
|
||||
function FAC:CreateDetector(zone)
|
||||
-- Detection in areas using Moose detection classes
|
||||
local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
|
||||
local setEnemies = SET_GROUP:New():FilterCoalitions(enemySide):FilterCategoryGround():FilterStart()
|
||||
local det = DETECTION_AREAS:New(setEnemies, zone:GetRadius())
|
||||
det:BoundZone(zone)
|
||||
return det
|
||||
end
|
||||
|
||||
function FAC:MenuListZones(group)
|
||||
local names = {}
|
||||
for _,Z in ipairs(self.Zones) do table.insert(names, Z.Name) end
|
||||
MESSAGE:New('Recce zones: '..(table.concat(names, ', '):gsub('^%s+$','none')), 15):ToGroup(group)
|
||||
end
|
||||
|
||||
function FAC:ForceScanAll(group)
|
||||
for _,Z in ipairs(self.Zones) do self:ScanZone(Z, group) end
|
||||
end
|
||||
|
||||
function FAC:Run()
|
||||
-- schedule periodic scanning
|
||||
if self.Sched then self.Sched:Stop() end
|
||||
self.Sched = SCHEDULER:New(nil, function()
|
||||
for _,Z in ipairs(self.Zones) do self:ScanZone(Z) end
|
||||
end, {}, 5, self.Config.ScanInterval)
|
||||
end
|
||||
|
||||
local function _p3(v2)
|
||||
return { x = v2.x, y = land.getHeight({x=v2.x, y=v2.y}), z = v2.y }
|
||||
end
|
||||
|
||||
function FAC:ScanZone(Z, notifyGroup)
|
||||
local now = timer.getTime()
|
||||
local det = Z.Detector
|
||||
det:DetectionUpdate()
|
||||
local reports = det:GetDetectedItems()
|
||||
if not reports or #reports == 0 then
|
||||
if notifyGroup then MESSAGE:New('No contacts detected in '..Z.Name, 10):ToGroup(notifyGroup) end
|
||||
return
|
||||
end
|
||||
|
||||
local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
|
||||
for _,rep in ipairs(reports) do
|
||||
local contact = rep.object -- wrapper around GROUP or UNIT
|
||||
local pos2 = rep.point -- vec2
|
||||
if pos2 then
|
||||
local markPoint = { x = pos2.x, z = pos2.y }
|
||||
local allow = true
|
||||
local last = FAC._lastMarks[Z.Name]
|
||||
if last then
|
||||
local dx = (markPoint.x - last.x); local dz = (markPoint.z - last.z)
|
||||
if math.sqrt(dx*dx+dz*dz) < self.Config.MinReportSeparation then allow = false end
|
||||
end
|
||||
if allow then
|
||||
FAC._lastMarks[Z.Name] = { x = markPoint.x, z = markPoint.z }
|
||||
self:MarkTarget(Z, markPoint, rep, enemySide)
|
||||
if notifyGroup then MESSAGE:New(string.format('Marked contact in %s', Z.Name), 10):ToGroup(notifyGroup) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FAC:MarkTarget(Z, point, rep, enemySide)
|
||||
-- Smoke
|
||||
trigger.action.smoke(point, self.Config.MarkSmokeColor)
|
||||
-- Map mark
|
||||
if self.Config.MarkText then
|
||||
local txt = string.format('FAC: %s at %s', rep.type or 'Contact', coord.LLtoString(coord.LOtoLL(point), 0))
|
||||
trigger.action.markToCoalition(math.random(100000,999999), txt, point, self.Side, true)
|
||||
end
|
||||
-- Optional arty marking
|
||||
if self.Config.Arty.Enabled then
|
||||
self:ArtyMark(point)
|
||||
end
|
||||
end
|
||||
|
||||
function FAC:ArtyMark(point)
|
||||
local rounds = self.Config.Arty.Rounds or 1
|
||||
for _,gname in ipairs(self.Config.Arty.Groups or {}) do
|
||||
local g = Group.getByName(gname)
|
||||
if g and g:isExist() then
|
||||
local ctrl = g:getController()
|
||||
if ctrl then
|
||||
for i=1,rounds do
|
||||
local spread = self.Config.Arty.Spread or 0
|
||||
local tgt = { x = point.x + math.random(-spread, spread), y = point.z + math.random(-spread, spread) }
|
||||
local task = {
|
||||
id = 'FireAtPoint',
|
||||
params = {
|
||||
point = { x = tgt.x, y = land.getHeight({x=tgt.x, y=tgt.y}), z = tgt.y },
|
||||
expendQty = 1,
|
||||
dispersion = 50,
|
||||
attackQty = 1,
|
||||
weaponType = 0,
|
||||
}
|
||||
}
|
||||
ctrl:setTask(task)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Bootstrap a JTAC on a spawned unit/group via MOOSE FAC_AUTO
|
||||
function FAC:StartJTACOnGroup(groupName, code, smoke)
|
||||
local grp = GROUP:FindByName(groupName)
|
||||
if not grp then return nil end
|
||||
local fac = FAC_AUTO:New(grp)
|
||||
fac:SetLaser(true, code or 1688)
|
||||
fac:SetSmoke(smoke or self.Config.MarkSmokeColor)
|
||||
fac:SetDetectVehicles()
|
||||
fac:Start()
|
||||
return fac
|
||||
end
|
||||
|
||||
_MOOSE_CTLD_FAC = FAC
|
||||
return FAC
|
||||
106
Moose_CTLD_Pure/README.md
Normal file
106
Moose_CTLD_Pure/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Moose_CTLD_Pure
|
||||
|
||||
Pure-MOOSE CTLD-style logistics and FAC/RECCE without MIST or mission editor templates. Drop-in, config-driven.
|
||||
|
||||
## What this is
|
||||
|
||||
- Logistics and troop transport similar to popular CTLD scripts, implemented directly on MOOSE.
|
||||
- No MIST. No mission editor templates. Unit compositions are defined in config tables.
|
||||
- Optional FAC/RECCE module that auto-marks targets in zones and can drive artillery marking and JTAC auto-lase.
|
||||
|
||||
## Quick start
|
||||
|
||||
1) Load `Moose.lua` first, then include these files (order matters):
|
||||
- `Moose_CTLD_Pure/Moose_CTLD.lua`
|
||||
- `Moose_CTLD_Pure/Moose_CTLD_FAC.lua` (optional, for FAC/RECCE)
|
||||
|
||||
2) Initialize CTLD with minimal config:
|
||||
|
||||
```lua
|
||||
local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]])
|
||||
local ctld = CTLD:New({
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Zones = {
|
||||
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
|
||||
DropZones = { { name = 'DROP_BLUE_1' } },
|
||||
},
|
||||
})
|
||||
|
||||
-- Optionally load a larger crate catalog extracted from your CTLD.lua
|
||||
local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]])
|
||||
ctld:MergeCatalog(extracted)
|
||||
```
|
||||
|
||||
- If you don't have ME trigger zones, define by coordinates:
|
||||
|
||||
```lua
|
||||
Zones = {
|
||||
PickupZones = {
|
||||
{ coord = { x=123456, y=0, z=654321 }, radius=150, name='ScriptPickup1' },
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3) (Optional) FAC/RECCE:
|
||||
|
||||
```lua
|
||||
local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]])
|
||||
local fac = FAC:New(ctld, {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Arty = { Enabled = true, Groups = { 'BLUE_ARTY_1' }, Rounds = 3, Spread = 100 },
|
||||
})
|
||||
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
|
||||
fac:Run()
|
||||
```
|
||||
|
||||
4) In mission, pilots of allowed aircraft (configured in `AllowedAircraft`) will see F10 menus:
|
||||
- CTLD > Request Crate > [Type]
|
||||
- CTLD > Load Troops / Unload Troops
|
||||
- CTLD > Build Here
|
||||
- FAC/RECCE > List Recce Zones / Mark Contacts (all zones)
|
||||
|
||||
## Configuring crates and builds (no templates)
|
||||
|
||||
Edit `CrateCatalog` in `Moose_CTLD.lua`. Each entry defines:
|
||||
- `required`: how many crates to assemble
|
||||
- `weight`: informational
|
||||
- `dcsCargoType`: DCS static cargo type string (e.g., `uh1h_cargo`, `container_cargo`); tweak per map/mods
|
||||
- `build(point, headingDeg)`: function returning a DCS group table for `coalition.addGroup`
|
||||
|
||||
Example snippet:
|
||||
|
||||
```lua
|
||||
CrateCatalog = {
|
||||
MANPADS = {
|
||||
description = '2x Crates -> MANPADS team',
|
||||
weight = 120,
|
||||
dcsCargoType = 'uh1h_cargo',
|
||||
required = 2,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
return { visible=false, lateActivation=false, units={
|
||||
{ type='Soldier stinger', name='CTLD-MANPADS-1', x=point.x, y=point.z, heading=math.rad(headingDeg or 0) }
|
||||
} }
|
||||
end,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes and limitations
|
||||
|
||||
- This avoids sling-load event dependency by using a player command "Build Here" that consumes nearby crates within a radius. You can still sling crates physically.
|
||||
- Cargo static types vary across DCS versions. If a spawned crate isn’t sling-loadable, change `dcsCargoType` strings in `CrateCatalog` (e.g., `uh1h_cargo`, `container_cargo`, `ammo_cargo`, `container_20ft` depending on map/version).
|
||||
- Troops are virtually loaded and spawned on unload. Adjust capacity logic if you want type-based capacities.
|
||||
- FAC/RECCE detection leverages Moose `DETECTION_AREAS`. Tweak `ScanInterval`, `DetectionRadius`, and `MinReportSeparation` to balance spam/performance.
|
||||
- Artillery marking uses `Controller.setTask('FireAtPoint')` on configured groups. Ensure those groups exist and are artillery-capable.
|
||||
- JTAC Auto-Lase helper provided: `fac:StartJTACOnGroup(groupName, laserCode, smokeColor)` uses `FAC_AUTO`.
|
||||
|
||||
## Extending
|
||||
|
||||
- Add radio beacons, FOB build recipes, fuel/ammo crates, and CSAR hooks by registering more `CrateCatalog` entries and/or adding helper methods.
|
||||
- To support per-airframe capacities and sling-only rules, extend `AllowedAircraft` and add a type->capacity map.
|
||||
|
||||
## Changelog
|
||||
|
||||
- 0.1.0-alpha: Initial release: CTLD crate/troops/build, FAC recce zones + arty mark + JTAC bootstrap.
|
||||
166
Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua
Normal file
166
Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua
Normal file
@@ -0,0 +1,166 @@
|
||||
-- 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
|
||||
|
||||
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 = {}
|
||||
|
||||
-- Combat Vehicles (BLUE)
|
||||
cat['BLUE_M1128_STRYKER_MGS'] = { menuCategory='Combat Vehicles', menu='M1128 Stryker MGS', description='M1128 Stryker MGS', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1128 Stryker MGS') }
|
||||
cat['BLUE_M60A3_PATTON'] = { menuCategory='Combat Vehicles', menu='M-60A3 Patton', description='M-60A3 Patton', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-60') }
|
||||
cat['BLUE_HMMWV_TOW'] = { menuCategory='Combat Vehicles', menu='Humvee - TOW', description='Humvee - TOW', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1045 HMMWV TOW') }
|
||||
cat['BLUE_M1134_STRYKER_ATGM']= { menuCategory='Combat Vehicles', menu='M1134 Stryker ATGM',description='M1134 Stryker ATGM',dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1134 Stryker ATGM') }
|
||||
cat['BLUE_LAV25'] = { menuCategory='Combat Vehicles', menu='LAV-25', description='LAV-25', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('LAV-25') }
|
||||
cat['BLUE_M2A2_BRADLEY'] = { menuCategory='Combat Vehicles', menu='M2A2 Bradley', description='M2A2 Bradley', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-2 Bradley') }
|
||||
cat['BLUE_VAB_MEPHISTO'] = { menuCategory='Combat Vehicles', menu='ATGM VAB Mephisto', description='ATGM VAB Mephisto', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('VAB_Mephisto') }
|
||||
cat['BLUE_M1A2C_ABRAMS'] = { menuCategory='Combat Vehicles', menu='M1A2C Abrams', description='M1A2C Abrams', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1A2C_SEP_V3') }
|
||||
|
||||
-- Combat Vehicles (RED)
|
||||
cat['RED_BTR82A'] = { menuCategory='Combat Vehicles', menu='BTR-82A', description='BTR-82A', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR-82A') }
|
||||
cat['RED_BRDM2'] = { menuCategory='Combat Vehicles', menu='BRDM-2', description='BRDM-2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BRDM-2') }
|
||||
cat['RED_BMP3'] = { menuCategory='Combat Vehicles', menu='BMP-3', description='BMP-3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BMP-3') }
|
||||
cat['RED_T55'] = { menuCategory='Combat Vehicles', menu='T-55', description='T-55', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-55') }
|
||||
cat['RED_T72B3'] = { menuCategory='Combat Vehicles', menu='T-72B3', description='T-72B3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-72B3') }
|
||||
|
||||
-- Support (BLUE)
|
||||
cat['BLUE_MRAP_JTAC'] = { menuCategory='Support', menu='MRAP - JTAC', description='JTAC MRAP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MaxxPro_MRAP') }
|
||||
cat['BLUE_M818_AMMO'] = { menuCategory='Support', menu='M-818 Ammo Truck', description='M-818 Ammo Truck', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M 818') }
|
||||
cat['BLUE_M978_TANKER'] = { menuCategory='Support', menu='M-978 Tanker', description='M-978 Tanker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M978 HEMTT Tanker') }
|
||||
cat['BLUE_EWR_FPS117'] = { menuCategory='Support', menu='EWR Radar FPS-117', description='EWR Radar FPS-117', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('FPS-117') }
|
||||
|
||||
-- Support (RED)
|
||||
cat['RED_TIGR_JTAC'] = { menuCategory='Support', menu='Tigr - JTAC', description='JTAC Tigr', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Tigr_233036') }
|
||||
cat['RED_URAL4320_AMMO'] = { menuCategory='Support', menu='Ural-4320-31 Ammo Truck', description='Ural-4320-31 Ammo Truck', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-4320-31') }
|
||||
cat['RED_ATZ10_TANKER'] = { menuCategory='Support', menu='ATZ-10 Refueler', description='ATZ-10 Refueler', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ATZ-10') }
|
||||
cat['RED_EWR_1L13'] = { menuCategory='Support', menu='EWR Radar 1L13', description='EWR Radar 1L13', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('1L13 EWR') }
|
||||
|
||||
-- Artillery (BLUE)
|
||||
cat['BLUE_MLRS'] = { menuCategory='Artillery', menu='MLRS', description='MLRS', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MLRS') }
|
||||
cat['BLUE_SMERCH_CM'] = { menuCategory='Artillery', menu='Smerch_CM', description='Smerch (CM)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch') }
|
||||
cat['BLUE_L118_105MM'] = { menuCategory='Artillery', menu='L118 Light Artillery 105mm', description='L118 105mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('L118_Unit') }
|
||||
cat['BLUE_SMERCH_HE'] = { menuCategory='Artillery', menu='Smerch_HE', description='Smerch (HE)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch_HE') }
|
||||
cat['BLUE_M109'] = { menuCategory='Artillery', menu='M-109', description='M-109', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-109') }
|
||||
|
||||
-- Artillery (RED)
|
||||
cat['RED_GVOZDika'] = { menuCategory='Artillery', menu='SAU Gvozdika', description='SAU Gvozdika', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Gvozdika') }
|
||||
cat['RED_2S19_MSTA'] = { menuCategory='Artillery', menu='SPH 2S19 Msta', description='SPH 2S19 Msta', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Msta') }
|
||||
cat['RED_URAGAN_BM27'] = { menuCategory='Artillery', menu='Uragan_BM-27', description='Uragan BM-27', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Uragan_BM-27') }
|
||||
cat['RED_BM21_GRAD'] = { menuCategory='Artillery', menu='BM-21 Grad Ural', description='BM-21 Grad Ural', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Grad-URAL') }
|
||||
cat['RED_PLZ05'] = { menuCategory='Artillery', menu='PLZ-05 Mobile Artillery', description='PLZ-05', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('PLZ05') }
|
||||
|
||||
-- AAA (BLUE)
|
||||
cat['BLUE_GEPARD'] = { menuCategory='AAA', menu='Gepard AAA', description='Gepard AAA', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Gepard') }
|
||||
cat['BLUE_CRAM'] = { menuCategory='AAA', menu='LPWS C-RAM', description='LPWS C-RAM', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('HEMTT_C-RAM_Phalanx') }
|
||||
cat['BLUE_VULCAN_M163'] = { menuCategory='AAA', menu='SPAAA Vulcan M163', description='Vulcan M163', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Vulcan') }
|
||||
cat['BLUE_BOFORS40'] = { menuCategory='AAA', menu='Bofors 40mm', description='Bofors 40mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('bofors40') }
|
||||
|
||||
-- AAA (RED)
|
||||
cat['RED_URAL_ZU23'] = { menuCategory='AAA', menu='Ural-375 ZU-23', description='Ural-375 ZU-23', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-375 ZU-23') }
|
||||
cat['RED_SHILKA'] = { menuCategory='AAA', menu='ZSU-23-4 Shilka', description='ZSU-23-4 Shilka', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU-23-4 Shilka') }
|
||||
cat['RED_ZSU57_2'] = { menuCategory='AAA', menu='ZSU_57_2', description='ZSU_57_2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU_57_2') }
|
||||
|
||||
-- SAM short range (BLUE)
|
||||
cat['BLUE_M1097_AVENGER'] = { menuCategory='SAM short range', menu='M1097 Avenger', description='M1097 Avenger', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1097 Avenger') }
|
||||
cat['BLUE_M48_CHAPARRAL'] = { menuCategory='SAM short range', menu='M48 Chaparral', description='M48 Chaparral', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M48 Chaparral') }
|
||||
cat['BLUE_ROLAND_ADS'] = { menuCategory='SAM short range', menu='Roland ADS', description='Roland ADS', dcsCargoType='container_cargo', required=2, 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, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M6 Linebacker') }
|
||||
-- Rapier components and site
|
||||
cat['BLUE_RAPIER_LN'] = { menuCategory='SAM short range', menu='Rapier Launcher', description='Rapier Launcher', dcsCargoType='container_cargo', required=1, 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, 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, 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 }, 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'] = { menuCategory='SAM short range', menu='9K33 Osa', description='9K33 Osa', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Osa 9A33 ln') }
|
||||
cat['RED_STRELA1_9P31'] = { menuCategory='SAM short range', menu='9P31 Strela-1', description='9P31 Strela-1', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-1 9P31') }
|
||||
cat['RED_TUNGUSKA_2S6'] = { menuCategory='SAM short range', menu='2K22 Tunguska', description='2K22 Tunguska', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('2S6 Tunguska') }
|
||||
cat['RED_STRELA10M3'] = { menuCategory='SAM short range', menu='SA-13 Strela-10M3', description='SA-13 Strela-10M3', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-10M3') }
|
||||
-- HQ-7 components and site
|
||||
cat['RED_HQ7_LN'] = { menuCategory='SAM short range', menu='HQ-7_Launcher', description='HQ-7 Launcher', dcsCargoType='container_cargo', required=2, 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, 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 }, 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, 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, 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, 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, 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, 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 }, 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} }) }
|
||||
|
||||
cat['BLUE_NASAMS_LN'] = { menuCategory='SAM mid range', menu='NASAMS Launcher 120C', description='NASAMS LN 120C', dcsCargoType='container_cargo', required=1, 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, 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, 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 }, 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, 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, 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 }, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Kub 2P25 ln'}, {type='Kub 1S91 str', dx=12, dz=8} }) }
|
||||
|
||||
-- SAM long range (BLUE) Patriot
|
||||
cat['BLUE_PATRIOT_LN'] = { menuCategory='SAM long range', menu='Patriot Launcher', description='Patriot Launcher', dcsCargoType='container_cargo', required=1, 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, 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, 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 }, 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} }) }
|
||||
|
||||
-- SAM long range (RED) BUK
|
||||
cat['RED_BUK_LN'] = { menuCategory='SAM long range', menu='BUK Launcher', description='BUK Launcher', dcsCargoType='container_cargo', required=1, 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, 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, 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 }, 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} }) }
|
||||
|
||||
-- Drones (JTAC)
|
||||
cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTAC', description='MQ-9 JTAC', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.AIRPLANE, build=singleUnit('MQ-9 Reaper') }
|
||||
cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.AIRPLANE, build=singleUnit('WingLoong-I') }
|
||||
|
||||
-- FOB crates (Support) — three small crates build a FOB site
|
||||
cat['FOB_SMALL'] = { menuCategory='Support', menu='FOB Crate - Small', description='FOB small crate', dcsCargoType='container_cargo', required=1, 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', dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, 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} }) }
|
||||
|
||||
return cat
|
||||
44
Moose_CTLD_Pure/init_example.lua
Normal file
44
Moose_CTLD_Pure/init_example.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
-- init_example.lua (optional dev harness snippet)
|
||||
-- Load Moose before this. Then do:
|
||||
-- dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\init_example.lua]])
|
||||
|
||||
local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]])
|
||||
local ctld = CTLD:New({
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Zones = {
|
||||
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
|
||||
DropZones = { { name = 'DROP_BLUE_1' } },
|
||||
},
|
||||
})
|
||||
|
||||
-- Load the extracted CTLD-based crate catalog
|
||||
local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]])
|
||||
ctld:MergeCatalog(extracted)
|
||||
|
||||
-- Register a SAM site built from 4 crates
|
||||
ctld:RegisterCrate('IR-SAM', {
|
||||
description = '4x crates -> IR SAM site',
|
||||
weight = 300,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 4,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = 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 units = {
|
||||
{ type='Strela-10M3', name='CTLD-SAM-1', x=point.x, y=point.z, heading=hdg },
|
||||
{ type='Ural-375', name='CTLD-SAM-SUP', x=off(10,12).x, y=off(10,12).z, heading=hdg },
|
||||
}
|
||||
return { visible=false, lateActivation=false, units=units, name='CTLD_SAM_'..math.random(100000,999999) }
|
||||
end,
|
||||
})
|
||||
|
||||
-- FAC/RECCE setup
|
||||
local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]])
|
||||
local fac = FAC:New(ctld, {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Arty = { Enabled=true, Groups={'BLUE_ARTY_1'}, Rounds=2, Spread=80 },
|
||||
})
|
||||
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
|
||||
fac:Run()
|
||||
Reference in New Issue
Block a user