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 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 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?", 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) 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 diff --git a/Script/The Universal Mission/Mission.lua b/Script/The Universal Mission/Mission.lua index 8ab8294..59c4d02 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.supportWingmen.removeAll() TUM.airForce.removeAll() TUM.ambientWorld.removeAll() TUM.enemyAirDefense.removeAll() @@ -72,7 +73,6 @@ do end TUM.supportAWACS.create() -- Create the AWACS aircraft if it wasn't airborne already - 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/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 new file mode 100644 index 0000000..714afd6 --- /dev/null +++ b/Script/The Universal Mission/SupportWingmen.lua @@ -0,0 +1,129 @@ +-- ==================================================================================== +-- TUM.SUPPORTWINGMEN - HANDLES THE PLAYER'S WINGMEN +-- ==================================================================================== +-- ==================================================================================== + +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...") + + local player = world:getPlayer() + if not player then return end + + 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), + { playerTypeName, playerTypeName }, + { + callsign = DCSEx.unitCallsignMaker.getNextGroupCallSign(player:getCallsign()), + 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() + if wingmenGroupID then TUM.log("Removing all wingmen...") end + + DCSEx.world.destroyGroupByID(wingmenGroupID) + + 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 + ------------------------------------- + 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