mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Worked on Artilery fire missoins.
This commit is contained in:
parent
b799e83283
commit
68fdee7e85
@ -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
|
||||
|
||||
@ -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.
Loading…
x
Reference in New Issue
Block a user