diff --git a/resources/plugins/MooseAutolase/Plugin_Autolase_JTAC.lua b/resources/plugins/MooseAutolase/Plugin_Autolase_JTAC.lua new file mode 100644 index 00000000..a911e6c4 --- /dev/null +++ b/resources/plugins/MooseAutolase/Plugin_Autolase_JTAC.lua @@ -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 ------") diff --git a/resources/plugins/MooseAutolase/SoundFiles/ConvoyChaos.ogg b/resources/plugins/MooseAutolase/SoundFiles/ConvoyChaos.ogg new file mode 100644 index 00000000..353dc1cd Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/ConvoyChaos.ogg differ diff --git a/resources/plugins/MooseAutolase/SoundFiles/LaserOff.ogg b/resources/plugins/MooseAutolase/SoundFiles/LaserOff.ogg new file mode 100644 index 00000000..ed42cd1b Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/LaserOff.ogg differ diff --git a/resources/plugins/MooseAutolase/SoundFiles/LaserOn.ogg b/resources/plugins/MooseAutolase/SoundFiles/LaserOn.ogg new file mode 100644 index 00000000..4843b37b Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/LaserOn.ogg differ diff --git a/resources/plugins/MooseAutolase/SoundFiles/RadioOn.ogg b/resources/plugins/MooseAutolase/SoundFiles/RadioOn.ogg new file mode 100644 index 00000000..5d669fc4 Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/RadioOn.ogg differ diff --git a/resources/plugins/MooseAutolase/SoundFiles/TargetLost.ogg b/resources/plugins/MooseAutolase/SoundFiles/TargetLost.ogg new file mode 100644 index 00000000..4b65f516 Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/TargetLost.ogg differ diff --git a/resources/plugins/MooseAutolase/SoundFiles/TargetSmoke.ogg b/resources/plugins/MooseAutolase/SoundFiles/TargetSmoke.ogg new file mode 100644 index 00000000..adb4c1a8 Binary files /dev/null and b/resources/plugins/MooseAutolase/SoundFiles/TargetSmoke.ogg differ diff --git a/resources/plugins/MooseAutolase/plugin.json b/resources/plugins/MooseAutolase/plugin.json new file mode 100644 index 00000000..9e69fb3a --- /dev/null +++ b/resources/plugins/MooseAutolase/plugin.json @@ -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" + ] +} \ No newline at end of file diff --git a/resources/plugins/plugins.json b/resources/plugins/plugins.json index 2e109ce7..7d50095f 100644 --- a/resources/plugins/plugins.json +++ b/resources/plugins/plugins.json @@ -10,5 +10,6 @@ "lotatc", "skynetiads", "splashdamage2", + "MooseAutolase", "MooseMarkerOps" ] \ No newline at end of file