From f438c73f5a5536bf0e5b6949981c52d9413af167 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:30:30 +0200 Subject: [PATCH 01/10] Created TUM.supportWingmen table --- Script/The Universal Mission/Mission.lua | 2 ++ .../The Universal Mission/SupportWingmen.lua | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Script/The Universal Mission/SupportWingmen.lua diff --git a/Script/The Universal Mission/Mission.lua b/Script/The Universal Mission/Mission.lua index 8ab8294..3d36e63 100644 --- a/Script/The Universal Mission/Mission.lua +++ b/Script/The Universal Mission/Mission.lua @@ -29,6 +29,7 @@ do local function closeMission(removeAllUnits) if removeAllUnits then + TUM.wingmen.removeAll() TUM.airForce.removeAll() TUM.ambientWorld.removeAll() TUM.enemyAirDefense.removeAll() @@ -73,6 +74,7 @@ do TUM.supportAWACS.create() -- Create the AWACS aircraft if it wasn't airborne already + TUM.wingmen.create() TUM.enemyAirDefense.create() -- Must be called once objectives have been created TUM.airForce.create() -- Must be called once objectives have been created TUM.missionMenu.create() -- Must be called once objectives have been created diff --git a/Script/The Universal Mission/SupportWingmen.lua b/Script/The Universal Mission/SupportWingmen.lua new file mode 100644 index 0000000..3ccc488 --- /dev/null +++ b/Script/The Universal Mission/SupportWingmen.lua @@ -0,0 +1,24 @@ +-- ==================================================================================== +-- TUM.SUPPORTWINGMEN - HANDLES THE PLAYER'S WINGMEN +-- ==================================================================================== +-- ==================================================================================== + +TUM.supportWingmen = {} + +do + local wingmenGroupID = nil + + function TUM.supportWingmen.create() + if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer + TUM.supportWingmen.removeAll() -- Destroy all pre-existing wingmen + TUM.log("Creating wingmen...") + end + + function TUM.supportWingmen.removeAll() + if wingmenGroupID then TUM.log("Removing all wingmen...") end + + DCSEx.world.destroyGroupByID(wingmenGroupID) + + wingmenGroupID = nil + end +end From 9cd6c057fc4719d1f415a35e904ea1cd7eb38d1e Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:01:03 +0200 Subject: [PATCH 02/10] Added "follow" task for spawned aircraft --- Script/DCS extensions/UnitGroupMaker.lua | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Script/DCS extensions/UnitGroupMaker.lua b/Script/DCS extensions/UnitGroupMaker.lua index db466b0..3e54d0e 100644 --- a/Script/DCS extensions/UnitGroupMaker.lua +++ b/Script/DCS extensions/UnitGroupMaker.lua @@ -101,6 +101,26 @@ do return groupTable end + local function setAircraftTaskFollow(groupTable, followedGroupID) + groupTable.task = "Escort" + + table.insert(groupTable.route.points[1].task.params.tasks, + { + ["enabled"] = true, + ["auto"] = true, + ["id"] = "Follow", + ["number"] = #groupTable.route.points[1].task.params.tasks + 1, + ["params"] = { + groupId = followedGroupID, + pos = { x = -100, y = 0, z = -100 }, + lastWptIndexFlag = false, + lastWptIndex = -1 + }, + }) + + return groupTable + end + local function setAircraftTaskOrbit(groupTable, options) -- TODO: oval orbit table.insert(groupTable.route.points[#groupTable.route.points].task.params.tasks, @@ -341,7 +361,11 @@ do if isAirUnit then if options.taskAwacs then setAircraftTaskAwacs(groupTable) end if options.taskCAP then setAircraftTaskCAP(groupTable) end - setAircraftTaskOrbit(groupTable, options) + if options.taskFollow then + setAircraftTaskFollow(groupTable, options.taskFollow) + else + setAircraftTaskOrbit(groupTable, options) + end groupCallsign = DCSEx.unitCallsignMaker.getCallsign(unitTypes[1]) groupTable.name = groupCallsign.name end From a5c6f4f75399965ba209b082630ce88006ef1407 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:01:14 +0200 Subject: [PATCH 03/10] Added wingman spawning logic --- .../The Universal Mission/SupportWingmen.lua | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Script/The Universal Mission/SupportWingmen.lua b/Script/The Universal Mission/SupportWingmen.lua index 3ccc488..b8f3770 100644 --- a/Script/The Universal Mission/SupportWingmen.lua +++ b/Script/The Universal Mission/SupportWingmen.lua @@ -12,6 +12,31 @@ do if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer TUM.supportWingmen.removeAll() -- Destroy all pre-existing wingmen TUM.log("Creating wingmen...") + + local player = world:getPlayer() + if not player then return end + + local count = 1 -- TODO + + local groupInfo = DCSEx.unitGroupMaker.create( + TUM.settings.getPlayerCoalition(), + Group.Category.AIRPLANE, -- TODO: or helicopter! + DCSEx.math.randomPointInCircle(DCSEx.math.vec3ToVec2(player:getPoint()), 500, 250), + { player:getTypeName() }, + { + silenced = true, + taskFollow = DCSEx.dcs.getObjectIDAsNumber(player:getGroup()), + unlimitedFuel = true + } + ) + + if not groupInfo then + TUM.log("Failed to spawn AI wingmen", TUM.logLevel.WARNING) + return + end + + TUM.log("Spawned AI wingmen") + wingmenGroupID = groupInfo.groupID end function TUM.supportWingmen.removeAll() From baa8d9160d8d3b3627e8ae92bf444002f9064d46 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:06:55 +0200 Subject: [PATCH 04/10] Corrected typo in comment --- Script/Script.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Script/Script.lua b/Script/Script.lua index b5b517e..86771f6 100644 --- a/Script/Script.lua +++ b/Script/Script.lua @@ -107,11 +107,12 @@ do function eventHandler:onEvent(event) if not event then return end -- No event - TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages with interrupt the "ambient" ones + TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages will interrupt the "ambient" ones TUM.ambientWorld.onEvent(event) TUM.objectives.onEvent(event) TUM.playerScore.onEvent(event) TUM.mission.onEvent(event) + TUM.supportWingmen.onEvent(event) end function TUM.onEvent(event) From 9d0e1084cf6a2dd6c663a45265680a8ea956ace6 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:07:09 +0200 Subject: [PATCH 05/10] Added "simulate player takeoff" command --- Script/The Universal Mission/DebugMenu.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Script/The Universal Mission/DebugMenu.lua b/Script/The Universal Mission/DebugMenu.lua index 58968c2..7ac34ba 100644 --- a/Script/The Universal Mission/DebugMenu.lua +++ b/Script/The Universal Mission/DebugMenu.lua @@ -55,6 +55,13 @@ do TUM.playerScore.awardCompletedObjective() end + local function doSimulatePlayerTakeOff() + local playerUnit = coalition.getPlayers(TUM.settings.getPlayerCoalition())[1] + + local takeOffEvent = { id = world.event.S_EVENT_TAKEOFF, initiator = playerUnit } + TUM.onEvent(takeOffEvent) + end + local function doSimulatePlayerLanding() local playerUnit = coalition.getPlayers(TUM.settings.getPlayerCoalition())[1] @@ -72,6 +79,7 @@ do missionCommands.addCommand("Detonate \"boom\" map markers", rootMenu, doMarkersBoom, nil) missionCommands.addCommand("Detonate aircraft near \"airboom\" map markers", rootMenu, doMarkersAirBoom, nil) missionCommands.addCommand("Award 100 points and 1 objective", rootMenu, doAwardPointsAndObjectives, nil) + missionCommands.addCommand("Simulate player takeoff", rootMenu, doSimulatePlayerTakeOff, nil) missionCommands.addCommand("Simulate player landing", rootMenu, doSimulatePlayerLanding, nil) missionCommands.addCommand("Reset player stats", rootMenu, TUM.playerCareer.reset, nil) end From 1b1136c0d35b5230bbf79311be3f2bc2717c7fe2 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:07:43 +0200 Subject: [PATCH 06/10] Fixed typo in TUM.supportWingmen.removeAll() --- Script/The Universal Mission/Mission.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Script/The Universal Mission/Mission.lua b/Script/The Universal Mission/Mission.lua index 3d36e63..59c4d02 100644 --- a/Script/The Universal Mission/Mission.lua +++ b/Script/The Universal Mission/Mission.lua @@ -29,7 +29,7 @@ do local function closeMission(removeAllUnits) if removeAllUnits then - TUM.wingmen.removeAll() + TUM.supportWingmen.removeAll() TUM.airForce.removeAll() TUM.ambientWorld.removeAll() TUM.enemyAirDefense.removeAll() @@ -73,8 +73,6 @@ do end TUM.supportAWACS.create() -- Create the AWACS aircraft if it wasn't airborne already - - TUM.wingmen.create() TUM.enemyAirDefense.create() -- Must be called once objectives have been created TUM.airForce.create() -- Must be called once objectives have been created TUM.missionMenu.create() -- Must be called once objectives have been created From 1f5993c87fda9299900a446b1e157f92a65562a3 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:07:50 +0200 Subject: [PATCH 07/10] Added DCSEx.unitCallsignMaker.getNextGroupCallSign(callsign) --- Script/DCS extensions/UnitCallsignMaker.lua | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Script/DCS extensions/UnitCallsignMaker.lua b/Script/DCS extensions/UnitCallsignMaker.lua index d00e5b7..03163c9 100644 --- a/Script/DCS extensions/UnitCallsignMaker.lua +++ b/Script/DCS extensions/UnitCallsignMaker.lua @@ -198,6 +198,28 @@ do return callsignTable end + function DCSEx.unitCallsignMaker.getNextGroupCallSign(callsign) + if not callsign then return nil end + local callsignName = callsign:sub(1, #callsign - 2):lower() + local callsignGroupNumber = tonumber(callsign:sub(#callsign - 2, #callsign - 1)) + local callsignUnitNumber = tonumber(callsign:sub(#callsign - 1, #callsign)) + + for csType,_ in pairs(CALLSIGNS) do + for csNameIndex,_ in pairs(CALLSIGNS[csType]) do + if CALLSIGNS[csType][csNameIndex]:lower() == callsignName then + return { + [1] = csNameIndex, + [2] = callsignGroupNumber, + [3] = callsignUnitNumber, + ["name"] = CALLSIGNS[csType][csNameIndex]..tostring(callsignGroupNumber), + } + end + end + end + + return nil + end + do local missionGroups = DCSEx.envMission.getGroups() for _,g in ipairs(missionGroups) do From 17596987d03816abc5d670e4df1f1255cd6f152d Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:08:07 +0200 Subject: [PATCH 08/10] Wingmen now generated on takeoff and destroyed on landing --- .../The Universal Mission/SupportWingmen.lua | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Script/The Universal Mission/SupportWingmen.lua b/Script/The Universal Mission/SupportWingmen.lua index b8f3770..eac35d1 100644 --- a/Script/The Universal Mission/SupportWingmen.lua +++ b/Script/The Universal Mission/SupportWingmen.lua @@ -8,22 +8,22 @@ TUM.supportWingmen = {} do local wingmenGroupID = nil - function TUM.supportWingmen.create() - if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer + local function createWingmen() TUM.supportWingmen.removeAll() -- Destroy all pre-existing wingmen TUM.log("Creating wingmen...") local player = world:getPlayer() if not player then return end - local count = 1 -- TODO + local playerTypeName = player:getTypeName() local groupInfo = DCSEx.unitGroupMaker.create( TUM.settings.getPlayerCoalition(), Group.Category.AIRPLANE, -- TODO: or helicopter! DCSEx.math.randomPointInCircle(DCSEx.math.vec3ToVec2(player:getPoint()), 500, 250), - { player:getTypeName() }, + { playerTypeName, playerTypeName }, { + callsign = DCSEx.unitCallsignMaker.getNextGroupCallSign(player:getCallsign()), silenced = true, taskFollow = DCSEx.dcs.getObjectIDAsNumber(player:getGroup()), unlimitedFuel = true @@ -46,4 +46,22 @@ do wingmenGroupID = nil end + + ------------------------------------- + -- Called when an event is raised + -- @param event The DCS World event + ------------------------------------- + function TUM.supportWingmen.onEvent(event) + if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer + if TUM.mission.getStatus() == TUM.mission.status.NONE then return end + if not event.initiator then return end + if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end + if not event.initiator:getPlayerName() then return end + + if event.id == world.event.S_EVENT_TAKEOFF then -- Create wingmen on takeoff + createWingmen() + elseif event.id == world.event.S_EVENT_LAND then + TUM.supportWingmen.removeAll() -- Remove wingmen on landing + end + end end From d18e6b606595d00ab5692119c1c923165e42e12b Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:20:42 +0200 Subject: [PATCH 09/10] Added radio messages for "flight, orbit" and "flight, rejoin" --- Script/Library/RadioMessages.lua | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Script/Library/RadioMessages.lua b/Script/Library/RadioMessages.lua index 00cf9d7..7065d22 100644 --- a/Script/Library/RadioMessages.lua +++ b/Script/Library/RadioMessages.lua @@ -70,6 +70,21 @@ Library.radioMessages = { pilotWarningMANPADS = { "MANPADS launch! Flare, flare, flare!", "Flight, MANPADS in the air. Dump flares, now!", "Coming from the ground, MANPADS hot!", "Go defensive, MANPADS off your nose! Flare, flare!", "MANPADS just came up from the deck, break hard and pop everything!" }, pilotWarningSAMLaunch = { "Spike! SAM just launched, break!", "SAM up! Defensive now!", "Launch! SAM, coming fast, pump chaff, go cold!", "SAM in the air, break hard!", "SAM fired, visual smoke! Extend, extend!" }, + pilotWingmanOrbit = { + "Wilco, holding here.", + "Copy, orbiting now.", + "Roger, in the hold.", + "Affirm, setting up the orbit.", + "Orbiting at your pos." + }, + pilotWingmanRejoin = { + "Off the perch, rejoining your side.", + "Tally visual, coming to you.", + "Clear, rejoining to route.", + "Pushing up to formation.", + "Visual, sliding back into position." + }, + atcSafeLanding = { "Be advised: $1 is wheels down at $2 and clear of runway.", "All aircraft, $1 has landed at $2 and vacated active. Runway is open for next inbound.", "Traffic, $1 is on deck at $2 and heading to parking. Runway clear.", "All flights, $1 just rolled out at $2 and cleared the active.", "Heads up, $1 landed at $2 and moving to the ramp. Runway available for next approach." }, atcSafeLandingPlayer = { "$1, wheels on deck, welcome back. You may taxi to the parking area.", "$1, good copy on landing. Exit when able, proceed to the parking area.", "$1, touchdown confirmed. Continue to parking.", "$1, welcome home. Clear of runway and taxi to parking area.", "$1, nice landing. Taxi to parking when ready." }, @@ -186,6 +201,21 @@ Library.radioMessages = { "Command, pass coordinates for objective $1.", "Command, confirm grid on objective $1." }, + playerFlightOrbit = { + "Flight, orbit my position.", + "Flight, set up an orbit on me.", + "Flight, hold on me.", + "Flight, anchor on my current pos.", + "Flight, orbit overhead" + }, + playerFlightRejoin = + { + "Flight, rejoin my side", + "Flight, push it up, rejoin formation.", + "Flight, come back to route.", + "Flight, tighten it up.", + "Flight, rejoin tactical." + }, playerJTACSmoke = { "$1, request smoke on objective $2, over.", "$1, mark objective $2 with smoke, how copy?", From 86d3159300026593228d0a5b476890823828268f Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:20:57 +0200 Subject: [PATCH 10/10] Added wingmen F10 menu --- Script/The Universal Mission/MissionMenu.lua | 1 + .../The Universal Mission/SupportWingmen.lua | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/Script/The Universal Mission/MissionMenu.lua b/Script/The Universal Mission/MissionMenu.lua index ebd0c1d..9bf13d7 100644 --- a/Script/The Universal Mission/MissionMenu.lua +++ b/Script/The Universal Mission/MissionMenu.lua @@ -46,6 +46,7 @@ do end end + TUM.supportWingmen.createMenu() TUM.supportAWACS.createMenu() if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then -- If not multiplayer, add "show mission score" command diff --git a/Script/The Universal Mission/SupportWingmen.lua b/Script/The Universal Mission/SupportWingmen.lua index eac35d1..714afd6 100644 --- a/Script/The Universal Mission/SupportWingmen.lua +++ b/Script/The Universal Mission/SupportWingmen.lua @@ -6,8 +6,62 @@ TUM.supportWingmen = {} do + TUM.supportWingmen.orderID = { + ORBIT = 1, + REJOIN = 2, + } + local wingmenGroupID = nil + local function doWingmenOrder(orderID) + local player = world:getPlayer() + if not player then return end + + if orderID == TUM.supportWingmen.orderID.ORBIT then + TUM.radio.playForAll("playerFlightOrbit", nil, player:getCallsign(), false) + elseif orderID == TUM.supportWingmen.orderID.REJOIN then + TUM.radio.playForAll("playerFlightRejoin", nil, player:getCallsign(), false) + end + + if not wingmenGroupID then return end + local wingmenGroup = DCSEx.world.getGroupByID(wingmenGroupID) + if not wingmenGroup then return end + if #wingmenGroup:getUnits() == 0 then return end + local wingmenCtrl = wingmenGroup:getController() + if not wingmenCtrl then return end + + local wingmanCallsign = wingmenGroup:getUnit(1):getCallsign() + + local taskTable = nil + + if orderID == TUM.supportWingmen.orderID.ORBIT then + taskTable = { + id = "Orbit", + params = { + pattern = "Circle", + point = DCSEx.math.vec3ToVec2(player:getPoint()), + altitude = player:getPoint().y + } + } + TUM.radio.playForAll("pilotWingmanOrbit", nil, wingmanCallsign, true) + elseif orderID == TUM.supportWingmen.orderID.REJOIN then + taskTable = { + id = "Follow", + params = { + groupId = DCSEx.dcs.getObjectIDAsNumber(world:getPlayer():getGroup()), + pos = { x = -100, y = 0, z = -100 }, + lastWptIndexFlag = false, + lastWptIndex = -1 + } + } + TUM.radio.playForAll("pilotWingmanRejoin", nil, wingmanCallsign, true) + end + + if not taskTable then return end + + wingmenCtrl:setTask(taskTable) + end + local function createWingmen() TUM.supportWingmen.removeAll() -- Destroy all pre-existing wingmen TUM.log("Creating wingmen...") @@ -47,6 +101,14 @@ do wingmenGroupID = nil end + function TUM.supportWingmen.createMenu() + if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer + + local rootPath = missionCommands.addSubMenu("Flight") + missionCommands.addCommand("Orbit", rootPath, doWingmenOrder, TUM.supportWingmen.orderID.ORBIT) + missionCommands.addCommand("Rejoin", rootPath, doWingmenOrder, TUM.supportWingmen.orderID.REJOIN) + end + ------------------------------------- -- Called when an event is raised -- @param event The DCS World event