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",
|
"lotatc",
|
||||||
"skynetiads",
|
"skynetiads",
|
||||||
"splashdamage2",
|
"splashdamage2",
|
||||||
|
"MooseAutolase",
|
||||||
"MooseMarkerOps"
|
"MooseMarkerOps"
|
||||||
]
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user