Merge branch 'environment-library'

This commit is contained in:
Ambroise Garel 2025-07-29 14:42:28 +02:00
commit 1488001442
8 changed files with 389 additions and 33 deletions

View File

@ -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")
@ -242,6 +243,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

View File

@ -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

View File

@ -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

View File

@ -251,24 +251,26 @@ 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",
"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 = {

View File

@ -0,0 +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

View File

@ -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

View File

@ -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()

View File

@ -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