mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
208 lines
6.8 KiB
Lua
208 lines
6.8 KiB
Lua
-- 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
|