diff --git a/Script/Script.lua b/Script/Script.lua index 60e3897..8bc1fe4 100644 --- a/Script/Script.lua +++ b/Script/Script.lua @@ -151,6 +151,108 @@ function TUM.log(message, logLevel) TUM.Logger.print(logLevel, message) end +-------------------------------------- +--- Administrative settings +-------------------------------------- + +TUM.administrativeSettings = { + -- This table defines the administrative settings for the script. + -- These settings can modify the behavior of the script, and can be set by the mission maker via a specific trigger zone's parameters, or via script (defining the TUM.administrativeSettingsValues below) + USE_SPECIFIC_RADIOMENU = 1, -- Use a specific radio menu for the mission commands, or use the main one? + INITIALIZE_AUTOMATICALLY = 2, -- Automatically initialize the mission when the script is loaded. If false, you must call TUM.initialize() manually. + IGNORE_ZONES_STARTINGWITH = 3, -- If set, ignore all zones starting with this string. This is useful to avoid conflicts with other scripts that use the same zone names. + ONLY_ZONES_STARTINGWITH = 4, -- If set, only adds zones starting with this string. This is useful to avoid conflicts with other scripts that use the same zone names. +} + +TUM.administrativeSettingsDefaultValues = { + -- This table defines the default values for the administrative settings. + -- The keys must match the keys in TUM.administrativeSettings + [TUM.administrativeSettings.USE_SPECIFIC_RADIOMENU] = false, -- Use a specific radio menu for the mission commands, or use the main one? + [TUM.administrativeSettings.INITIALIZE_AUTOMATICALLY] = true, -- Automatically initialize the mission when the script is loaded. If false, you must call TUM.initialize() manually. + [TUM.administrativeSettings.IGNORE_ZONES_STARTINGWITH] = nil, -- If set, ignore all zones starting with this string. This is useful to avoid conflicts with other scripts that use the same zone names. + [TUM.administrativeSettings.ONLY_ZONES_STARTINGWITH] = nil, -- If set, only adds zones starting with this string. This is useful to avoid conflicts with other scripts that use the same zone names. +} + +TUM.administrativeSettingsValues = { + -- This table defines the administrative settings values for the script. + -- The keys must match the keys in TUM.administrativeSettings + -- If set, these values will prevail over both the default values in TUM.administrativeSettings and the values set by the mission maker via a specific trigger zone's parameters. +} + +--- Returns the value of the administrative setting with the given key +function TUM.administrativeSettings.getValue(key) + if TUM.administrativeSettingsValues[key] ~= nil then + return TUM.administrativeSettingsValues[key] + else + return TUM.administrativeSettingsDefaultValues[key] + end +end + +--- Sets the value of the administrative setting with the given key +function TUM.administrativeSettings.setValue(key, value) + -- check if the key is in the administrative settings table + local foundKey = false + for _, v in pairs(TUM.administrativeSettings) do + if v == key then + foundKey = true + break + end + end + if not foundKey then + TUM.log("Tried to set an unknown administrative setting: "..tostring(key), TUM.logLevel.ERROR) + return nil + end + + TUM.administrativeSettingsValues[key] = value +end + +--- Takes all the values from (in order of priority): +--- 1. The TUM.administrativeSettingsValues table (optionnaly set by script) +--- 2. The trigger zone parameters (set by the mission maker) +--- 3. The TUM.administrativeSettingsDefaultValues table (default values) +function TUM.administrativeSettings.initializeSettings() + local ADMIN_ZONE_NAME = "TUM_Administrative_Settings" -- The name of the administrative settings trigger zone + local adminZone = DCSEx.zones.getByName(ADMIN_ZONE_NAME) + + for key, _ in pairs(TUM.administrativeSettings) do + local value = nil + if TUM.administrativeSettingsValues[TUM.administrativeSettings[key]] then -- Check if the value is set by script + value = TUM.administrativeSettingsValues[TUM.administrativeSettings[key]] + end + if value == nil and adminZone then -- If the value is not set by script, check the trigger zone parameters + local zoneValue = DCSEx.zones.getProperty(adminZone, key) + if zoneValue ~= nil then + value = zoneValue + end + end + if value == nil then -- If the value is not set by script or trigger zone, use the default value + value = TUM.administrativeSettingsDefaultValues[TUM.administrativeSettings[key]] + end + TUM.administrativeSettingsValues[TUM.administrativeSettings[key]] = value + end +end + +-------------------------------------- +--- Radio menu for the mission commands +-------------------------------------- +TUM.rootMenu = nil +function TUM.getOrCreateRootMenu(reset) -- Get or create the root menu for the mission commands; if reset is true, the menu will be cleared and recreated + if reset then + missionCommands.removeItem(TUM.rootMenu) -- Clear the menu + TUM.rootMenu = nil + TUM.getOrCreateRootMenu() -- Recreate the root menu + end + if not TUM.rootMenu then + if TUM.administrativeSettings.getValue(TUM.administrativeSettings.USE_SPECIFIC_RADIOMENU) then + local rootMenuTitle = "✈ TUM" + TUM.rootMenu = missionCommands.addSubMenu(rootMenuTitle) + end + end + return TUM.rootMenu +end + +-------------------------------------- + --[[DCS EXTENSIONS]]-- --[[LIBRARY]]-- @@ -161,118 +263,129 @@ end -- Module startup -- -------------------- -do - local function startUpMission() - TUM.hasStarted = false +function TUM.initialize() + do + -- load the administrative settings + TUM.administrativeSettings.initializeSettings() - local coreSettings = { - multiplayer = false - } + local function startUpMission() + TUM.hasStarted = false - if not net or not net.dostring_in then - TUM.log("Mission failed to execute. Please copy the provided \"autoexec.cfg\" file to the [Saved Games]\\DCS\\Config directory.\nThe file can be downloaded from github.com/akaAgar/the-universal-mission-for-dcs-world", TUM.logLevel.ERROR) - return nil - end + local coreSettings = { + multiplayer = false + } - if #DCSEx.envMission.getPlayerGroups() == 0 then - TUM.log("No \"Player\" or \"Client\" aircraft slots have been found. Please fix this problem in the mission editor.", TUM.logLevel.ERROR) - return nil - end - - if world:getPlayer() then - coreSettings.multiplayer = false - - if #DCSEx.envMission.getPlayerGroups() > 1 then - TUM.log("Multiple players slots have been found in addition to the single-player \"Player\" aircraft. Please fix this problem in the mission editor.", TUM.logLevel.ERROR) - return nil - end - else - coreSettings.multiplayer = true - - if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) == 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) == 0 then - TUM.log("Neither BLUE nor RED coalitions have player slots. Please make sure one coalition has player slots in the mission editor.", TUM.logLevel.ERROR) + if not net or not net.dostring_in then + TUM.log("Mission failed to execute. Please copy the provided \"autoexec.cfg\" file to the [Saved Games]\\DCS\\Config directory.\nThe file can be downloaded from github.com/akaAgar/the-universal-mission-for-dcs-world", TUM.logLevel.ERROR) return nil end - if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) > 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) > 0 then - TUM.log("Both coalitions have player slots. The Universal Mission is a purely singleplayer/PvE experience and does not support PvP. Please make sure only one coalition has player slots in the mission editor.", TUM.logLevel.ERROR) + if #DCSEx.envMission.getPlayerGroups() == 0 then + TUM.log("No \"Player\" or \"Client\" aircraft slots have been found. Please fix this problem in the mission editor.", TUM.logLevel.ERROR) return nil end + + if world:getPlayer() then + coreSettings.multiplayer = false + + if #DCSEx.envMission.getPlayerGroups() > 1 then + TUM.log("Multiple players slots have been found in addition to the single-player \"Player\" aircraft. Please fix this problem in the mission editor.", TUM.logLevel.ERROR) + return nil + end + else + coreSettings.multiplayer = true + + if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) == 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) == 0 then + TUM.log("Neither BLUE nor RED coalitions have player slots. Please make sure one coalition has player slots in the mission editor.", TUM.logLevel.ERROR) + return nil + end + + if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) > 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) > 0 then + TUM.log("Both coalitions have player slots. The Universal Mission is a purely singleplayer/PvE experience and does not support PvP. Please make sure only one coalition has player slots in the mission editor.", TUM.logLevel.ERROR) + return nil + end + end + + if not TUM.territories.onStartUp() then return nil end + if not TUM.settings.onStartUp(coreSettings) then return nil end -- Must be called after TUM.territories.onStartUp() + if not TUM.playerCareer.onStartUp() then return nil end + if not TUM.intermission.onStartUp() then return nil end + if not TUM.airForce.onStartUp() then return nil end + if not TUM.mizCleaner.onStartUp() then return nil end -- Must be called after TUM.settings.onStartUp() + + TUM.hasStarted = true + + return coreSettings end - if not TUM.territories.onStartUp() then return nil end - if not TUM.settings.onStartUp(coreSettings) then return nil end -- Must be called after TUM.territories.onStartUp() - if not TUM.playerCareer.onStartUp() then return nil end - if not TUM.intermission.onStartUp() then return nil end - if not TUM.airForce.onStartUp() then return nil end - if not TUM.mizCleaner.onStartUp() then return nil end -- Must be called after TUM.settings.onStartUp() - - TUM.hasStarted = true - - return coreSettings + if not startUpMission() then + trigger.action.outText("A critical error has happened, cannot start the mission.", 3600) + end end - if not startUpMission() then - trigger.action.outText("A critical error has happened, cannot start the mission.", 3600) - end -end + ------------------- + -- Event handler -- + ------------------- + do + local eventHandler = {} -------------------- --- Event handler -- -------------------- -do - local eventHandler = {} + function eventHandler:onEvent(event) + if not event then return end -- No event - 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 will interrupt the "ambient" ones - TUM.ambientWorld.onEvent(event) - TUM.objectives.onEvent(event) - TUM.playerScore.onEvent(event) - TUM.mission.onEvent(event) - TUM.wingmen.onEvent(event) - TUM.mizCleaner.onEvent(event) -- Must be last, can remove units which could cause bugs in other onEvent methods - end - - function TUM.onEvent(event) - eventHandler:onEvent(event) - end - - if TUM.hasStarted then - world.addEventHandler(eventHandler) - end -end - --------------------------------------------- --- Game clock, called every 10-20 seconds -- --------------------------------------------- -do - local clockTick = -1 - - function TUM.onClockTick(arg, time) - local nextTickTime = time + math.random(10, 20) - clockTick = clockTick + 1 - - TUM.wingmenTasking.onClockTick() -- No need to check the function return, it's just here to check if wingmen target is still alive - - if clockTick % 4 == 0 then - if TUM.playerScore.onClockTick() then return nextTickTime end - if TUM.mission.onClockTick() then return nextTickTime end - elseif clockTick % 4 == 1 then - if TUM.airForce.onClockTick(TUM.settings.getPlayerCoalition()) then return nextTickTime end - elseif clockTick % 4 == 2 then - if TUM.supportAWACS.onClockTick() then return nextTickTime end - else - if TUM.airForce.onClockTick(TUM.settings.getEnemyCoalition()) then return nextTickTime end + 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.wingmen.onEvent(event) + TUM.mizCleaner.onEvent(event) -- Must be last, can remove units which could cause bugs in other onEvent methods end - if TUM.wingmenContacts.onClockTick() then return nextTickTime end -- Called every tick if no other action has taken place + function TUM.onEvent(event) + eventHandler:onEvent(event) + end - return nextTickTime + if TUM.hasStarted then + world.addEventHandler(eventHandler) + end end - if TUM.hasStarted then - timer.scheduleFunction(TUM.onClockTick, nil, timer.getTime() + math.random(10, 15)) + -------------------------------------------- + -- Game clock, called every 10-20 seconds -- + -------------------------------------------- + do + local clockTick = -1 + + function TUM.onClockTick(arg, time) + local nextTickTime = time + math.random(10, 20) + clockTick = clockTick + 1 + + TUM.wingmenTasking.onClockTick() -- No need to check the function return, it's just here to check if wingmen target is still alive + + if clockTick % 4 == 0 then + if TUM.playerScore.onClockTick() then return nextTickTime end + if TUM.mission.onClockTick() then return nextTickTime end + elseif clockTick % 4 == 1 then + if TUM.airForce.onClockTick(TUM.settings.getPlayerCoalition()) then return nextTickTime end + elseif clockTick % 4 == 2 then + if TUM.supportAWACS.onClockTick() then return nextTickTime end + else + if TUM.airForce.onClockTick(TUM.settings.getEnemyCoalition()) then return nextTickTime end + end + + if TUM.wingmenContacts.onClockTick() then return nextTickTime end -- Called every tick if no other action has taken place + + return nextTickTime + end + + if TUM.hasStarted then + timer.scheduleFunction(TUM.onClockTick, nil, timer.getTime() + math.random(10, 15)) + end end end + +if TUM.administrativeSettings.getValue(TUM.administrativeSettings.INITIALIZE_AUTOMATICALLY) then + TUM.initialize() +else + TUM.log("TUM has been loaded, but not initialized. Call TUM.initialize() to start the mission.", TUM.logLevel.INFO) +end \ No newline at end of file diff --git a/Script/The Universal Mission/DebugMenu.lua b/Script/The Universal Mission/DebugMenu.lua index 2b62f09..d3074ec 100644 --- a/Script/The Universal Mission/DebugMenu.lua +++ b/Script/The Universal Mission/DebugMenu.lua @@ -94,7 +94,7 @@ do function TUM.debugMenu.createMenu() if not TUM.DEBUG_MODE then return end - local rootMenu = missionCommands.addSubMenu("[DEBUG]") + local rootMenu = missionCommands.addSubMenu("[DEBUG]", TUM.getOrCreateRootMenu()) missionCommands.addCommand("Detonate - BOOM map markers", rootMenu, doMarkersBoom, nil) missionCommands.addCommand("Detonate - AIRBOOM map markers", rootMenu, doMarkersAirBoom, nil) missionCommands.addCommand("Wingman - kill", rootMenu, doKillWingman, nil) diff --git a/Script/The Universal Mission/Intermission.lua b/Script/The Universal Mission/Intermission.lua index c78176a..c477ad6 100644 --- a/Script/The Universal Mission/Intermission.lua +++ b/Script/The Universal Mission/Intermission.lua @@ -86,7 +86,7 @@ do -- Creates the mission briefing menu ------------------------------------- function TUM.intermission.createMenu() - missionCommands.removeItem() -- Clear the menu + local rootMenu = TUM.getOrCreateRootMenu(true) -- Clear the menu local briefingText = "Welcome to The Universal Mission for DCS World, a highly customizable mission available for single-player and PvE.\n\nOpen the communication menu and select the ''F10. Other'' option to access mission settings." DCSEx.envMission.setBriefing(coalition.side.RED, briefingText) @@ -94,9 +94,9 @@ do TUM.intermission.createMissionZonesMarkers() -- Show the available mission zones on the F10 map - missionCommands.addCommand("ℹ Display mission settings", nil, TUM.settings.printSettingsSummary, false) + missionCommands.addCommand("ℹ Display mission settings", rootMenu, TUM.settings.printSettingsSummary, false) - local settingsMenu = missionCommands.addSubMenu("✎ Change mission settings") + local settingsMenu = missionCommands.addSubMenu("✎ Change mission settings", rootMenu) createSubMenu(TUM.settings.id.COALITION_BLUE, settingsMenu) createSubMenu(TUM.settings.id.COALITION_RED, settingsMenu) createSubMenu(TUM.settings.id.TASKING, settingsMenu) @@ -107,7 +107,7 @@ do createSubMenu(TUM.settings.id.WINGMEN, settingsMenu) createSubMenu(TUM.settings.id.AI_CAP, settingsMenu) TUM.playerCareer.createMenu() - missionCommands.addCommand("➤ Begin mission", nil, doCommandStartMission, nil) + missionCommands.addCommand("➤ Begin mission", rootMenu, doCommandStartMission, nil) TUM.debugMenu.createMenu() -- Append debug menu to other menus (if debug mode enabled) end diff --git a/Script/The Universal Mission/MissionMenu.lua b/Script/The Universal Mission/MissionMenu.lua index fc65d99..36c118b 100644 --- a/Script/The Universal Mission/MissionMenu.lua +++ b/Script/The Universal Mission/MissionMenu.lua @@ -34,11 +34,11 @@ do end function TUM.missionMenu.create() - missionCommands.removeItem() -- Clear the menu - missionCommands.addCommand("☱ Mission status", nil, doCommandMissionStatus, nil) + local rootMenu = TUM.getOrCreateRootMenu(true) -- Clear the menu + missionCommands.addCommand("☱ Mission status", rootMenu, doCommandMissionStatus, nil) - local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives") - local navigationMenuRoot = missionCommands.addSubMenu("➽ Navigation") + local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives", rootMenu) + local navigationMenuRoot = missionCommands.addSubMenu("➽ Navigation", rootMenu) -- missionCommands.addCommand("Nav to nearest airbase", navigationMenuRoot, doCommandNearestAirbase, nil) for i=1,TUM.objectives.getCount() do @@ -57,10 +57,10 @@ do TUM.supportAWACS.createMenu() if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then -- If not multiplayer, add "show mission score" command - missionCommands.addCommand("★ Display mission score", nil, TUM.playerScore.showScore, nil) + missionCommands.addCommand("★ Display mission score", rootMenu, TUM.playerScore.showScore, nil) end - local abortRoot = missionCommands.addSubMenu("⬣ Abort mission") + local abortRoot = missionCommands.addSubMenu("⬣ Abort mission", rootMenu) if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) and DCSEx.io.canReadAndWrite() then missionCommands.addCommand("✓ Confirm (all xp since last landing will be lost!)", abortRoot, doCommandAbortMission, nil) else diff --git a/Script/The Universal Mission/PlayerCareer.lua b/Script/The Universal Mission/PlayerCareer.lua index 60d7ece..24f4240 100644 --- a/Script/The Universal Mission/PlayerCareer.lua +++ b/Script/The Universal Mission/PlayerCareer.lua @@ -146,10 +146,11 @@ do -- Appends the career menu to the F10 menu. Only works in single-player missions ------------------------------------- function TUM.playerCareer.createMenu() + local rootMenu = TUM.getOrCreateRootMenu() if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No career in multiplayer - missionCommands.addCommand("✪ View pilot career stats", nil, TUM.playerCareer.displayMedalBox, true) + missionCommands.addCommand("✪ View pilot career stats", rootMenu, TUM.playerCareer.displayMedalBox, true) end ------------------------------------- diff --git a/Script/The Universal Mission/SupportAWACS.lua b/Script/The Universal Mission/SupportAWACS.lua index eeaf683..95df77f 100644 --- a/Script/The Universal Mission/SupportAWACS.lua +++ b/Script/The Universal Mission/SupportAWACS.lua @@ -91,8 +91,9 @@ do function TUM.supportAWACS.createMenu() if not awacsGroupID then return end -- No AWACS + local rootMenu = TUM.getOrCreateRootMenu() - local rootPath = missionCommands.addSubMenu("⌾ Awacs") + local rootPath = missionCommands.addSubMenu("⌾ Awacs", rootMenu) missionCommands.addCommand("Bogey dope", rootPath, doCommandBogeyDope, nil) missionCommands.addCommand("Picture", rootPath, doCommandPicture, nil) end diff --git a/Script/The Universal Mission/Territories.lua b/Script/The Universal Mission/Territories.lua index 05cae5a..b41b1e3 100644 --- a/Script/The Universal Mission/Territories.lua +++ b/Script/The Universal Mission/Territories.lua @@ -125,7 +125,27 @@ do elseif DCSEx.string.startsWith(z.name:lower(), "water") then table.insert(waterZones, z) else - table.insert(missionZones, z) + local onlyZonesStartingWith = TUM.administrativeSettings.getValue(TUM.administrativeSettings.ONLY_ZONES_STARTINGWITH) + if onlyZonesStartingWith and #onlyZonesStartingWith > 0 then + if type(onlyZonesStartingWith) ~= "table" then + onlyZonesStartingWith = { onlyZonesStartingWith } + end + for _, zonePrefix in ipairs(onlyZonesStartingWith) do + if DCSEx.string.startsWith(z.name:lower(), zonePrefix:lower()) then + table.insert(missionZones, z) + break + end + end + else + local ignoreZonesStartingWith = TUM.administrativeSettings.getValue(TUM.administrativeSettings.IGNORE_ZONES_STARTINGWITH) + if ignoreZonesStartingWith then + if not DCSEx.string.startsWith(z.name:lower(), ignoreZonesStartingWith:lower()) then + table.insert(missionZones, z) + end + else + table.insert(missionZones, z) + end + end end end diff --git a/Script/The Universal Mission/WingmenMenu.lua b/Script/The Universal Mission/WingmenMenu.lua index 9e3fa4a..d5fd41e 100644 --- a/Script/The Universal Mission/WingmenMenu.lua +++ b/Script/The Universal Mission/WingmenMenu.lua @@ -75,11 +75,12 @@ do end function TUM.wingmenMenu.create() + local rootMenu = TUM.getOrCreateRootMenu() if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer if TUM.settings.getValue(TUM.settings.id.WINGMEN) <= 1 then return end -- No wingmen local isWW2 = (TUM.settings.getValue(TUM.settings) == DCSEx.enums.timePeriod.WORLD_WAR_2) -- Some options are different when time period is WW2 - local rootPath = missionCommands.addSubMenu("✈ Flight") + local rootPath = missionCommands.addSubMenu("✈ Flight", rootMenu) missionCommands.addCommand("Cover me!", rootPath, radioCommandCoverMe, nil) ------------------------------------------------------