mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
New Moose-based Autolase alternative with UI options (#565)
This commit is contained in:
parent
d2aa615133
commit
e8190748eb
610
resources/plugins/MooseAutolase/Plugin_Autolase_JTAC.lua
Normal file
610
resources/plugins/MooseAutolase/Plugin_Autolase_JTAC.lua
Normal file
@ -0,0 +1,610 @@
|
||||
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 ------")
|
||||
BIN
resources/plugins/MooseAutolase/SoundFiles/ConvoyChaos.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/ConvoyChaos.ogg
Normal file
Binary file not shown.
BIN
resources/plugins/MooseAutolase/SoundFiles/LaserOff.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/LaserOff.ogg
Normal file
Binary file not shown.
BIN
resources/plugins/MooseAutolase/SoundFiles/LaserOn.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/LaserOn.ogg
Normal file
Binary file not shown.
BIN
resources/plugins/MooseAutolase/SoundFiles/RadioOn.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/RadioOn.ogg
Normal file
Binary file not shown.
BIN
resources/plugins/MooseAutolase/SoundFiles/TargetLost.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/TargetLost.ogg
Normal file
Binary file not shown.
BIN
resources/plugins/MooseAutolase/SoundFiles/TargetSmoke.ogg
Normal file
BIN
resources/plugins/MooseAutolase/SoundFiles/TargetSmoke.ogg
Normal file
Binary file not shown.
83
resources/plugins/MooseAutolase/plugin.json
Normal file
83
resources/plugins/MooseAutolase/plugin.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"nameInUI": "Moose Autolase",
|
||||
"defaultValue": false,
|
||||
"specificOptions": [
|
||||
{
|
||||
"nameInUI": "JTAC Alpha - Smoke On/Off",
|
||||
"mnemonic": "JtacAlphaSmoke",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Alpha - UHF Frequency",
|
||||
"mnemonic": "JtacAlphaUHF",
|
||||
"defaultValue": 250
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Alpha - VHF Frequency",
|
||||
"mnemonic": "JtacAlphaVHF",
|
||||
"defaultValue": 123
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Alpha - Maximum Lasing Targets",
|
||||
"mnemonic": "JtacAlphaTargetsMax",
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Alpha - Notify Radius (NM)",
|
||||
"mnemonic": "JtacAlphaRadiusNM",
|
||||
"defaultValue": 50
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Bravo - Smoke On/Off",
|
||||
"mnemonic": "JtacBravoSmoke",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Bravo - UHF Frequency",
|
||||
"mnemonic": "JtacBravoUHF",
|
||||
"defaultValue": 251
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Bravo - VHF Frequency",
|
||||
"mnemonic": "JtacBravoVHF",
|
||||
"defaultValue": 124
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Bravo - Maximum Lasing Targets",
|
||||
"mnemonic": "JtacBravoTargetsMax",
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"nameInUI": "JTAC Bravo - Notify Radius (NM)",
|
||||
"mnemonic": "JtacBravoRadiusNM",
|
||||
"defaultValue": 50
|
||||
},
|
||||
{
|
||||
"nameInUI": "Danger Close Radius (NM)",
|
||||
"mnemonic": "DangerCloseNM",
|
||||
"minimumValue": 0.1,
|
||||
"maximumValue": 2,
|
||||
"defaultValue": 1.0
|
||||
},
|
||||
{
|
||||
"nameInUI": "Debug Mode",
|
||||
"mnemonic": "MooseAutolaseDebug",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"scriptsWorkOrders": [],
|
||||
"configurationWorkOrders": [
|
||||
{
|
||||
"file": "Plugin_Autolase_JTAC.lua",
|
||||
"mnemonic": "MooseAutolase-config"
|
||||
}
|
||||
],
|
||||
"otherResourceFiles": [
|
||||
"SoundFiles/TargetLost.ogg",
|
||||
"SoundFiles/TargetSmoke.ogg",
|
||||
"SoundFiles/LaserOn.ogg",
|
||||
"SoundFiles/LaserOff.ogg",
|
||||
"SoundFiles/RadioOn.ogg",
|
||||
"SoundFiles/ConvoyChaos.ogg"
|
||||
]
|
||||
}
|
||||
@ -10,5 +10,6 @@
|
||||
"lotatc",
|
||||
"skynetiads",
|
||||
"splashdamage2",
|
||||
"MooseAutolase",
|
||||
"MooseMarkerOps"
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user