Worked on Artilery fire missoins.

This commit is contained in:
iTracerFacer 2025-11-04 17:05:59 -06:00
parent b799e83283
commit 68fdee7e85
3 changed files with 176 additions and 17 deletions

View File

@ -124,6 +124,7 @@ CTLD.Messages = {
-- Coach & nav
vectors_to_crate = "Nearest crate {id}: bearing {brg}°, range {rng} {rng_u}.",
vectors_to_pickup_zone = "Nearest supply zone {zone}: bearing {brg}°, range {rng} {rng_u}.",
coach_enabled = "Hover Coach enabled.",
coach_disabled = "Hover Coach disabled.",
@ -158,6 +159,12 @@ CTLD.Config = {
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
RequirePickupZoneForCrateRequest = true, -- enforce that crate requests must be near a Supply (Pickup) Zone
PickupZoneMaxDistance = 10000, -- meters; nearest pickup zone must be within this distance to allow a request
-- Crate spawn placement within pickup zones
PickupZoneSpawnRandomize = true, -- if true, spawn crates at a random point within the pickup zone (avoids stacking)
PickupZoneSpawnEdgeBuffer = 10, -- meters: keep spawns at least this far inside the zone edge
PickupZoneSpawnMinOffset = 100, -- meters: keep spawns at least this far from the exact center
CrateSpawnMinSeparation = 7, -- meters: try not to place a new crate closer than this to an existing one
CrateSpawnSeparationTries = 6, -- attempts to find a non-overlapping position before accepting best effort
BuildRequiresGroundCrates = true, -- if true, all required crates must be on the ground (won't count/consume carried crates)
-- Hover pickup configuration (Ciribob-style inspired)
@ -836,6 +843,37 @@ function CTLD:BuildGroupMenus(group)
_msgGroup(group, 'No friendly crates found.')
end
end)
MENU_GROUP_COMMAND:New(group, 'Vectors to Nearest Pickup Zone', navRoot, function()
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
local zone = nil
local dist = nil
-- Prefer configured pickup zones list; fallback to runtime zones converted to name list
local list = nil
if self.Config and self.Config.Zones and self.Config.Zones.PickupZones then
list = self.Config.Zones.PickupZones
elseif self.PickupZones and #self.PickupZones > 0 then
list = {}
for _,mz in ipairs(self.PickupZones) do
if mz and mz.GetName then table.insert(list, { name = mz:GetName() }) end
end
else
list = {}
end
zone, dist = _nearestZonePoint(unit, list)
if not zone then
_eventSend(self, group, nil, 'no_pickup_zones', {})
return
end
local up = unit:GetPointVec3()
local zp = zone:GetPointVec3()
local from = { x = up.x, z = up.z }
local to = { x = zp.x, z = zp.z }
local brg = _bearingDeg(from, to)
local isMetric = _getPlayerIsMetric(unit)
local rngV, rngU = _fmtRange(dist, isMetric)
_eventSend(self, group, nil, 'vectors_to_pickup_zone', { zone = zone:GetName(), brg = brg, rng = rngV, rng_u = rngU })
end)
MENU_GROUP_COMMAND:New(group, 'Re-mark Nearest Crate (Smoke)', navRoot, function()
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
@ -898,8 +936,50 @@ function CTLD:RequestCrateForGroup(group, crateKey)
end
if zone and dist <= maxd then
spawnPoint = zone:GetPointVec3()
-- if pickup zone has smoke configured, mark it
-- Compute a random spawn point within the pickup zone to avoid stacking crates
local center = zone:GetPointVec3()
local rZone = self:_getZoneRadius(zone)
local edgeBuf = math.max(0, self.Config.PickupZoneSpawnEdgeBuffer or 10)
local minOff = math.max(0, self.Config.PickupZoneSpawnMinOffset or 5)
local rMax = math.max(0, (rZone or 150) - edgeBuf)
local tries = math.max(1, self.Config.CrateSpawnSeparationTries or 6)
local minSep = math.max(0, self.Config.CrateSpawnMinSeparation or 7)
local function candidate()
if (self.Config.PickupZoneSpawnRandomize == false) or rMax <= 0 then
return { x = center.x, z = center.z }
end
local rr
if rMax > minOff then
rr = minOff + math.sqrt(math.random()) * (rMax - minOff)
else
rr = rMax
end
local th = math.random() * 2 * math.pi
return { x = center.x + rr * math.cos(th), z = center.z + rr * math.sin(th) }
end
local function isClear(pt)
if minSep <= 0 then return true end
for _,meta in pairs(CTLD._crates) do
if meta and meta.side == self.Side and meta.point then
local dx = (meta.point.x - pt.x)
local dz = (meta.point.z - pt.z)
if (dx*dx + dz*dz) < (minSep*minSep) then return false end
end
end
return true
end
local chosen = candidate()
if not isClear(chosen) then
for _=1,tries-1 do
local c = candidate()
if isClear(c) then chosen = c; break end
end
end
spawnPoint = { x = chosen.x, z = chosen.z }
-- if pickup zone has smoke configured, mark the spawn location
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
if smokeColor then

View File

@ -248,6 +248,16 @@ local function _isBomberOrFighter(u)
return u:hasAttribute('Strategic bombers') or u:hasAttribute('Bombers') or u:hasAttribute('Multirole fighters')
end
local function _artyMaxRangeForUnit(u)
-- Heuristic max range (meters) by unit type name; conservative to avoid "never fires" when out of range
local tn = string.lower(u:getTypeName() or '')
if tn:find('mortar') or tn:find('2b11') or tn:find('m252') then return 6000 end
if tn:find('mlrs') or tn:find('m270') or tn:find('bm%-21') or tn:find('grad') then return 30000 end
if tn:find('howitzer') or tn:find('m109') or tn:find('paladin') or tn:find('2s19') or tn:find('msta') or tn:find('2s3') or tn:find('akatsiya') then return 20000 end
-- generic tube artillery fallback
return 12000
end
local function _coalitionOpposite(side)
return (side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
end
@ -494,6 +504,21 @@ function FAC:_buildGroupMenus(group)
-- RECCE
MENU_GROUP_COMMAND:New(group, 'RECCE: Sweep & Mark', root, function() self:_recceDetect(group) end)
-- Debug controls (mission-maker convenience; per-instance toggle)
local dbg = MENU_GROUP:New(group, 'Debug', root)
MENU_GROUP_COMMAND:New(group, 'Enable Debug Logging', dbg, function()
self.Config.Debug = true
local u = group:GetUnit(1); local who = (u and u:GetName()) or 'Unknown'
env.info(string.format('[FAC] Debug ENABLED by %s', who))
MESSAGE:New('FAC Debug logging ENABLED', 8):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, 'Disable Debug Logging', dbg, function()
self.Config.Debug = false
local u = group:GetUnit(1); local who = (u and u:GetName()) or 'Unknown'
env.info(string.format('[FAC] Debug DISABLED by %s', who))
MESSAGE:New('FAC Debug logging DISABLED', 8):ToGroup(group)
end)
MESSAGE:New('FAC/RECCE menu ready (F10)', 10):ToGroup(group)
return { root = root }
end
@ -554,6 +579,7 @@ function FAC:_setOnStation(group, on)
local u = group:GetUnit(1)
if not u or not u:IsAlive() then return end
local uname = u:GetName()
_dbg(self, string.format('Action:SetOnStation unit=%s on=%s', uname, tostring(on and true or false)))
-- init defaults
if not self._laserCodes[uname] then self._laserCodes[uname] = self.Config.FAC_laser_codes[1] end
if not self._markerType[uname] then self._markerType[uname] = self.Config.MarkerDefault end
@ -577,6 +603,7 @@ function FAC:_setLaserCode(group, code)
-- Set the laser code for this FAC; updates status if on-station
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
_dbg(self, string.format('Action:SetLaserCode unit=%s code=%s', uname, tostring(code)))
self._laserCodes[uname] = tostring(code)
if self._facOnStation[uname] then
trigger.action.outTextForCoalition(u:GetCoalition(), string.format('[FAC "%s" on-station using CODE %s]', self:_facName(uname), self._laserCodes[uname]), 10)
@ -586,6 +613,7 @@ end
function FAC:_setLaserDigit(group, digit, val)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
_dbg(self, string.format('Action:SetLaserDigit unit=%s digit=%d val=%s', uname, digit, tostring(val)))
local cur = self._laserCodes[uname] or '1688'
local s = tostring(cur)
if #s ~= 4 then s = '1688' end
@ -598,6 +626,7 @@ end
function FAC:_setMarker(group, typ, color)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
_dbg(self, string.format('Action:SetMarker unit=%s type=%s color=%s', uname, tostring(typ), tostring(color)))
self._markerType[uname] = typ
self._markerColor[uname] = color
local colorStr = ({[trigger.smokeColor.Green]='GREEN',[trigger.smokeColor.Red]='RED',[trigger.smokeColor.White]='WHITE',[trigger.smokeColor.Orange]='ORANGE',[trigger.smokeColor.Blue]='BLUE'})[color] or 'WHITE'
@ -615,6 +644,7 @@ function FAC:_setMapMarker(group)
if not tgt then MESSAGE:New('No Target to Mark', 10):ToGroup(group); return end
local t = Unit.getByName(tgt.name)
if not t or not t:isActive() then return end
_dbg(self, string.format('Action:MapMarker unit=%s target=%s', uname, tgt.name))
local dms, mgrs, altM, altF, hdg, mph = _formatUnitGeo(t)
local text = string.format('%s - DMS %s Alt %dm/%dft\nHeading %d Speed %d MPH\nSpotted by %s', t:getTypeName(), dms, altM, altF, hdg, mph, self:_facName(uname))
local id = math.floor(timer.getTime()*1000 + 0.5)
@ -660,6 +690,7 @@ function FAC:_autolase(uname)
local enemy = self:_currentOrFindEnemy(u, uname)
if enemy then
_dbg(self, string.format('AutoLase: unit=%s target=%s type=%s', uname, enemy:getName(), enemy:getTypeName()))
self:_laseUnit(enemy, u, uname, self._laserCodes[uname])
-- variable next tick based on target speed
local v = enemy:getVelocity() or {x=0,z=0}
@ -670,6 +701,7 @@ function FAC:_autolase(uname)
local nm = self._smokeMarks[enemy:getName()]
if nm and nm < timer.getTime() then self:_createMarker(enemy, uname) end
else
_dbg(self, string.format('AutoLase: unit=%s no-visible-target -> cancel', uname))
self:_cancelLase(uname)
timer.scheduleFunction(function(args) self:_autolase(args[1]) end, {uname}, timer.getTime()+5)
end
@ -690,6 +722,7 @@ function FAC:_currentOrFindEnemy(facUnit, uname)
end
end
-- find nearest visible
_dbg(self, string.format('FindNearest: unit=%s mode=%s', uname, tostring(self.Config.FAC_lock)))
return self:_findNearestEnemy(facUnit, self.Config.FAC_lock)
end
@ -698,6 +731,7 @@ function FAC:_findNearestEnemy(facUnit, targetType)
local enemySide = _coalitionOpposite(facSide)
local nearest, best = nil, self.Config.FAC_maxDistance or 18520
local origin = facUnit:getPoint()
_dbg(self, string.format('Search: origin=(%.0f,%.0f) radius=%d targetType=%s', origin.x, origin.z, self.Config.FAC_maxDistance or 18520, tostring(targetType)))
local volume = { id = world.VolumeType.SPHERE, params = { point = origin, radius = self.Config.FAC_maxDistance or 18520 } }
local function search(u)
@ -720,6 +754,7 @@ function FAC:_findNearestEnemy(facUnit, targetType)
self._currentTargets[uname] = { name = nearest:getName(), unitType = nearest:getTypeName(), unitId = nearest:getID() }
self:_announceNewTarget(facUnit, nearest, uname)
self:_createMarker(nearest, uname)
_dbg(self, string.format('Search: selected target=%s type=%s dist=%.0f', nearest:getName(), nearest:getTypeName(), best))
end
return nearest
end
@ -728,6 +763,7 @@ function FAC:_announceNewTarget(facUnit, enemy, uname)
local col = self._markerColor[uname]
local colorStr = ({[trigger.smokeColor.Green]='GREEN',[trigger.smokeColor.Red]='RED',[trigger.smokeColor.White]='WHITE',[trigger.smokeColor.Orange]='ORANGE',[trigger.smokeColor.Blue]='BLUE'})[col or trigger.smokeColor.White] or 'WHITE'
local dms, mgrs, altM, altF, hdg, mph = _formatUnitGeo(enemy)
_dbg(self, string.format('AnnounceTarget: fac=%s target=%s code=%s mark=%s %s', self:_facName(uname), enemy:getName(), self._laserCodes[uname] or '1688', colorStr, self._markerType[uname] or 'FLARES'))
local msg = string.format('[%s lasing new target %s. CODE %s @ DMS %s MGRS %s Alt %dm/%dft\nMarked %s %s]',
self:_facName(uname), enemy:getTypeName(), self._laserCodes[uname] or '1688', dms, mgrs, altM, altF, colorStr, self._markerType[uname] or 'FLARES')
trigger.action.outTextForCoalition(facUnit:getCoalition(), msg, 10)
@ -739,6 +775,7 @@ function FAC:_createMarker(enemy, uname)
local when = (typ == 'SMOKE') and 300 or 5
self._smokeMarks[enemy:getName()] = timer.getTime() + when
local p = enemy:getPoint()
_dbg(self, string.format('CreateMarker: target=%s type=%s color=%s ttl=%.0fs', enemy:getName(), typ, tostring(col or trigger.smokeColor.White), when))
if typ == 'SMOKE' then
trigger.action.smoke({x=p.x, y=p.y+2, z=p.z}, col or trigger.smokeColor.White)
else
@ -794,7 +831,11 @@ end
function FAC:_scanManualList(group)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
local origin = u:GetPoint()
-- Use DCS Unit position for robust coords
local du = Unit.getByName(uname)
if not du or not du:getPoint() then return end
local origin = du:getPoint()
_dbg(self, string.format('Action:ScanManual unit=%s origin=(%.0f,%.0f) radius=%d', uname, origin.x, origin.z, self.Config.FAC_maxDistance))
local enemySide = _coalitionOpposite(u:GetCoalition())
local foundAA, foundOther = {}, {}
local function ins(tbl, item) table.insert(tbl, item) end
@ -814,6 +855,7 @@ function FAC:_scanManualList(group)
local list = {}
for i=1,10 do list[i] = foundAA[i] or foundOther[i] end
self._manualLists[uname] = list
_dbg(self, string.format('Action:ScanManual unit=%s results=%d', uname, #list))
-- print bearings/ranges
local gid = group:GetDCSObject() and group:GetDCSObject():getID() or nil
for i,v in ipairs(list) do
@ -836,6 +878,7 @@ end
function FAC:_setManualTarget(group, idx)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
_dbg(self, string.format('Action:SetManualTarget unit=%s index=%d', uname, idx))
local list = self._manualLists[uname]
if not list or not list[idx] then
MESSAGE:New('Invalid Target', 10):ToGroup(group)
@ -847,8 +890,10 @@ function FAC:_setManualTarget(group, idx)
self:_setOnStation(group, true)
self:_createMarker(enemy, uname)
MESSAGE:New(string.format('Designating Target %d: %s', idx, enemy:getTypeName()), 10):ToGroup(group)
_dbg(self, string.format('Action:SetManualTarget unit=%s target=%s type=%s', uname, enemy:getName(), enemy:getTypeName()))
else
MESSAGE:New(string.format('Target %d already dead', idx), 10):ToGroup(group)
_dbg(self, string.format('Action:SetManualTarget unit=%s index=%d dead', uname, idx))
end
end
@ -856,6 +901,7 @@ function FAC:_multiStrike(group)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
local list = self._manualLists[uname] or {}
_dbg(self, string.format('Action:MultiStrike unit=%s targets=%d', uname, #list))
for _,t in ipairs(list) do if t and t:isExist() then self:_callFireMission(group, 10, 0, t) end end
end
-- #endregion Manual Scan/Select
@ -865,7 +911,11 @@ function FAC:_recceDetect(group)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local uname = u:GetName()
local side = u:GetCoalition()
local pos = u:GetPoint()
-- Use DCS Unit API for coordinates to avoid relying on MOOSE point methods
local du = Unit.getByName(uname)
if not du or not du:getPoint() then return end
local pos = du:getPoint()
_dbg(self, string.format('Action:RecceSweep unit=%s center=(%.0f,%.0f) radius=%d', uname, pos.x, pos.z, self.Config.RecceScanRadius))
local enemySide = _coalitionOpposite(side)
local temp = {}
local count = 0
@ -886,6 +936,7 @@ function FAC:_recceDetect(group)
world.searchObjects(Object.Category.UNIT, { id=world.VolumeType.SPHERE, params={ point = pos, radius = self.Config.RecceScanRadius } }, cb)
world.searchObjects(Object.Category.STATIC, { id=world.VolumeType.SPHERE, params={ point = pos, radius = self.Config.RecceScanRadius } }, cb)
self._manualLists[uname] = temp
_dbg(self, string.format('Action:RecceSweep unit=%s results=%d', uname, #temp))
end
function FAC:_executeRecceMark(pos, coal)
@ -941,7 +992,16 @@ end
function FAC:_getArtyFor(point, facUnit, mode)
-- mode: 0 HE, 1 illum, 2 mortar only, 3 heavy only (no smart), 4 guided/naval/air, -1 any except bombers
local side = facUnit and facUnit:getCoalition() or self.Side
-- Accept either a MOOSE Unit (GetCoalition) or a DCS Unit (getCoalition)
local side
if facUnit then
if facUnit.GetCoalition then
side = facUnit:GetCoalition()
elseif facUnit.getCoalition then
side = facUnit:getCoalition()
end
end
side = side or self.Side
local bestName
local candidates = {}
local function consider(found)
@ -978,8 +1038,8 @@ function FAC:_getArtyFor(point, facUnit, mode)
local tot, rng = self:_navalGunStats(g:getUnits())
if tot>0 and rng >= d then table.insert(filtered, gname) end
elseif _isArtilleryUnit(u1) then
-- crude range gates by type name buckets
table.insert(filtered, gname)
local r = _artyMaxRangeForUnit(u1)
if d <= r then table.insert(filtered, gname) end
end
end
end
@ -996,11 +1056,17 @@ end
function FAC:_checkArty(group)
local u = group:GetUnit(1); if not u or not u:IsAlive() then return end
local pos = u:GetPoint()
local g = self:_getArtyFor(pos, u, 0)
-- Resolve using DCS Unit position
local du = Unit.getByName(u:GetName())
if not du or not du:getPoint() then return end
local pos = du:getPoint()
_dbg(self, string.format('Action:CheckArty unit=%s at=(%.0f,%.0f)', u:GetName(), pos.x, pos.z))
local g = self:_getArtyFor(pos, du, 0)
if g then
_dbg(self, string.format('Action:CheckArty unit=%s found=%s', u:GetName(), g:getName()))
MESSAGE:New('Arty available: '..g:getName(), 10):ToGroup(group)
else
_dbg(self, string.format('Action:CheckArty unit=%s none-found', u:GetName()))
MESSAGE:New('No untasked arty/bomber/naval in range', 10):ToGroup(group)
end
end
@ -1013,30 +1079,37 @@ function FAC:_callFireMission(group, rounds, mode, specificTarget)
local attackPoint
if enemy and enemy:isActive() then attackPoint = enemy:getPoint() else
-- offset forward of FAC as fallback
local hdg = _getHeading(u)
attackPoint = { x = u:GetPoint().x + math.cos(hdg)*self.Config.facOffsetDist, y = u:GetPoint().y, z = u:GetPoint().z + math.sin(hdg)*self.Config.facOffsetDist }
local du = Unit.getByName(uname)
if not du or not du:getPoint() then return end
local hdg = _getHeading(du)
local up = du:getPoint()
attackPoint = { x = up.x + math.cos(hdg)*self.Config.facOffsetDist, y = up.y, z = up.z + math.sin(hdg)*self.Config.facOffsetDist }
end
local arty = self:_getArtyFor(attackPoint, u, mode)
_dbg(self, string.format('Action:CallFireMission unit=%s rounds=%s mode=%s target=%s', uname, tostring(rounds), tostring(mode), enemy and enemy:getName() or 'offset'))
local arty = self:_getArtyFor(attackPoint, Unit.getByName(uname), mode)
if not arty then
_dbg(self, string.format('Action:CallFireMission unit=%s no-asset-in-range', uname))
MESSAGE:New('Unable to process fire mission: no asset in range', 10):ToGroup(group)
return
end
local firepoint = { x = attackPoint.x, y = attackPoint.z, altitude = arty:getUnit(1):getPoint().y, altitudeEnabled = true, attackQty = 1, expend = 'One', weaponType = 268402702 }
local task
if _isNavalUnit(arty:getUnit(1)) then
task = { id='FireAtPoint', params = { point = { x = attackPoint.x, y = attackPoint.y, z = attackPoint.z }, expendQty = 1, dispersion = 50, attackQty = 1, weaponType = 0 } }
-- FireAtPoint expects a 2D vec2 where y=z; do not pass altitude here
task = { id='FireAtPoint', params = { point = { x = attackPoint.x, y = attackPoint.z }, expendQty = 1, radius = 50, weaponType = 0 } }
elseif _isBomberOrFighter(arty:getUnit(1)) then
task = { id='Bombing', params = { y = attackPoint.z, x = attackPoint.x, altitude = firepoint.altitude, altitudeEnabled = true, attackQty = 1, groupAttack = true, weaponType = 2147485694 } }
else
task = { id='FireAtPoint', params = { point = { x = attackPoint.x, y = attackPoint.y, z = attackPoint.z }, expendQty = rounds or 1, dispersion = 50, attackQty = 1, weaponType = 0 } }
-- Ground artillery
task = { id='FireAtPoint', params = { point = { x = attackPoint.x, y = attackPoint.z }, expendQty = rounds or 1, radius = 50, weaponType = 0 } }
end
local ctrl = arty:getController()
ctrl:setOption(1,1)
ctrl:pushTask(task)
ctrl:setOption(10,3221225470)
-- Avoid forcing unknown option ids; rely on group's ROE/AlarmState from mission editor
local ammo = self:_artyAmmo(arty:getUnits())
self._ArtyTasked[arty:getName()] = { name = arty:getName(), tasked = rounds or 1, timeTasked = timer.getTime(), tgt = enemy, requestor = self:_facName(uname) }
trigger.action.outTextForCoalition(u:GetCoalition(), string.format('Fire mission sent: %s firing %d rounds. Requestor: %s', arty:getUnit(1):getTypeName(), rounds or 1, self:_facName(uname)), 10)
_dbg(self, string.format('Action:CallFireMission unit=%s asset=%s rounds=%s point=(%.0f,%.0f)', uname, arty:getName(), tostring(rounds), attackPoint.x, attackPoint.z))
end
function FAC:_callFireMissionMulti(group, rounds, mode)
@ -1047,6 +1120,7 @@ function FAC:_callFireMissionMulti(group, rounds, mode)
local first = list[1]
local arty = self:_getArtyFor(first:getPoint(), u, 4)
if not arty then MESSAGE:New('No guided asset available', 10):ToGroup(group) return end
_dbg(self, string.format('Action:CallFireMissionMulti unit=%s targets=%d asset=%s', uname, #list, arty:getName()))
local tasks = {}
local guided = self:_guidedAmmo(arty:getUnits())
for i,t in ipairs(list) do
@ -1061,6 +1135,7 @@ function FAC:_callFireMissionMulti(group, rounds, mode)
ctrl:setOption(10,3221225470)
self._ArtyTasked[arty:getName()] = { name=arty:getName(), tasked = #tasks, timeTasked=timer.getTime(), tgt=nil, requestor=self:_facName(uname) }
trigger.action.outTextForCoalition(u:GetCoalition(), string.format('Guided strike queued on %d targets', #tasks), 10)
_dbg(self, string.format('Action:CallFireMissionMulti unit=%s queuedTasks=%d', uname, #tasks))
end
function FAC:_callCarpetOnCurrent(group)
@ -1071,7 +1146,10 @@ function FAC:_callCarpetOnCurrent(group)
if not tgt then MESSAGE:New('No current target', 10):ToGroup(group) return end
local enemy = Unit.getByName(tgt.name)
if not enemy or not enemy:isActive() then MESSAGE:New('Target invalid', 10):ToGroup(group) return end
self:_executeCarpetOrTALD(enemy:getPoint(), u:GetCoalition(), 'CARPET', math.floor(_getHeading(u)*180/math.pi))
local du = Unit.getByName(uname)
local attackHdgDeg = du and math.floor(_getHeading(du)*180/math.pi) or 0
_dbg(self, string.format('Action:Carpet unit=%s target=%s hdg=%d', uname, enemy:getName(), attackHdgDeg))
self:_executeCarpetOrTALD(enemy:getPoint(), u:GetCoalition(), 'CARPET', attackHdgDeg)
end
function FAC:_executeCarpetOrTALD(point, coal, mode, attackHeadingDeg)
@ -1086,6 +1164,7 @@ function FAC:_executeCarpetOrTALD(point, coal, mode, attackHeadingDeg)
local hdg = attackHeadingDeg and math.rad(attackHeadingDeg) or 0
local weaponType = (mode=='TALD') and 8589934592 or 2147485694
local altitude = (mode=='TALD') and 10000 or pos.y
_dbg(self, string.format('Action:%s asset=%s heading=%d', tostring(mode or 'CARPET'), u1:getName(), attackHeadingDeg or -1))
local task = { id='Bombing', params={ x=point.x, y=point.z, altitude=altitude, altitudeEnabled=true, attackQty=1, groupAttack=true, weaponType=weaponType, direction=hdg, directionEnabled=true } }
local ctrl = arty:getController()
ctrl:setOption(1,1)

Binary file not shown.