mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
611 lines
29 KiB
Lua
611 lines
29 KiB
Lua
env.info("-----DCSRetribution|MOOSE Autolase plugin - configuration start ------")
|
||
|
||
-- Defaults (overridden by dcsRetribution.plugins.MooseAutolase when present)
|
||
JtacAlphaSmoke = true
|
||
JtacAlphaUHF = 250
|
||
JtacAlphaVHF = 123
|
||
JtacAlphaTargetsMax = 1
|
||
JtacAlphaRadiusNM = 50
|
||
JtacBravoSmoke = true
|
||
JtacBravoUHF = 249
|
||
JtacBravoVHF = 122
|
||
JtacBravoTargetsMax = 1
|
||
JtacBravoRadiusNM = 50
|
||
DangerCloseNM = 1
|
||
AutolaseSmokeDurationSec = 180
|
||
MooseAutolaseDebug = false
|
||
UseConvoyChaosSFX = true -- NEW: set false if ConvoyChaos.ogg not in mission
|
||
|
||
-- Pull from UI if available
|
||
if dcsRetribution and dcsRetribution.plugins and dcsRetribution.plugins.MooseAutolase then
|
||
local p = dcsRetribution.plugins.MooseAutolase
|
||
JtacAlphaSmoke = p.JtacAlphaSmoke
|
||
JtacAlphaUHF = p.JtacAlphaUHF
|
||
JtacAlphaVHF = p.JtacAlphaVHF
|
||
JtacAlphaTargetsMax = p.JtacAlphaTargetsMax
|
||
JtacAlphaRadiusNM = p.JtacAlphaRadiusNM
|
||
JtacBravoSmoke = p.JtacBravoSmoke
|
||
JtacBravoUHF = p.JtacBravoUHF
|
||
JtacBravoVHF = p.JtacBravoVHF
|
||
JtacBravoTargetsMax = p.JtacBravoTargetsMax
|
||
JtacBravoRadiusNM = p.JtacBravoRadiusNM
|
||
DangerCloseNM = p.DangerCloseNM
|
||
AutolaseSmokeDurationSec = p.AutolaseSmokeDurationSec
|
||
MooseAutolaseDebug = p.MooseAutolaseDebug
|
||
if p.UseConvoyChaosSFX ~= nil then UseConvoyChaosSFX = p.UseConvoyChaosSFX end
|
||
else
|
||
env.info("-----dcsRetribution.plugins.MooseAutolase NOT FOUND")
|
||
end
|
||
|
||
-- Debug output to DCS log
|
||
env.info("--------- JtacAlphaSmoke=" .. tostring(JtacAlphaSmoke) ..
|
||
" | JtacAlphaUHF=" .. tostring(JtacAlphaUHF) ..
|
||
" | JtacAlphaVHF=" .. tostring(JtacAlphaVHF) ..
|
||
" | JtacAlphaTargetsMax=" .. tostring(JtacAlphaTargetsMax) ..
|
||
" | JtacAlphaRadiusNM=" .. tostring(JtacAlphaRadiusNM) ..
|
||
" | JtacBravoSmoke=" .. tostring(JtacBravoSmoke) ..
|
||
" | JtacBravoUHF=" .. tostring(JtacBravoUHF) ..
|
||
" | JtacBravoVHF=" .. tostring(JtacBravoVHF) ..
|
||
" | JtacBravoTargetsMax=" .. tostring(JtacBravoTargetsMax) ..
|
||
" | JtacBravoRadiusNM=" .. tostring(JtacBravoRadiusNM) ..
|
||
" | DangerCloseNM=" .. tostring(DangerCloseNM) ..
|
||
" | UseConvoyChaosSFX=" .. tostring(UseConvoyChaosSFX) ..
|
||
" | MooseAutolaseDebug=" .. tostring(MooseAutolaseDebug) ..
|
||
" | AutolaseSmokeDurationSec=" .. tostring(AutolaseSmokeDurationSec)
|
||
)
|
||
|
||
-----------------------------------------------------------------
|
||
-- COORDINATE:Smoke duration override (uses AutolaseSmokeDurationSec when duration omitted)
|
||
-----------------------------------------------------------------
|
||
if not _G.__CoordSmokePatched and COORDINATE and COORDINATE.Smoke then
|
||
_G.__CoordSmokePatched = true
|
||
COORDINATE._Smoke_original = COORDINATE._Smoke_original or COORDINATE.Smoke
|
||
function COORDINATE:Smoke(color, duration, delay, name, offset, direction, distance)
|
||
local useDuration = (duration ~= nil) and duration or AutolaseSmokeDurationSec
|
||
if MooseAutolaseDebug and duration == nil then
|
||
env.info(string.format("[AutolaseSmokePatch] default duration %ds (color=%s)", useDuration, tostring(color)))
|
||
end
|
||
return COORDINATE._Smoke_original(self, color, useDuration, delay, name, offset, direction, distance)
|
||
end
|
||
end
|
||
|
||
-- Safe AM constant
|
||
local AM = (radio and radio.modulation and radio.modulation.AM) or 0
|
||
|
||
-- Utility: format integer values with thousands separators (e.g., 12,345)
|
||
local function FormatWithCommas(n)
|
||
local s = tostring(math.floor(n or 0))
|
||
local left, num, right = s:match('^([^%d]*%d)(%d*)(.-)$')
|
||
return left .. (num:reverse():gsub('(%d%d%d)', '%1,'):reverse()) .. right
|
||
end
|
||
|
||
-- Per-file durations (seconds). Adjust as needed.
|
||
local SFX_DURATION_DEFAULT = 5.0
|
||
local SFX_DURATIONS = {
|
||
["ConvoyChaos.ogg"] = 6.0,
|
||
["LaserOff.ogg"] = 1.0,
|
||
["LaserOn.ogg"] = 1.0,
|
||
["TargetLost.ogg"] = 1.0,
|
||
["TargetSmoke.ogg"] = 5.0, -- assumed from your note
|
||
}
|
||
local function GetSfxDuration(sfx)
|
||
return SFX_DURATIONS[sfx] or SFX_DURATION_DEFAULT
|
||
end
|
||
|
||
-- Unified transmitter (uses the JTAC’s own unit(1))
|
||
local function TransmitRadio(forGroup, freqMHz, sfx, text, dbgTag)
|
||
local unit = forGroup and forGroup:GetUnit(1)
|
||
if not unit then
|
||
if MooseAutolaseDebug then env.info(string.format("[%s] No unit to transmit on", dbgTag or "TX")) end
|
||
return
|
||
end
|
||
local Radio = unit:GetRadio()
|
||
if not Radio then
|
||
if MooseAutolaseDebug then env.info(string.format("[%s] No Radio on %s", dbgTag or "TX", unit:GetName())) end
|
||
return
|
||
end
|
||
Radio:SetFrequency(freqMHz)
|
||
Radio:SetModulation(AM)
|
||
Radio:SetPower(100)
|
||
Radio:SetFileName(sfx)
|
||
Radio:SetSubtitle(text, 60)
|
||
Radio:Broadcast()
|
||
if MooseAutolaseDebug then
|
||
env.info(string.format("[%s] tx @ %.3f MHz AM via %s (sfx=%s)", dbgTag or "TX", freqMHz, unit:GetName(), sfx))
|
||
end
|
||
end
|
||
|
||
-----------------------------------------------------------------
|
||
------ ALPHA
|
||
-----------------------------------------------------------------
|
||
if GROUP:FindByName("JTAC Alpha") then
|
||
if MooseAutolaseDebug then env.info("------JTAC Alpha Located-------") end
|
||
|
||
local JtacAlpha = GROUP:FindByName("JTAC Alpha")
|
||
local JtacAlphaCoord = JtacAlpha:GetCoordinate()
|
||
local AlphaDroneUnit = JtacAlpha:GetUnit(1)
|
||
local JtacAlphaFlightgroup = FLIGHTGROUP:New(JtacAlpha)
|
||
local JtacAlphaName = JtacAlpha:GetName()
|
||
|
||
if MooseAutolaseDebug then
|
||
MESSAGE:New("SETTING UP JTAC ALPHA FOR AUTOLASE (MOOSE)", 5, "RETRIBUTION", false):ToAll():ToLog()
|
||
end
|
||
|
||
local AlphaRacetrack = AUFTRAG:NewORBIT_CIRCLE(JtacAlphaCoord, 20000, 120)
|
||
JtacAlphaFlightgroup:SetDefaultInvisible(true)
|
||
JtacAlphaFlightgroup:SetDefaultImmortal(true)
|
||
JtacAlphaFlightgroup:AddMission(AlphaRacetrack)
|
||
|
||
local function _DumpMissionQueue(tag)
|
||
local lines = {}
|
||
local queue = JtacAlphaFlightgroup and JtacAlphaFlightgroup.missionqueue or nil
|
||
|
||
local function safe(call, default)
|
||
local ok, val = pcall(call); return ok and val or default
|
||
end
|
||
local function _describeAuftrag(idx, auf)
|
||
local name = safe(function() return auf:GetName() end,
|
||
safe(function() return auf.Name end, safe(function() return auf.name end, "AUFTRAG")))
|
||
local prio = safe(function() return auf:GetPriority() end,
|
||
safe(function() return auf.Priority end, safe(function() return auf.prio end, "?")))
|
||
local urgent = safe(function() return tostring(auf:GetUrgent()) end,
|
||
safe(function() return tostring(auf.Urgent) end, safe(function() return tostring(auf.urgent) end, "?")))
|
||
return string.format("%02d) %s [prio=%s, urgent=%s]", idx, tostring(name), tostring(prio), tostring(urgent))
|
||
end
|
||
|
||
if type(queue) == "table" then
|
||
local idx = 0
|
||
for i, auf in ipairs(queue) do
|
||
idx = i; table.insert(lines, _describeAuftrag(i, auf))
|
||
end
|
||
for k, auf in pairs(queue) do
|
||
if type(k) ~= "number" or k < 1 or k > idx then
|
||
table.insert(lines,
|
||
_describeAuftrag(#lines + 1, auf))
|
||
end
|
||
end
|
||
else
|
||
table.insert(lines, "(missionqueue not available on this FLIGHTGROUP)")
|
||
end
|
||
|
||
local text = string.format("JTAC %s MissionQueue — %s\n%s", JtacAlphaName, tag or "update",
|
||
table.concat(lines, "\n"))
|
||
if MooseAutolaseDebug then MESSAGE:New(text, 15, "Alpha Queue"):ToAll() end
|
||
end
|
||
|
||
local Alpha_AutolaseSet = SET_GROUP:New():FilterPrefixes("Alpha"):FilterCoalitions("blue"):FilterOnce()
|
||
Alpha_AutolaseSet:AddGroup(JtacAlpha)
|
||
|
||
local Alpha_DroneZone = ZONE_GROUP:New("Alpha_DroneZone", JtacAlpha,
|
||
1852 * JtacAlphaRadiusNM)
|
||
local Alpha_PilotSet = SET_CLIENT:New():FilterCoalitions("blue"):FilterZones({
|
||
Alpha_DroneZone }):FilterActive():FilterStart()
|
||
|
||
local Alpha_RetributionAutolase = AUTOLASE:New(Alpha_AutolaseSet, coalition.side.BLUE,
|
||
JtacAlphaName .. " Autolase", Alpha_PilotSet)
|
||
:SetMaxLasingTargets(JtacAlphaTargetsMax)
|
||
:SetLasingParameters(15000, AutolaseSmokeDurationSec)
|
||
:SetNotifyPilots(false)
|
||
:SetSmokeTargets(JtacAlphaSmoke, SMOKECOLOR.Red)
|
||
:EnableSmokeMenu({ Angle = 30, Distance = 40 })
|
||
|
||
Alpha_RetributionAutolase._currentOrbitAuftrag = AlphaRacetrack
|
||
Alpha_RetributionAutolase._homeCoord = JtacAlphaCoord
|
||
Alpha_RetributionAutolase._altHomeFt = 20000
|
||
Alpha_RetributionAutolase._altTgtFt = 18000
|
||
Alpha_RetributionAutolase._orbitSpeedKts = 120
|
||
Alpha_RetributionAutolase._lastLaserInfo = {}
|
||
|
||
function Alpha_RetributionAutolase:_ReplaceOrbitAt(coord, altFt, reasonTag)
|
||
if not coord then return end
|
||
if self._currentOrbitAuftrag then
|
||
pcall(function() self._currentOrbitAuftrag:Cancel() end); self._currentOrbitAuftrag = nil
|
||
end
|
||
local newOrbit = AUFTRAG:NewORBIT_CIRCLE(coord, altFt or self._altTgtFt, self._orbitSpeedKts)
|
||
newOrbit:SetPriority(1, true, 1)
|
||
JtacAlphaFlightgroup:AddMission(newOrbit)
|
||
self._currentOrbitAuftrag = newOrbit
|
||
if MooseAutolaseDebug then
|
||
env.info(string.format("JTAC %s orbit RECENTERED at %s (alt %d ft) [%s]",
|
||
JtacAlphaName, coord:ToStringMGRS(), altFt or self._altTgtFt, tostring(reasonTag or "update")))
|
||
end
|
||
_DumpMissionQueue(reasonTag or "orbit retask")
|
||
end
|
||
|
||
function Alpha_RetributionAutolase:OnAfterLasing(From, Event, To, LaserSpot)
|
||
if MooseAutolaseDebug then env.info(JtacAlphaName .. " ------ Laser On!") end
|
||
|
||
self._lastLaserInfo = {
|
||
code = LaserSpot and LaserSpot.lasercode or 0,
|
||
mgrs = (LaserSpot and LaserSpot.coordinate) and LaserSpot.coordinate:ToStringMGRS() or "N/A",
|
||
unittype = LaserSpot and LaserSpot.unittype or "Unknown",
|
||
reccename = LaserSpot and LaserSpot.reccename or "Unknown"
|
||
}
|
||
|
||
if LaserSpot and LaserSpot.coordinate then
|
||
self:_ReplaceOrbitAt(LaserSpot.coordinate, self._altTgtFt, "new lase")
|
||
else
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "no coord")
|
||
end
|
||
|
||
-- pilot notifications (+ danger-close friendlies + BR fix + sound selection)
|
||
Alpha_PilotSet:ForEachClient(function(client)
|
||
if client and client:IsAlive() then
|
||
local laserspot = LaserSpot
|
||
if laserspot and laserspot.coordinate then
|
||
local clientCoord = client:GetCoordinate()
|
||
local BRInfo = laserspot.coordinate:ToStringBR(clientCoord)
|
||
local BRInfoClean = (BRInfo or "N/A"):gsub("^%s*:%s*", "")
|
||
|
||
-- DANGER CLOSE detection
|
||
local dangerNote, dangerClose = "", false
|
||
do
|
||
local tgtCoord = laserspot.coordinate
|
||
local DC_RADIUS_M = DangerCloseNM * 1852
|
||
local jtacSide = JtacAlpha:GetCoalition()
|
||
local sideStr = (jtacSide == coalition.side.RED and "red")
|
||
or (jtacSide == coalition.side.BLUE and "blue")
|
||
or "neutral"
|
||
local nearestCoord, nearestDist
|
||
|
||
local FriendlyGroundSet = SET_GROUP:New()
|
||
:FilterCoalitions(sideStr)
|
||
:FilterActive()
|
||
:FilterStart()
|
||
|
||
FriendlyGroundSet:ForEachGroup(function(g)
|
||
if g:IsGround() then
|
||
local c = g:GetCoordinate()
|
||
local d = c and tgtCoord and c:Get2DDistance(tgtCoord) or nil
|
||
if d and d <= DC_RADIUS_M and (not nearestDist or d < nearestDist) then
|
||
nearestDist, nearestCoord = d, c
|
||
end
|
||
end
|
||
end)
|
||
|
||
if nearestCoord and nearestDist then
|
||
dangerClose = true
|
||
nearestCoord:Smoke(SMOKECOLOR.Green, AutolaseSmokeDurationSec)
|
||
|
||
local brtxt = tgtCoord:ToStringBR(nearestCoord) or ""
|
||
local bearing = tonumber(brtxt:match("(%d+)")) or 0
|
||
local dirs = { "north", "northeast", "east", "southeast", "south", "southwest", "west",
|
||
"northwest" }
|
||
local idx = (math.floor((bearing + 22.5) / 45) % 8) + 1
|
||
local cardinal = dirs[idx]
|
||
|
||
local feet = math.floor(nearestDist * 3.28084 + 0.5)
|
||
local feetStr = (feet >= 1000) and FormatWithCommas(feet) or tostring(feet)
|
||
|
||
dangerNote = string.format(
|
||
"\nFriendlies are DANGER CLOSE, %s feet %s of target, marking with green smoke.",
|
||
feetStr, cardinal)
|
||
end
|
||
end
|
||
|
||
local text = string.format(
|
||
"%s is lasing %s code %d\nat %s\n%s%s",
|
||
laserspot.reccename or "Unknown",
|
||
laserspot.unittype or "Unknown",
|
||
laserspot.lasercode or 0,
|
||
laserspot.coordinate:ToStringMGRS(),
|
||
BRInfoClean,
|
||
dangerNote
|
||
)
|
||
|
||
-- choose SFX (unchanged from your working logic)
|
||
local function pickSFX(smokeOn)
|
||
if dangerClose and UseConvoyChaosSFX then return "ConvoyChaos.ogg" end
|
||
return (smokeOn and "TargetSmoke.ogg") or "LaserOn.ogg"
|
||
end
|
||
|
||
-- choose SFX exactly as you already do
|
||
local function pickSFX(smokeOn)
|
||
if dangerClose and UseConvoyChaosSFX then return "ConvoyChaos.ogg" end
|
||
return (smokeOn and "TargetSmoke.ogg") or "LaserOn.ogg"
|
||
end
|
||
local sfx = pickSFX(Alpha_RetributionAutolase.smoketargets)
|
||
local firstDur = GetSfxDuration(sfx)
|
||
|
||
-- UHF now
|
||
TransmitRadio(JtacAlpha, JtacAlphaUHF, sfx, text, "ALPHA UHF")
|
||
|
||
-- VHF after UHF completes (+pad)
|
||
TIMER:New(function()
|
||
TransmitRadio(JtacAlpha, JtacAlphaVHF, sfx, text, "ALPHA VHF")
|
||
end):Start(firstDur + 0.15)
|
||
|
||
MESSAGE:New(text, 60, "Alpha"):ToLog()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
function Alpha_RetributionAutolase:OnAfterTargetDestroyed(From, Event, To, UnitName, RecceName)
|
||
env.info("----JTAC Alpha's target destroyed, returning to home orbit-----")
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "target destroyed")
|
||
end
|
||
|
||
function Alpha_RetributionAutolase:OnAfterTargetLost(From, Event, To, UnitName, RecceName)
|
||
if MooseAutolaseDebug then env.info(JtacAlphaName .. " ------ Target Lost!") end
|
||
|
||
Alpha_PilotSet:ForEachClient(function(client)
|
||
if client and client:IsAlive() then
|
||
local info = self._lastLaserInfo or {}
|
||
local text = string.format("%s LOST TARGET\n%s (code %d) at %s", info.reccename or RecceName,
|
||
info.unittype or "Unknown", info.code or 0, info.mgrs or "Unknown")
|
||
|
||
-- match your previous logic: TargetLost if smoke on, else LaserOff
|
||
local sfx = (Alpha_RetributionAutolase.smoketargets and "TargetLost.ogg") or "LaserOff.ogg"
|
||
local firstDur = GetSfxDuration(sfx)
|
||
|
||
TransmitRadio(JtacAlpha, JtacAlphaUHF, sfx, text, "ALPHA UHF LOST")
|
||
|
||
TIMER:New(function()
|
||
TransmitRadio(JtacAlpha, JtacAlphaVHF, sfx, text, "ALPHA VHF LOST")
|
||
end):Start(firstDur + 0.15)
|
||
|
||
MESSAGE:New(text, 60, "Alpha"):ToLog()
|
||
end
|
||
end)
|
||
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "target lost")
|
||
end
|
||
|
||
if AlphaDroneUnit then
|
||
local unitName = AlphaDroneUnit:GetName()
|
||
local laserCode = 1688
|
||
Alpha_RetributionAutolase:SetRecceLaserCode(unitName, laserCode)
|
||
Alpha_RetributionAutolase:SetRecceSmokeColor(unitName, SMOKECOLOR.Red)
|
||
if MooseAutolaseDebug then env.info(string.format("%s assigned laser code %d", unitName, laserCode)) end
|
||
end
|
||
|
||
_DumpMissionQueue("initial")
|
||
else
|
||
if MooseAutolaseDebug then env.info("------JTAC Alpha NOT Located-------") end
|
||
end
|
||
|
||
-----------------------------------------------------------------
|
||
------ BRAVO
|
||
-----------------------------------------------------------------
|
||
if GROUP:FindByName("JTAC Bravo") then
|
||
if MooseAutolaseDebug then env.info("------JTAC Bravo Located-------") end
|
||
|
||
local JtacBravo = GROUP:FindByName("JTAC Bravo")
|
||
local JtacBravoCoord = JtacBravo:GetCoordinate()
|
||
local BravoDroneUnit = JtacBravo:GetUnit(1)
|
||
local JtacBravoName = JtacBravo:GetName()
|
||
local JtacBravoFlightgroup = FLIGHTGROUP:New(JtacBravo)
|
||
|
||
if MooseAutolaseDebug then
|
||
MESSAGE:New("SETTING UP JTAC BRAVO FOR AUTOLASE (MOOSE)", 5, "RETRIBUTION", false):ToAll():ToLog()
|
||
end
|
||
|
||
local BravoRacetrack = AUFTRAG:NewORBIT_CIRCLE(JtacBravoCoord, 15000, 120)
|
||
JtacBravoFlightgroup:SetDefaultInvisible(true)
|
||
JtacBravoFlightgroup:SetDefaultImmortal(true)
|
||
JtacBravoFlightgroup:AddMission(BravoRacetrack)
|
||
|
||
local function _DumpMissionQueue(tag)
|
||
local lines = {}
|
||
local queue = JtacBravoFlightgroup and JtacBravoFlightgroup.missionqueue or nil
|
||
local function safe(call, default)
|
||
local ok, val = pcall(call); return ok and val or default
|
||
end
|
||
local function _describeAuftrag(idx, auf)
|
||
local name = safe(function() return auf:GetName() end,
|
||
safe(function() return auf.Name end, safe(function() return auf.name end, "AUFTRAG")))
|
||
local prio = safe(function() return auf:GetPriority() end,
|
||
safe(function() return auf.Priority end, safe(function() return auf.prio end, "?")))
|
||
local urgent = safe(function() return tostring(auf:GetUrgent()) end,
|
||
safe(function() return tostring(auf.Urgent) end, safe(function() return tostring(auf.urgent) end, "?")))
|
||
return string.format("%02d) %s [prio=%s, urgent=%s]", idx, tostring(name), tostring(prio), tostring(urgent))
|
||
end
|
||
if type(queue) == "table" then
|
||
local idx = 0
|
||
for i, auf in ipairs(queue) do
|
||
idx = i; table.insert(lines, _describeAuftrag(i, auf))
|
||
end
|
||
for k, auf in pairs(queue) do
|
||
if type(k) ~= "number" or k < 1 or k > idx then
|
||
table.insert(lines,
|
||
_describeAuftrag(#lines + 1, auf))
|
||
end
|
||
end
|
||
else
|
||
table.insert(lines, "(missionqueue not available on this FLIGHTGROUP)")
|
||
end
|
||
local text = string.format("JTAC %s MissionQueue — %s\n%s", JtacBravoName, tag or "update",
|
||
table.concat(lines, "\n"))
|
||
if MooseAutolaseDebug then MESSAGE:New(text, 15, "Bravo Queue"):ToAll() end
|
||
end
|
||
|
||
local Bravo_AutolaseSet = SET_GROUP:New():FilterPrefixes("Bravo"):FilterCoalitions("blue"):FilterOnce()
|
||
Bravo_AutolaseSet:AddGroup(JtacBravo)
|
||
|
||
local Bravo_DroneZone = ZONE_GROUP:New("Bravo_DroneZone", JtacBravo,
|
||
1852 * JtacBravoRadiusNM)
|
||
local Bravo_PilotSet = SET_CLIENT:New():FilterCoalitions("blue"):FilterZones({
|
||
Bravo_DroneZone }):FilterActive():FilterStart()
|
||
|
||
local Bravo_RetributionAutolase = AUTOLASE:New(Bravo_AutolaseSet, coalition.side.BLUE,
|
||
JtacBravoName .. " Autolase", Bravo_PilotSet)
|
||
:SetMaxLasingTargets(JtacBravoTargetsMax)
|
||
:SetLasingParameters(10000, AutolaseSmokeDurationSec)
|
||
:SetNotifyPilots(false)
|
||
:SetSmokeTargets(JtacBravoSmoke, SMOKECOLOR.Red)
|
||
:EnableSmokeMenu({ Angle = 30, Distance = 40 })
|
||
|
||
Bravo_RetributionAutolase._currentOrbitAuftrag = BravoRacetrack
|
||
Bravo_RetributionAutolase._homeCoord = JtacBravoCoord
|
||
Bravo_RetributionAutolase._altHomeFt = 15000
|
||
Bravo_RetributionAutolase._altTgtFt = 16000
|
||
Bravo_RetributionAutolase._orbitSpeedKts = 120
|
||
Bravo_RetributionAutolase._lastLaserInfo = {}
|
||
|
||
function Bravo_RetributionAutolase:_ReplaceOrbitAt(coord, altFt, reasonTag)
|
||
if not coord then return end
|
||
if self._currentOrbitAuftrag then
|
||
pcall(function() self._currentOrbitAuftrag:Cancel() end); self._currentOrbitAuftrag = nil
|
||
end
|
||
local newOrbit = AUFTRAG:NewORBIT_CIRCLE(coord, altFt or self._altTgtFt, self._orbitSpeedKts)
|
||
newOrbit:SetPriority(1, true, 1)
|
||
JtacBravoFlightgroup:AddMission(newOrbit)
|
||
self._currentOrbitAuftrag = newOrbit
|
||
if MooseAutolaseDebug then
|
||
env.info(string.format("JTAC %s orbit RECENTERED at %s (alt %d ft) [%s]",
|
||
JtacBravoName, coord:ToStringMGRS(), altFt or self._altTgtFt, tostring(reasonTag or "update")))
|
||
end
|
||
_DumpMissionQueue(reasonTag or "orbit retask")
|
||
end
|
||
|
||
function Bravo_RetributionAutolase:OnAfterLasing(From, Event, To, LaserSpot)
|
||
if MooseAutolaseDebug then env.info(JtacBravoName .. " ------ Laser On!") end
|
||
|
||
self._lastLaserInfo = {
|
||
code = LaserSpot and LaserSpot.lasercode or 0,
|
||
mgrs = (LaserSpot and LaserSpot.coordinate) and LaserSpot.coordinate:ToStringMGRS() or "N/A",
|
||
unittype = LaserSpot and LaserSpot.unittype or "Unknown",
|
||
reccename = LaserSpot and LaserSpot.reccename or "Unknown"
|
||
}
|
||
|
||
if LaserSpot and LaserSpot.coordinate then
|
||
self:_ReplaceOrbitAt(LaserSpot.coordinate, self._altTgtFt, "new lase")
|
||
else
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "no coord")
|
||
end
|
||
|
||
-- pilot notifications (+ danger-close friendlies + BR fix + sound selection)
|
||
Bravo_PilotSet:ForEachClient(function(client)
|
||
if client and client:IsAlive() then
|
||
local laserspot = LaserSpot
|
||
if laserspot and laserspot.coordinate then
|
||
local clientCoord = client:GetCoordinate()
|
||
local BRInfo = laserspot.coordinate:ToStringBR(clientCoord)
|
||
local BRInfoClean = (BRInfo or "N/A"):gsub("^%s*:%s*", "")
|
||
|
||
-- DANGER CLOSE detection
|
||
local dangerNote, dangerClose = "", false
|
||
do
|
||
local tgtCoord = laserspot.coordinate
|
||
local DC_RADIUS_M = DangerCloseNM * 1852
|
||
local jtacSide = JtacBravo:GetCoalition()
|
||
local sideStr = (jtacSide == coalition.side.RED and "red")
|
||
or (jtacSide == coalition.side.BLUE and "blue")
|
||
or "neutral"
|
||
local nearestCoord, nearestDist
|
||
|
||
local FriendlyGroundSet = SET_GROUP:New()
|
||
:FilterCoalitions(sideStr)
|
||
:FilterActive()
|
||
:FilterStart()
|
||
|
||
FriendlyGroundSet:ForEachGroup(function(g)
|
||
if g:IsGround() then
|
||
local c = g:GetCoordinate()
|
||
local d = c and tgtCoord and c:Get2DDistance(tgtCoord) or nil
|
||
if d and d <= DC_RADIUS_M and (not nearestDist or d < nearestDist) then
|
||
nearestDist, nearestCoord = d, c
|
||
end
|
||
end
|
||
end)
|
||
|
||
if nearestCoord and nearestDist then
|
||
dangerClose = true
|
||
nearestCoord:Smoke(SMOKECOLOR.Green, AutolaseSmokeDurationSec)
|
||
|
||
local brtxt = tgtCoord:ToStringBR(nearestCoord) or ""
|
||
local bearing = tonumber(brtxt:match("(%d+)")) or 0
|
||
local dirs = { "north", "northeast", "east", "southeast", "south", "southwest", "west",
|
||
"northwest" }
|
||
local idx = (math.floor((bearing + 22.5) / 45) % 8) + 1
|
||
local cardinal = dirs[idx]
|
||
|
||
local feet = math.floor(nearestDist * 3.28084 + 0.5)
|
||
local feetStr = (feet >= 1000) and FormatWithCommas(feet) or tostring(feet)
|
||
|
||
dangerNote = string.format(
|
||
"\nFriendlies are DANGER CLOSE, %s feet %s of target, marking with green smoke.",
|
||
feetStr, cardinal)
|
||
end
|
||
end
|
||
|
||
local text = string.format(
|
||
"%s is lasing %s code %d\nat %s\n%s%s",
|
||
laserspot.reccename or "Unknown",
|
||
laserspot.unittype or "Unknown",
|
||
laserspot.lasercode or 0,
|
||
laserspot.coordinate:ToStringMGRS(),
|
||
BRInfoClean,
|
||
dangerNote
|
||
)
|
||
|
||
local function pickSFX(smokeOn)
|
||
if dangerClose and UseConvoyChaosSFX then return "ConvoyChaos.ogg" end
|
||
return (smokeOn and "TargetSmoke.ogg") or "LaserOn.ogg"
|
||
end
|
||
|
||
local function pickSFX(smokeOn)
|
||
if dangerClose and UseConvoyChaosSFX then return "ConvoyChaos.ogg" end
|
||
return (smokeOn and "TargetSmoke.ogg") or "LaserOn.ogg"
|
||
end
|
||
local sfx = pickSFX(Bravo_RetributionAutolase.smoketargets)
|
||
local firstDur = GetSfxDuration(sfx)
|
||
|
||
TransmitRadio(JtacBravo, JtacBravoUHF, sfx, text, "BRAVO UHF")
|
||
|
||
TIMER:New(function()
|
||
TransmitRadio(JtacBravo, JtacBravoVHF, sfx, text, "BRAVO VHF")
|
||
end):Start(firstDur + 0.15)
|
||
|
||
MESSAGE:New(text, 60, "Bravo"):ToLog()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
function Bravo_RetributionAutolase:OnAfterTargetDestroyed(From, Event, To, UnitName, RecceName)
|
||
env.info("----JTAC Bravo's target destroyed, returning to home orbit-----")
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "target destroyed")
|
||
end
|
||
|
||
function Bravo_RetributionAutolase:OnAfterTargetLost(From, Event, To, UnitName, RecceName)
|
||
if MooseAutolaseDebug then env.info(JtacBravoName .. " ------ Target Lost!") end
|
||
|
||
Bravo_PilotSet:ForEachClient(function(client)
|
||
if client and client:IsAlive() then
|
||
local info = self._lastLaserInfo or {}
|
||
local text = string.format("%s LOST TARGET\n%s (code %d) at %s", info.reccename or RecceName,
|
||
info.unittype or "Unknown", info.code or 0, info.mgrs or "Unknown")
|
||
|
||
local sfx = (Bravo_RetributionAutolase.smoketargets and "TargetLost.ogg") or "LaserOff.ogg"
|
||
local firstDur = GetSfxDuration(sfx)
|
||
|
||
TransmitRadio(JtacBravo, JtacBravoUHF, sfx, text, "BRAVO UHF LOST")
|
||
|
||
TIMER:New(function()
|
||
TransmitRadio(JtacBravo, JtacBravoVHF, sfx, text, "BRAVO VHF LOST")
|
||
end):Start(firstDur + 0.15)
|
||
|
||
MESSAGE:New(text, 60, "Bravo"):ToLog()
|
||
end
|
||
end)
|
||
|
||
self:_ReplaceOrbitAt(self._homeCoord, self._altHomeFt, "target lost")
|
||
end
|
||
|
||
if BravoDroneUnit then
|
||
local unitName = BravoDroneUnit:GetName()
|
||
local laserCode = 1688
|
||
Bravo_RetributionAutolase:SetRecceLaserCode(unitName, laserCode)
|
||
Bravo_RetributionAutolase:SetRecceSmokeColor(unitName, SMOKECOLOR.Red)
|
||
if MooseAutolaseDebug then env.info(string.format("%s assigned laser code %d", unitName, laserCode)) end
|
||
end
|
||
|
||
_DumpMissionQueue("initial")
|
||
else
|
||
if MooseAutolaseDebug then env.info("------JTAC Bravo NOT Located-------") end
|
||
end
|
||
|
||
env.info("-----DCSRetribution|MOOSE Autolase plugin - configuration end ------")
|