Merge branch 'environment-library'

This commit is contained in:
Ambroise Garel
2025-07-29 14:42:28 +02:00
8 changed files with 389 additions and 33 deletions
+22
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
+277
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
+17 -15
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 = {
+41
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
+17 -1
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
+8 -12
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()
+1 -1
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