From f46b3d31b07f1ce7b2bf9dd51d5f3fb3ff9368a3 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:09:05 +0200 Subject: [PATCH] Added "strike buildings", "attack infantry" and "attack MANPADS" commands --- Script/The Universal Mission/WingmenMenu.lua | 10 +- .../The Universal Mission/WingmenTasking.lua | 153 +++++++++++++++--- 2 files changed, 137 insertions(+), 26 deletions(-) diff --git a/Script/The Universal Mission/WingmenMenu.lua b/Script/The Universal Mission/WingmenMenu.lua index 9c91b85..221dcc1 100644 --- a/Script/The Universal Mission/WingmenMenu.lua +++ b/Script/The Universal Mission/WingmenMenu.lua @@ -90,17 +90,18 @@ do -- Ground -- (radioTargetName must be singular) engageSubPath = missionCommands.addSubMenu("Ground", engagePath) - missionCommands.addCommand("Any ground", engageSubPath, radioCommandEngage, { attributes = {"Tanks", "Trucks", "Artillery", "IFV", "APC"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "ground" }) + missionCommands.addCommand("Any vehicles", engageSubPath, radioCommandEngage, { attributes = {"Tanks", "Trucks", "Artillery", "IFV", "APC"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "ground" }) missionCommands.addCommand("Armor", engageSubPath, radioCommandEngage, { attributes = {"Tanks", "IFV", "APC"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "armor" }) missionCommands.addCommand("Artillery", engageSubPath, radioCommandEngage, { attributes = {"Artillery"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "artillery" }) - missionCommands.addCommand("Trucks", engageSubPath, radioCommandEngage, { attributes = {"Trucks"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "unarmed" }) + missionCommands.addCommand("Infantry", engageSubPath, radioCommandEngage, { attributes = {"Infantry"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "infantry" }) + missionCommands.addCommand("Trucks", engageSubPath, radioCommandEngage, { attributes = {"Trucks"}, category = Group.Category.GROUND, radioMessageSuffix = "Ground", radioTargetName = "truck" }) -- Air defense -- (radioTargetName must be singular) engageSubPath = missionCommands.addSubMenu("Air defense", engagePath) missionCommands.addCommand("Any air defense", engageSubPath, radioCommandEngage, { attributes = { "Air Defence" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "air defense" }) missionCommands.addCommand("AAA", engageSubPath, radioCommandEngage, { attributes = { "AAA" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "AAA" }) if not isWW2 then - missionCommands.addCommand("MANPADS", engageSubPath, radioCommandEngage, { attributes = { "SR SAM", "IR Guided SAM" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "MANPADS" }) + missionCommands.addCommand("MANPADS", engageSubPath, radioCommandEngage, { attributes = { "MANPADS" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "MANPADS" }) missionCommands.addCommand("Short range SAMs", engageSubPath, radioCommandEngage, { attributes = { "SR SAM", "IR Guided SAM" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "SHORAD" }) missionCommands.addCommand("Long-range SAMs", engageSubPath, radioCommandEngage, { attributes = { "LR SAM", "MR SAM" }, category = Group.Category.GROUND, radioMessageSuffix = "AirDefense", radioTargetName = "HIMAD" }) end @@ -110,6 +111,9 @@ do missionCommands.addCommand("Any ship", engageSubPath, radioCommandEngage, { attributes = nil, category = Group.Category.SHIP, radioMessageSuffix = "Ships", radioTargetName = "ships" }) missionCommands.addCommand("Armed ships", engageSubPath, radioCommandEngage, { attributes = { "Armed ships" }, category = Group.Category.SHIP, radioMessageSuffix = "Ships", radioTargetName = "armed ships" }) missionCommands.addCommand("Cargo ships", engageSubPath, radioCommandEngage, { attributes = { "Unarmed ships" }, category = Group.Category.SHIP, radioMessageSuffix = "Ships", radioTargetName = "cargo ships" }) + + -- Structures + missionCommands.addCommand("Structures", engagePath, radioCommandEngage, { attributes = nil, category = "strike", radioMessageSuffix = "Strike", radioTargetName = "building" }) ------------------------------------------------------ -- missionCommands.addCommand("Go to map marker "..TUM.wingmenTasking.DEFAULT_MARKER_TEXT:upper(), rootPath, radioCommandGoToMapMarker, nil) diff --git a/Script/The Universal Mission/WingmenTasking.lua b/Script/The Universal Mission/WingmenTasking.lua index 4cf51dd..d338362 100644 --- a/Script/The Universal Mission/WingmenTasking.lua +++ b/Script/The Universal Mission/WingmenTasking.lua @@ -11,7 +11,8 @@ do local mapMarkerMissingWarningAlreadyDisplayed = false -- Was the "map marker missing" warning already displayed during the mission? local targetPointMapMarker = nil - local currentTargetedGroupID = nil + local currentTargetIDorPoint = nil -- ID for groups/statics, point2 for scenery objects + local currentTargetType = nil local function allowWeaponUse(wingmenCtrl, allowUse) allowUse = allowUse or false @@ -20,6 +21,76 @@ do wingmenCtrl:setOption(AI.Option.Air.id.PROHIBIT_AG, not allowUse) end + local function doCommandEngageStrikeTargets() + -- Not a strike mission, so no strike targets + if TUM.settings.getValue(TUM.settings.id.TASKING) ~= DCSEx.enums.taskFamily.STRIKE then return nil end + + local wingmenCtrl = TUM.wingmen.getController() + if not wingmenCtrl then return nil end + + local wingmenPosition = DCSEx.world.getGroupCenter(TUM.wingmen.getGroup()) + + -- Look for nearest strike target + local nearestDistance = 9999999999 + local nearestTarget = nil + local nearestTargetType = nil + + for i=1,TUM.objectives.getCount() do + local obj = TUM.objectives.getObjective(i) + if obj and not obj.completed then + -- TODO: check distance + local objectiveDB = Library.tasks[obj.taskID] + if objectiveDB.targetFamilies and #objectiveDB.targetFamilies > 0 then + allowWeaponUse(wingmenCtrl, true) + + local distanceFromTarget = DCSEx.math.getDistance2D(wingmenPosition, obj.point2) + + if distanceFromTarget < nearestDistance then -- TODO: distanceFromTarget < maxSearchDist + if objectiveDB.targetFamilies[1] == DCSEx.enums.unitFamily.STATIC_SCENERY then + nearestDistance = distanceFromTarget + nearestTargetType = "scenery" + nearestTarget = obj.point2 + elseif obj.unitsID and #obj.unitsID >= 1 then + nearestDistance = distanceFromTarget + nearestTargetType = "static" + nearestTarget = obj.unitsID[1] + end + end + end + end + end + + if not nearestTarget or not nearestTargetType then return nil end + + if nearestTargetType == "static" then + currentTargetType = nearestTargetType + currentTargetIDorPoint = nearestTarget + local taskTable = { + id = "AttackUnit", + params = { + groupAttack = true, + unitId = nearestTarget, + } + } + wingmenCtrl:setTask(taskTable) + return DCSEx.world.getStaticObjectByID(nearestTarget):getPoint() + elseif nearestTargetType == "scenery" then + currentTargetType = nearestTargetType + currentTargetIDorPoint = nearestTarget + local taskTable = { + id = "AttackMapObject", + params = { + groupAttack = true, + point = nearestTarget, + } + } + wingmenCtrl:setTask(taskTable) + return DCSEx.math.vec2ToVec3(nearestTarget, "land") + end + + return nil + end + local function getOrbitTaskTable(point2) return { id = "Orbit", @@ -53,7 +124,17 @@ do local wingmenCtrl = TUM.wingmen.getController() if not wingmenCtrl then return end - allowWeaponUse(wingmenCtrl, true) + -- Strike targets are handled differently + if groupCategory == "strike" then + local targetPoint3 = doCommandEngageStrikeTargets() + + if targetPoint3 then + TUM.radio.playForAll("pilotWingmanEngageStrike", { TUM.wingmen.getFirstWingmanNumber(), DCSEx.dcs.getBRAA(targetPoint3, DCSEx.world.getGroupCenter(TUM.wingmen.getGroup()), false) }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) + else + TUM.radio.playForAll("pilotWingmanEngageNoTarget", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) + end + return + end local detectedContacts = TUM.wingmenContacts.getContacts(groupCategory) local validTargets = {} @@ -80,34 +161,47 @@ do end if #validTargets == 0 then - TUM.radio.playForAll("pilotWingmanEngageNoTarget", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), true) + TUM.radio.playForAll("pilotWingmanEngageNoTarget", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) return end local wingmenPosition = DCSEx.world.getGroupCenter(TUM.wingmen.getGroup()) + validTargets = DCSEx.dcs.getNearestObjects(wingmenPosition, validTargets, 1) local target = validTargets[1] - currentTargetedGroupID = DCSEx.dcs.getGroupIDAsNumber(target:getGroup()) + currentTargetIDorPoint = DCSEx.dcs.getGroupIDAsNumber(target:getGroup()) + currentTargetType = "group" + + -- If target is tough, make everyone attack it. Else, only one aircraft attack to save ammo + local allWingmenShouldAttack = false + if target:hasAttribute("Heavy armed ships") then + allWingmenShouldAttack = true + elseif (target:hasAttribute("MR SAM") or target:hasAttribute("MR SAM")) and target:hasAttribute("SAM TR") then + allWingmenShouldAttack = true + end + local taskTable = { id = "AttackGroup", params = { - groupId = currentTargetedGroupID, + groupAttack = allWingmenShouldAttack, + groupId = currentTargetIDorPoint, } } + allowWeaponUse(wingmenCtrl, true) wingmenCtrl:setTask(taskTable) - -- wingmenCtrl:pushTask(getRejoinTaskTable()) -- Makes sure wingmen rejoin with the player after attack + local targetBRAA = "" local targetInfo = nil local messageSuffix = nil if target:inAir() then messageSuffix = "Air" - targetInfo = Library.objectNames.getGeneric(target)..", " - targetInfo = targetInfo..DCSEx.dcs.getBRAA(target:getPoint(), wingmenPosition, true) + targetInfo = Library.objectNames.getGeneric(target) + targetBRAA = targetInfo..DCSEx.dcs.getBRAA(target:getPoint(), wingmenPosition, true) else messageSuffix = "Surface" - targetInfo = Library.objectNames.getGeneric(target)..", " - targetInfo = targetInfo..DCSEx.dcs.getBRAA(target:getPoint(), wingmenPosition, false) + targetInfo = Library.objectNames.getGeneric(target) + targetBRAA = targetInfo..DCSEx.dcs.getBRAA(target:getPoint(), wingmenPosition, false) end -- Mark the last targeted point in debug mode @@ -120,7 +214,7 @@ do trigger.action.markToAll(targetPointMapMarker, "Last wingmen attack point", target:getPoint(), true) end - TUM.radio.playForAll("pilotWingmanEngage"..messageSuffix, { TUM.wingmen.getFirstWingmanNumber(), targetInfo }, TUM.wingmen.getFirstWingmanCallsign(), true) + TUM.radio.playForAll("pilotWingmanEngage"..messageSuffix, { TUM.wingmen.getFirstWingmanNumber(), targetInfo, targetBRAA }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) end function TUM.wingmenTasking.commandGoToMapMarker(markerText, delayRadioAnswer) @@ -137,16 +231,16 @@ do local wingmenCtrl = TUM.wingmen.getController() if not wingmenCtrl then return end - allowWeaponUse(wingmenCtrl, false) if not mapMarker then - -- TUM.radio.playForAll("pilotWingmanGoToMarkerNoMarker", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), true) + -- TUM.radio.playForAll("pilotWingmanGoToMarkerNoMarker", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) return end - currentTargetedGroupID = nil + currentTargetIDorPoint = nil + allowWeaponUse(wingmenCtrl, false) wingmenCtrl:setTask(getOrbitTaskTable(DCSEx.math.vec3ToVec2(mapMarker.pos))) - -- TUM.radio.playForAll("pilotWingmanGoToMarker", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), true) + -- TUM.radio.playForAll("pilotWingmanGoToMarker", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) end function TUM.wingmenTasking.commandOrbit(delayRadioAnswer) @@ -158,7 +252,7 @@ do allowWeaponUse(wingmenCtrl, false) - currentTargetedGroupID = nil + currentTargetIDorPoint = nil wingmenCtrl:setTask(getOrbitTaskTable(DCSEx.world.getGroupCenter(TUM.wingmen.getGroup()))) TUM.radio.playForAll("pilotWingmanOrbit", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) end @@ -174,9 +268,8 @@ do local wingmenCtrl = TUM.wingmen.getController() if not wingmenCtrl then return end + currentTargetIDorPoint = nil allowWeaponUse(wingmenCtrl, false) - - currentTargetedGroupID = nil wingmenCtrl:setTask(getRejoinTaskTable(formationDistance)) if not silent then TUM.radio.playForAll("pilotWingmanRejoin", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) @@ -192,7 +285,7 @@ do if not reportString then if noReportIfNoContacts then return false end - TUM.radio.playForAll("pilotWingmanReportContactsNoJoy", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), true) + TUM.radio.playForAll("pilotWingmanReportContactsNoJoy", { TUM.wingmen.getFirstWingmanNumber() }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) return true else TUM.radio.playForAll("pilotWingmanReportContacts", { TUM.wingmen.getFirstWingmanNumber(), reportString }, TUM.wingmen.getFirstWingmanCallsign(), delayRadioAnswer) @@ -242,12 +335,26 @@ do -- Called on every mission update tick (every 10-20 seconds) ---------------------------------------------------------- function TUM.wingmenTasking.onClockTick() - -- Targeted group is dead? Mark group as nil and rejoin leader - if currentTargetedGroupID then - local tgtGroup = DCSEx.world.getGroupByID(currentTargetedGroupID) + if not currentTargetIDorPoint then return end + + -- Targeted object is dead? Mark currentTargetID as nil and rejoin leader + if currentTargetType == "group" then + local tgtGroup = DCSEx.world.getGroupByID(currentTargetIDorPoint) if not tgtGroup or tgtGroup:getSize() == 0 then TUM.wingmenTasking.commandRejoin(nil, false) - tgtGroup = nil + currentTargetIDorPoint = nil + end + elseif currentTargetType == "static" then + local tgtStatic = DCSEx.world.getStaticObjectByID(currentTargetIDorPoint) + if not tgtStatic then + TUM.wingmenTasking.commandRejoin(nil, false) + currentTargetIDorPoint = nil + end + elseif currentTargetType == "scenery" then + local sceneriesInZone = DCSEx.world.getSceneriesInZone(currentTargetIDorPoint, 5) + if not sceneriesInZone or #sceneriesInZone == 0 or sceneriesInZone[1]:getLife() < 1 then + TUM.wingmenTasking.commandRejoin(nil, false) + currentTargetIDorPoint = nil end end end