From ba63c5b9cdc0831534d735f089d86604761912dd Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:09:28 +0200 Subject: [PATCH 1/5] Added Library.environment table --- Script/Library/Environment.lua | 277 +++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 Script/Library/Environment.lua diff --git a/Script/Library/Environment.lua b/Script/Library/Environment.lua new file mode 100644 index 0000000..30b2506 --- /dev/null +++ b/Script/Library/Environment.lua @@ -0,0 +1,277 @@ +Library.environment = {} + +Library.environment.windAmount = { + CALM = 1, + LIGHT_BREEZE = 2, + MODERATE_BREEZE = 3, + STRONG_BREEZE = 4, + GALE = 5, + STORM = 6 +} + +do + local DAYTIME_TABLE = { + ["Afghanistan"] = { + { 05 * 60 + 31, 18 * 60 + 33 }, + { 05 * 60 + 14, 19 * 60 + 00 }, + { 04 * 60 + 40, 19 * 60 + 24 }, + { 03 * 60 + 53, 19 * 60 + 54 }, + { 03 * 60 + 12, 20 * 60 + 27 }, + { 02 * 60 + 54, 20 * 60 + 53 }, + { 03 * 60 + 09, 20 * 60 + 48 }, + { 03 * 60 + 42, 20 * 60 + 12 }, + { 04 * 60 + 11, 19 * 60 + 24 }, + { 04 * 60 + 35, 18 * 60 + 42 }, + { 04 * 60 + 59, 18 * 60 + 15 }, + { 05 * 60 + 22, 18 * 60 + 14 }, + }, + ["Caucasus"] = { + { 08 * 60 + 26, 17 * 60 + 57 }, + { 07 * 60 + 49, 18 * 60 + 30 }, + { 07 * 60 + 14, 19 * 60 + 01 }, + { 07 * 60 + 30, 19 * 60 + 34 }, + { 06 * 60 + 35, 20 * 60 + 06 }, + { 05 * 60 + 33, 20 * 60 + 40 }, + { 06 * 60 + 06, 20 * 60 + 18 }, + { 06 * 60 + 39, 19 * 60 + 45 }, + { 07 * 60 + 11, 19 * 60 + 13 }, + { 08 * 60 + 02, 18 * 60 + 40 }, + { 08 * 60 + 17, 18 * 60 + 07 }, + { 08 * 60 + 39, 17 * 60 + 45 }, + }, + ["Falklands"] = { + { 04 * 60 + 42, 20 * 60 + 48 }, + { 05 * 60 + 32, 19 * 60 + 54 }, + { 06 * 60 + 24, 18 * 60 + 43 }, + { 07 * 60 + 18, 17 * 60 + 40 }, + { 08 * 60 + 08, 16 * 60 + 57 }, + { 08 * 60 + 51, 16 * 60 + 56 }, + { 09 * 60 + 04, 17 * 60 + 31 }, + { 08 * 60 + 33, 18 * 60 + 19 }, + { 07 * 60 + 33, 19 * 60 + 07 }, + { 06 * 60 + 24, 20 * 60 + 01 }, + { 05 * 60 + 17, 20 * 60 + 53 }, + { 04 * 60 + 36, 21 * 60 + 17 }, + }, + ["GermanyCW"] = { + { 08 * 60 + 09, 16 * 60 + 25 }, + { 07 * 60 + 22, 17 * 60 + 19 }, + { 06 * 60 + 20, 18 * 60 + 11 }, + { 06 * 60 + 08, 20 * 60 + 05 }, + { 05 * 60 + 10, 20 * 60 + 56 }, + { 04 * 60 + 42, 21 * 60 + 31 }, + { 05 * 60 + 02, 21 * 60 + 22 }, + { 05 * 60 + 49, 20 * 60 + 31 }, + { 06 * 60 + 40, 19 * 60 + 21 }, + { 07 * 60 + 32, 18 * 60 + 11 }, + { 07 * 60 + 28, 16 * 60 + 13 }, + { 08 * 60 + 11, 15 * 60 + 52 }, + }, + ["Iraq"] = { + { 07 * 60 + 06, 17 * 60 + 33 }, + { 06 * 60 + 58, 17 * 60 + 58 }, + { 06 * 60 + 30, 18 * 60 + 21 }, + { 05 * 60 + 50, 18 * 60 + 44 }, + { 05 * 60 + 14, 19 * 60 + 06 }, + { 04 * 60 + 53, 19 * 60 + 15 }, + { 04 * 60 + 56, 19 * 60 + 03 }, + { 05 * 60 + 15, 18 * 60 + 28 }, + { 05 * 60 + 36, 17 * 60 + 48 }, + { 05 * 60 + 56, 17 * 60 + 11 }, + { 06 * 60 + 20, 16 * 60 + 54 }, + { 06 * 60 + 47, 17 * 60 + 04 }, + }, + ["Kola"] = { + { 11 * 60 + 52, 14 * 60 + 02 }, + { 09 * 60 + 15, 16 * 60 + 50 }, + { 07 * 60 + 07, 18 * 60 + 47 }, + { 04 * 60 + 49, 20 * 60 + 48 }, + { 02 * 60 + 10, 23 * 60 + 24 }, + { 00 * 60 + 00, 23 * 60 + 59 }, + { 00 * 60 + 00, 23 * 60 + 59 }, + { 03 * 60 + 59, 21 * 60 + 41 }, + { 06 * 60 + 03, 19 * 60 + 19 }, + { 07 * 60 + 56, 17 * 60 + 08 }, + { 10 * 60 + 16, 14 * 60 + 47 }, + { 12 * 60 + 00, 13 * 60 + 01 }, + }, + ["MarianaIslands"] = { + { 07 * 60 + 10, 16 * 60 + 15 }, + { 06 * 60 + 50, 16 * 60 + 45 }, + { 06 * 60 + 13, 17 * 60 + 11 }, + { 05 * 60 + 31, 19 * 60 + 35 }, + { 05 * 60 + 00, 19 * 60 + 59 }, + { 04 * 60 + 49, 20 * 60 + 18 }, + { 05 * 60 + 31, 20 * 60 + 17 }, + { 05 * 60 + 24, 19 * 60 + 51 }, + { 05 * 60 + 46, 19 * 60 + 10 }, + { 06 * 60 + 09, 18 * 60 + 28 }, + { 06 * 60 + 37, 15 * 60 + 58 }, + { 07 * 60 + 03, 15 * 60 + 54 }, + }, + ["MarianaIslandsWWII"] = { + { 07 * 60 + 10, 16 * 60 + 15 }, + { 06 * 60 + 50, 16 * 60 + 45 }, + { 06 * 60 + 13, 17 * 60 + 11 }, + { 05 * 60 + 31, 19 * 60 + 35 }, + { 05 * 60 + 00, 19 * 60 + 59 }, + { 04 * 60 + 49, 20 * 60 + 18 }, + { 05 * 60 + 31, 20 * 60 + 17 }, + { 05 * 60 + 24, 19 * 60 + 51 }, + { 05 * 60 + 46, 19 * 60 + 10 }, + { 06 * 60 + 09, 18 * 60 + 28 }, + { 06 * 60 + 37, 15 * 60 + 58 }, + { 07 * 60 + 03, 15 * 60 + 54 }, + }, + ["Nevada"] = { + { 07 * 60 + 21, 16 * 60 + 19 }, + { 06 * 60 + 58, 16 * 60 + 51 }, + { 06 * 60 + 21, 17 * 60 + 18 }, + { 06 * 60 + 37, 18 * 60 + 44 }, + { 06 * 60 + 04, 19 * 60 + 10 }, + { 05 * 60 + 53, 19 * 60 + 29 }, + { 06 * 60 + 05, 19 * 60 + 27 }, + { 06 * 60 + 29, 19 * 60 + 00 }, + { 06 * 60 + 53, 18 * 60 + 17 }, + { 07 * 60 + 28, 17 * 60 + 34 }, + { 06 * 60 + 47, 16 * 60 + 02 }, + { 07 * 60 + 14, 15 * 60 + 58 }, + }, + ["Normandy"] = { + { 09 * 60 + 23, 17 * 60 + 01 }, + { 08 * 60 + 43, 17 * 60 + 52 }, + { 07 * 60 + 48, 18 * 60 + 37 }, + { 06 * 60 + 43, 19 * 60 + 24 }, + { 05 * 60 + 50, 20 * 60 + 09 }, + { 05 * 60 + 26, 20 * 60 + 40 }, + { 05 * 60 + 42, 20 * 60 + 34 }, + { 06 * 60 + 23, 19 * 60 + 51 }, + { 07 * 60 + 08, 18 * 60 + 48 }, + { 07 * 60 + 53, 17 * 60 + 44 }, + { 08 * 60 + 42, 16 * 60 + 52 }, + { 09 * 60 + 21, 16 * 60 + 34 }, + }, + ["PersianGulf"] = { + { 07 * 60 + 34, 17 * 60 + 17 }, + { 07 * 60 + 22, 17 * 60 + 39 }, + { 06 * 60 + 55, 17 * 60 + 55 }, + { 06 * 60 + 23, 18 * 60 + 09 }, + { 06 * 60 + 01, 18 * 60 + 25 }, + { 05 * 60 + 55, 18 * 60 + 39 }, + { 06 * 60 + 05, 18 * 60 + 40 }, + { 06 * 60 + 20, 18 * 60 + 21 }, + { 06 * 60 + 32, 17 * 60 + 50 }, + { 06 * 60 + 45, 17 * 60 + 19 }, + { 07 * 60 + 04, 16 * 60 + 58 }, + { 07 * 60 + 25, 16 * 60 + 58 }, + }, + ["SinaiMap"] = { + { 07 * 60 + 10, 16 * 60 + 15 }, + { 06 * 60 + 50, 16 * 60 + 45 }, + { 06 * 60 + 13, 17 * 60 + 11 }, + { 05 * 60 + 31, 19 * 60 + 35 }, + { 05 * 60 + 00, 19 * 60 + 59 }, + { 04 * 60 + 49, 20 * 60 + 18 }, + { 05 * 60 + 31, 20 * 60 + 17 }, + { 05 * 60 + 24, 19 * 60 + 51 }, + { 05 * 60 + 46, 19 * 60 + 10 }, + { 06 * 60 + 09, 18 * 60 + 28 }, + { 06 * 60 + 37, 15 * 60 + 58 }, + { 07 * 60 + 03, 15 * 60 + 54 }, + }, + ["Syria"] = { + { 07 * 60 + 10, 16 * 60 + 15 }, + { 06 * 60 + 50, 16 * 60 + 45 }, + { 06 * 60 + 13, 17 * 60 + 11 }, + { 05 * 60 + 31, 19 * 60 + 35 }, + { 05 * 60 + 00, 19 * 60 + 59 }, + { 04 * 60 + 49, 20 * 60 + 18 }, + { 05 * 60 + 31, 20 * 60 + 17 }, + { 05 * 60 + 24, 19 * 60 + 51 }, + { 05 * 60 + 46, 19 * 60 + 10 }, + { 06 * 60 + 09, 18 * 60 + 28 }, + { 06 * 60 + 37, 15 * 60 + 58 }, + { 07 * 60 + 03, 15 * 60 + 54 }, + }, + ["TheChannel"] = { + { 09 * 60 + 23, 17 * 60 + 01 }, + { 08 * 60 + 43, 17 * 60 + 52 }, + { 07 * 60 + 48, 18 * 60 + 37 }, + { 06 * 60 + 43, 19 * 60 + 24 }, + { 05 * 60 + 50, 20 * 60 + 09 }, + { 05 * 60 + 26, 20 * 60 + 40 }, + { 05 * 60 + 42, 20 * 60 + 34 }, + { 06 * 60 + 23, 19 * 60 + 51 }, + { 07 * 60 + 08, 18 * 60 + 48 }, + { 07 * 60 + 53, 17 * 60 + 44 }, + { 08 * 60 + 42, 16 * 60 + 52 }, + { 09 * 60 + 21, 16 * 60 + 34 }, + } + } + + function Library.environment.getDayTime(monthIndex, sunset) + monthIndex = DCSEx.math.clamp(monthIndex or env.mission.date.Month, 1, 12) + sunset = sunset or false + + if not env or not env.mission or not env.mission.theatre then return 0 end + if not DAYTIME_TABLE[env.mission.theatre] then return 0 end + + local sunIndex = 1 + if sunset then sunIndex = 2 end + + return DAYTIME_TABLE[env.mission.theatre][monthIndex][sunIndex] * 60 + end + + function Library.environment.isItNightTime(timeOfDayInSeconds) + if not env or not env.mission or not env.mission.theatre then return false end + if not DAYTIME_TABLE[env.mission.theatre] then return false end + + timeOfDayInSeconds = math.max(0, timeOfDayInSeconds or timer.getAbsTime()) + + if timeOfDayInSeconds > 86400 then + while timeOfDayInSeconds > 86400 do + timeOfDayInSeconds = timeOfDayInSeconds - 86400 + end + end + + local sunriseTime = Library.environment.getDayTime(nil, false) + local sunsetTime = Library.environment.getDayTime(nil, true) + + return timeOfDayInSeconds < sunriseTime or timeOfDayInSeconds > sunsetTime + end + + function Library.environment.getWindLevel() + local windSpeed = 0 + local windSpeedValuesCount = 0 + + if not env or not env.mission or not env.mission.weather or not env.mission.weather.wind then + return Library.environment.windAmount.CALM + end + + for _,v in ipairs({"at8000", "atGround", "at2000"}) do + if env.mission.weather.wind[v] and env.mission.weather.wind[v].speed then + windSpeed = windSpeed + env.mission.weather.wind[v].speed + windSpeedValuesCount = windSpeedValuesCount + 1 + end + end + + if windSpeedValuesCount == 0 then + return Library.environment.windAmount.CALM + end + + windSpeed = windSpeed / windSpeedValuesCount + if windSpeed < 1 then + return Library.environment.windAmount.CALM + elseif windSpeed < 4 then + return Library.environment.windAmount.LIGHT_BREEZE + elseif windSpeed < 8 then + return Library.environment.windAmount.MODERATE_BREEZE + elseif windSpeed < 14 then + return Library.environment.windAmount.STRONG_BREEZE + elseif windSpeed < 21 then + return Library.environment.windAmount.GALE + else + return Library.environment.windAmount.STORM + end + end +end \ No newline at end of file From e1928722dbb658fa60c7bcac7085f54d59af5fee Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:03:03 +0200 Subject: [PATCH 2/5] Added function DCSEx.string.getTimeString --- Script/DCS extensions/String.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Script/DCS extensions/String.lua b/Script/DCS extensions/String.lua index 8e9ba4f..29453de 100644 --- a/Script/DCS extensions/String.lua +++ b/Script/DCS extensions/String.lua @@ -45,6 +45,28 @@ function DCSEx.string.join(table, separator) return joinedString end +-- TODO: description, file header +function DCSEx.string.getTimeString(timeInSeconds, useColon) + timeInSeconds = timeInSeconds or timer.getAbsTime() + useColon = useColon or false + + timeInSeconds = math.max(0, timeInSeconds) % 86400 + + local hours = math.floor(timeInSeconds / 3600) + local minutes = math.floor(timeInSeconds / 60 - hours * 60) + + local hoursStr = tostring(hours) + if #hoursStr == 1 then hoursStr = "0"..hoursStr end + + local minutesStr = tostring(minutes) + if #minutesStr == 1 then minutesStr = "0"..minutesStr end + + local separator = "" + if useColon then separator = ":" end + + return hoursStr..separator..minutesStr +end + -- TODO: description, file header function DCSEx.string.toStringNumber(number, firstToUpper) firstToUpper = firstToUpper or false From 09e25ea09e51baf9a43c2a69b8a079edafa4a6d5 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:11:04 +0200 Subject: [PATCH 3/5] Added TUM.atc table --- Script/The Universal Mission/ATC.lua | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Script/The Universal Mission/ATC.lua diff --git a/Script/The Universal Mission/ATC.lua b/Script/The Universal Mission/ATC.lua new file mode 100644 index 0000000..447869b --- /dev/null +++ b/Script/The Universal Mission/ATC.lua @@ -0,0 +1,4 @@ +TUM.atc = {} + +do +end From 89e390eaa00bac6c5323b5a4a6b0a2b5917f1331 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:29:06 +0200 Subject: [PATCH 4/5] Moved "Request objective coordinates" radio commands to new "Navigation" submenu --- README.md | 10 +++-- Script/Library/RadioMessages.lua | 43 ++++++++++---------- Script/The Universal Mission/ATC.lua | 37 +++++++++++++++++ Script/The Universal Mission/MissionMenu.lua | 20 ++++----- Script/The Universal Mission/SupportJTAC.lua | 2 +- 5 files changed, 74 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d94528e..31003cc 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The exact content of the menu will depend on the current phase of the mission. - **Target count**: How many objectives will be spawned. More objectives means potentially more xp in a single sortie, so better medals, but also more work and more risk. Be aware that you can RTB to rearm/refuel at any time between objectives, but you won't accumulate as many single-sortie XP as if you complete objectives without going back to base, because XP is awarded to your profile and reset each time you land. - **Enemy air defense**: Amount, quality and skill of enemy surface-to-air units (AAA, MANPADS and SAM). A higher setting awards more XP. - **Enemy air force**: Amount, quality and skill of enemy combat air patrols. A higher setting awards more XP. - - **Wingmen count**: **(COMING SOON!)** How many wingmen will fly by your side (from zero to three). A small XP penalty is added for each additional wingman. Wingman won't get replaced if they get shot during a mission, but they will (with full payload) each time you land and takeoff again. Only shown in single-player missions. + - **Wingmen count**: How many wingmen will fly by your side (from zero to three). A small XP penalty is added for each additional wingman. Wingman won't get replaced if they get shot during a mission, but they will (with full payload) each time you land and takeoff again. Only shown in single-player missions. - **Friendly AI CAP**: Should AI fighter aicraft be spawned regularly to patrol the AO and shoot down potential threats? Disabling this option will award you more XP (only if "Enemy air force" is not set to "None") but also means you and your wingmen will be alone against the whole enemy air force. - **View pilot career stats**: Displays a list of your achievements, as well as your medal case. Only available when playing single-player missions and if the Lua IO module has been unsanitized (see "First setup" above) - **Begin mission**: Starts a mission with the current settings. @@ -94,10 +94,11 @@ The exact content of the menu will depend on the current phase of the mission. #### During the mission - **Mission status**: Displays a summary of the mission's status (list of objectives and progress on each objective). -- **Objectives**: Displays a list of commands related to each of the mission's objectives. - - **Objective coordinates**: Displays the coordinates of the objective and its BRA ("fly X for Y") relative to the player's position. Some objectives types (e.g. strike missions) are provided with exact coordinates, but must will only have approximate coordiantes, so you'll have to search for your targets in the objective area. +- **Objectives**: Displays a list of special commands related to each of the mission's objectives. Be aware that some objectives may have no special commands associated with them. - **Smoke marker on target**: Asks for a friendly JTAC to pop a smoke marker on the target. Makes finding the target easier, but will cost you a small XP penalty. Only available for missions where a JTAC is available (it's pretty hard to throw a smoke grenade at an airplane or a ship in the middle of the sea). -- **Flight**: **(COMING SOON!)** Displays a list of commands for your wingmen. Only shown in single-player missions and if wingmen are available for this mission. +- **Navigation**: Displays a list of commands related to navigational assistance. + - **Navigation to objective [OBJECTIVE NAME]**: Displays the coordinates of the objective, its BRA ("fly X for Y") relative to the player's position and an estimated flight time and ETA. Some objectives types (e.g. strike missions) are provided with exact coordinates, but most will only have approximate coordiantes, so you'll have to search for targets yourself once in the objective area. +- **Flight**: Displays a list of commands for your wingmen. Only shown in single-player missions and if wingmen are available for this mission. - **Cover me!**: Tasks your wingmen to immediately engage any nearby air threats. - **Engage**: Tasks your wingmen to engage a certain type of targets. Targets must be detected by your wingmen (see "Report contacts" below), or they won't be able to engage them. - **Report contacts**: Asks your wingmen for a list of all detected contacts. According to range and sensors capabilities, their reports can go from perfect ID (e.g. "Su-27") to very generic descriptions (e.g. "fighter" or even "aircraft") @@ -239,6 +240,7 @@ The core script is quite simple and small, I probably won't need too much help w - Increased minimum aircraft spawn altitude to avoir crashes in nearby hills - Infantry escaping from destroyed vehicles is now hidden on F10 map, as it should be - Interception objectives are now marked as complete when target is shot down + - Moved "Request objective coordinates" radio commands to new "Navigation" submenu, which will include additional navigational assist in future versions - Lowered MANPADS count and skill (MANPADS are overpowered in DCS, especially SA-18) - "New friendly/enemy aircraft taking off" radio messages now mention their BRAA relative to the player, number of bandits taking off now displayed as a word instead of digits - "Rifle!" and "Missile away!" radio calls now both used for any kind of A/G missiles diff --git a/Script/Library/RadioMessages.lua b/Script/Library/RadioMessages.lua index a474ff6..1ac2045 100644 --- a/Script/Library/RadioMessages.lua +++ b/Script/Library/RadioMessages.lua @@ -11,7 +11,7 @@ Library.radioMessages = { "Taking fire!", "Bird's hurt, trying to hold altitude!", "Impact on fuselage, took a solid hit!", - "Taking damage, warning lights all over" + "Taking damage, warning lights all over!" }, pilotKillAir = { "Splash one $1. Rejoining.", @@ -96,12 +96,12 @@ Library.radioMessages = { "$1! They got me! Left wing's gone! I'm getting out!", "$1! They nailed me, fire in the pit! I'm outta here!", "$1! Fire in the cockpit! Can't see anything! Ejecting!", - "$1! Mayday! No control—this is it, ejecting now!" + "$1! Mayday! No control! This is it, ejecting now!" }, pilotWingmanEngageAir = { "$1, copy, engaging $2 now ($3).", "$1, tally one, pressing on $2 ($3).", - "$1, roger, comitting on $2 ($3).", + "$1, roger, committing on $2 ($3).", "$1, confirm, going after $2 ($3).", "$1, affirm, moving in on $2 ($3)." }, -- "$2" should be "bandits" in audio version @@ -141,7 +141,7 @@ Library.radioMessages = { "$1. Copy, orbiting now.", "$1. Roger, in the hold.", "$1. Affirm, setting up the orbit.", - "$1. Orbiting at pos." + "$1. Orbiting at position." }, pilotWingmanRejoin = { "$1, off the perch, rejoining your side.", @@ -171,7 +171,7 @@ Library.radioMessages = { pilotWingmanReportContactsNew = { "Heads up, new contacts just popped up.$2", "Look out, tally fresh threats.$2", - "Stay sharp, eyes on new group, stand by.$2", + "Stay sharp, eyes on new group.$2", "Eyes open, additional threats spotted.$2", "Stay alert, new activity in our sector.$2", "Be aware, got more targets popping up.$2", @@ -251,8 +251,10 @@ Library.radioMessages = { "All flights, objective $1 wrapped, keep on the good work.", "All callsigns, objective $1's done. Proceed direct to next waypoint." }, - commandObjectiveCoordinates = "We have no exact coordinates for objective $1.\nObjective should be located near:\n$2\n\nFly $3 to reach the objective.", - commandObjectiveCoordinatesPrecise = "We have exact coordinates for objective $1.\nObjective coordinates are:\n$2\n\nFly $3 to reach the objective.", + + commandObjectiveCoordinates = "Copy, navigational assist for objective $1.\n\n$2", + commandObjectiveCoordinatesPrecise = "Copy, navigational assist for objective $1.\n\n$2", + commandObjectivesManyLeft = { "Stay focused, people. We still have work to do.\n$1", "Our work is not done yet, we have a lot to do.\n$1", @@ -317,6 +319,13 @@ Library.radioMessages = { "Flight, update altitude profile to $1.", "Flight, altitude correction, go to $1." }, + playerWingmanCoverMe = { + "Flight, need cover now.", + "Flight, keep my six clean.", + "Flight, engaged defensive, cover me!", + "Flight, break off and clear my tail.", + "Flight, keep bandits off me." + }, playerWingmanEngageAirDefense = { "Flight, prioritize $1, engage now.", "Flight, take down $1 systems.", @@ -331,13 +340,6 @@ Library.radioMessages = { "Flight, engage $1, your discretion.", "Flight, you're free to engage $1." }, -- "$1" should be "bandits" in audio version - playerWingmanCoverMe = { - "Flight, need cover now.", - "Flight, keep my six clean.", - "Flight, engaged defensive, cover me!", - "Flight, break off and clear my tail.", - "Flight, keep bandits off me." - }, playerWingmanEngageGround = { "Flight, engage $1 targets.", "Flight, prosecute $1 targets ahead.", @@ -364,15 +366,14 @@ Library.radioMessages = { -- "Flight, move to designated steerpoint." -- }, playerWingmanOrbit = { - "Flight, orbit your position.", + "Flight, orbit at position.", "Flight, set up an orbit.", - "Flight, hold on me.", - "Flight, anchor on my current pos.", - "Flight, orbit overhead" + "Flight, hold on position.", + "Flight, anchor on current position.", + "Flight, orbit overhead." }, - playerWingmanRejoin = - { - "Flight, rejoin my side", + playerWingmanRejoin = { + "Flight, rejoin my side.", "Flight, push it up, rejoin formation.", "Flight, come back to route.", "Flight, tighten it up.", diff --git a/Script/The Universal Mission/ATC.lua b/Script/The Universal Mission/ATC.lua index 447869b..25082d7 100644 --- a/Script/The Universal Mission/ATC.lua +++ b/Script/The Universal Mission/ATC.lua @@ -1,4 +1,41 @@ TUM.atc = {} do + function TUM.atc.requestNavAssistanceToObjective(index, delayRadioAnswer) + local obj = TUM.objectives.getObjective(index) + if not obj then return end + + local msgIDSuffix = "" + if obj.preciseCoordinates then msgIDSuffix = "Precise" end + + local players = coalition.getPlayers(TUM.settings.getPlayerCoalition()) + for _,p in ipairs(players) do + -- Give BRA to objective + local navInfo = "- Fly "..DCSEx.dcs.getBRAA(obj.waypoint3, p:getPoint(), false).."\n" + + -- Give flight time and ETA + local velocity = p:getVelocity() + local speed = math.max(1, math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z)) + local distance = DCSEx.math.getDistance2D(obj.waypoint2, DCSEx.math.vec3ToVec2(p:getPoint())) + local timeInMinutes = math.max(1, math.floor(distance / (speed * 60))) + local eta = DCSEx.string.getTimeString(timer.getAbsTime() + timeInMinutes * 60) + if timeInMinutes > 600 then + navInfo = navInfo.."- More than ten hours of flight time at current airspeed\n" + elseif timeInMinutes > 120 then + navInfo = navInfo.."- "..tostring(math.floor(timeInMinutes / 60)).." hours of flight time at current airspeed, ETA "..eta.."\n" + else + navInfo = navInfo.."- "..tostring(timeInMinutes).." minute(s) of flight time at current airspeed, ETA "..eta.."\n" + end + + -- Give objective coordinates + if obj.preciseCoordinates then + navInfo = navInfo.."\nExact coordinates for objective are:\n" + else + navInfo = navInfo.."\nNo exact coordinates for objective. Approximate coordinates are:\n" + end + navInfo = navInfo..DCSEx.world.getCoordinatesAsString(obj.waypoint3, false) + + TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..msgIDSuffix, { obj.name, navInfo }, "Command", delayRadioAnswer) + end + end end diff --git a/Script/The Universal Mission/MissionMenu.lua b/Script/The Universal Mission/MissionMenu.lua index 1e8d367..807702a 100644 --- a/Script/The Universal Mission/MissionMenu.lua +++ b/Script/The Universal Mission/MissionMenu.lua @@ -19,17 +19,8 @@ do local obj = TUM.objectives.getObjective(index) if not obj then return end - local messageSuffix = "" - if obj.preciseCoordinates then messageSuffix = "Precise" end - TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerCommandRequireObjectives", { obj.name }, "Flight", false) - - local players = coalition.getPlayers(TUM.settings.getPlayerCoalition()) - for _,p in ipairs(players) do - local coordinates = DCSEx.world.getCoordinatesAsString(obj.waypoint3, false) - local braa = DCSEx.dcs.getBRAA(obj.waypoint3, p:getPoint(), false) - TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..messageSuffix, { obj.name, coordinates, braa }, "Command", true) - end + TUM.atc.requestNavAssistanceToObjective(index, true) end function TUM.missionMenu.create() @@ -37,15 +28,20 @@ do missionCommands.addCommand("☱ Mission status", nil, doCommandMissionStatus, nil) local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives") + local navigationMenuRoot = missionCommands.addSubMenu("➽ Navigation") + for i=1,TUM.objectives.getCount() do local obj = TUM.objectives.getObjective(i) if obj then - local objRoot = missionCommands.addSubMenu("Objective "..obj.name.." ("..Library.tasks[obj.taskID].description.short..")", objectivesMenuRoot) - missionCommands.addCommand("Request objective coordinates", objRoot, doCommandObjectiveLocation, i) + local objNameAndDescription = obj.name.." ("..Library.tasks[obj.taskID].description.short..")" + local objRoot = missionCommands.addSubMenu("Objective "..objNameAndDescription, objectivesMenuRoot) TUM.supportJTAC.setupJTACOnObjective(i, objRoot) + + missionCommands.addCommand("Navigation to objective "..objNameAndDescription, navigationMenuRoot, doCommandObjectiveLocation, i) end end + TUM.wingmenMenu.create() TUM.supportAWACS.createMenu() diff --git a/Script/The Universal Mission/SupportJTAC.lua b/Script/The Universal Mission/SupportJTAC.lua index 21757c2..14741b8 100644 --- a/Script/The Universal Mission/SupportJTAC.lua +++ b/Script/The Universal Mission/SupportJTAC.lua @@ -88,6 +88,6 @@ do local objectiveDB = Library.tasks[obj.taskID] if not DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.ALLOW_JTAC) then return end -- No JTAC for this objective - missionCommands.addCommand("Require smoke marker on target ("..tostring(SMOKE_MARKER_PENALTY).."xp)", menuRoot, doCommandSmoke, index) + missionCommands.addCommand("Smoke marker on target ("..tostring(SMOKE_MARKER_PENALTY).."xp)", menuRoot, doCommandSmoke, index) end end From b4704f885f0a5e3800b74f1d96b82d3a6613cef3 Mon Sep 17 00:00:00 2001 From: Ambroise Garel <47314805+akaAgar@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:42:17 +0200 Subject: [PATCH 5/5] Prettified "mission status report" messages --- Script/Library/RadioMessages.lua | 26 ++++++++++++------------ Script/The Universal Mission/Mission.lua | 18 +++++++++++++++- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Script/Library/RadioMessages.lua b/Script/Library/RadioMessages.lua index 1ac2045..553939c 100644 --- a/Script/Library/RadioMessages.lua +++ b/Script/Library/RadioMessages.lua @@ -256,21 +256,21 @@ Library.radioMessages = { commandObjectiveCoordinatesPrecise = "Copy, navigational assist for objective $1.\n\n$2", commandObjectivesManyLeft = { - "Stay focused, people. We still have work to do.\n$1", - "Our work is not done yet, we have a lot to do.\n$1", - "Flights, you've got a couple of tasks left, keep moving down the list.\n$1", - "Still a few objectives outstanding, don't break until they're complete.\n$1", - "Alright people, keep your focus, more work ahead before you can head home.\n$1", - "All flights, maintain timeline. You've got more boxes to check before RTB.\n$1" + "Stay focused, people. We still have work to do.\n\n$1", + "Our work is not done yet, we have a lot to do.\n\n$1", + "Flights, you've got a couple of tasks left, keep moving down the list.\n\n$1", + "Still a few objectives outstanding, don't break until they're complete.\n\n$1", + "Alright people, keep your focus, more work ahead before you can head home.\n\n$1", + "All flights, maintain timeline. You've got more boxes to check before RTB.\n\n$1" }, commandObjectivesOneLeft = { - "All flights, you've got one last task before you're done.\n$1", - "Flights, almost home. One objective remaining, then you're RTB.\n$1", - "All flights, you're down to the final push. Complete this and you're done.\n$1", - "All leads, one more on the board. Finish it and head back to base.\n$1", - "Flights, you're not clear yet. One last objective to wrap up.\n$1", - "Come on people, one last push and we're done.\n$1", - "Just one objective to complete and we're done.\n$1" + "All flights, you've got one last task before you're done.\n\n$1", + "Flights, almost home. One objective remaining, then you're RTB.\n\n$1", + "All flights, you're down to the final push. Complete this and you're done.\n\n$1", + "All leads, one more on the board. Finish it and head back to base.\n\n$1", + "Flights, you're not clear yet. One last objective to wrap up.\n\n$1", + "Come on people, one last push and we're done.\n\n$1", + "Just one objective to complete and we're done.\n\n$1" }, jtacSmokeOK = { diff --git a/Script/The Universal Mission/Mission.lua b/Script/The Universal Mission/Mission.lua index 699720b..e7f7c1b 100644 --- a/Script/The Universal Mission/Mission.lua +++ b/Script/The Universal Mission/Mission.lua @@ -111,12 +111,26 @@ do if missionStatus == TUM.mission.status.NONE then return "" end local missionSummary = "" + local validObjCount = 1 for i=1,TUM.objectives.getCount() do local o = TUM.objectives.getObjective(i) if o then if not o.completed or not onlyShowIncomplete then - missionSummary = missionSummary.."- Objective "..o.name..": "..Library.tasks[o.taskID].description.short + local bulletCharacter = "⬤" + if validObjCount == 1 then bulletCharacter = "❶" + elseif validObjCount == 2 then bulletCharacter = "❷" + elseif validObjCount == 3 then bulletCharacter = "❸" + elseif validObjCount == 4 then bulletCharacter = "❹" + elseif validObjCount == 5 then bulletCharacter = "❺" + elseif validObjCount == 6 then bulletCharacter = "❻" + elseif validObjCount == 7 then bulletCharacter = "❼" + elseif validObjCount == 8 then bulletCharacter = "❽" + elseif validObjCount == 9 then bulletCharacter = "❾" + elseif validObjCount == 10 then bulletCharacter = "❿" + end + + missionSummary = missionSummary..bulletCharacter.." Objective "..o.name..": "..Library.tasks[o.taskID].description.short if not o.completed then missionSummary = missionSummary.." ("..TUM.objectives.getObjectiveProgress(i, doublePercentage)..")" else @@ -126,6 +140,8 @@ do if i < TUM.objectives.getCount() then missionSummary = missionSummary.."\n" end + + validObjCount = validObjCount + 1 end end end