Compare commits

...

57 Commits

Author SHA1 Message Date
Ambroise Garel
83c49bdd1f Removed duplicate lines in Russia units DB 2025-10-24 19:53:01 +02:00
Ambroise Garel
d7941c3485 Updated README and manual for release v0.3.251019 2025-10-19 19:20:15 +02:00
Ambroise Garel
3fd0d39be0 Missions with higher cloud cover and wind speed now award more XP 2025-10-19 18:55:02 +02:00
Ambroise Garel
a51ff5cff0 Weather reports now include cloud cover and rain 2025-10-19 18:53:57 +02:00
Ambroise Garel
a7bf94de5f Added TUM.weather table 2025-10-19 18:52:58 +02:00
Ambroise Garel
a66c3000c2 Added Offensive counter-air tasks 2025-10-19 17:34:16 +02:00
Ambroise Garel
2d96f23d2f Temporarily disabled Helicopter missions 2025-10-19 17:34:06 +02:00
Ambroise Garel
392f06d4e2 Commented out Library.tasks.helicopterDestroyInfantry 2025-10-19 17:33:49 +02:00
Ambroise Garel
32248a9b72 Added COLD WAR units 2025-10-19 17:33:37 +02:00
Ambroise Garel
5b2b17a8b0 Added placeholder NATO faction 2025-10-19 17:33:30 +02:00
Ambroise Garel
806421062b AWACS, bombers and transports kills on the ramp now don't suffer a -50% score penalty 2025-09-20 17:42:39 +02:00
Ambroise Garel
591ec6e10a "On landing" actions no longer triggered when player lands away from an airfield 2025-09-18 22:03:11 +02:00
Ambroise Garel
92a00160ef Increased LAND objective minimum distance from 200m to 500m 2025-09-18 21:59:06 +02:00
Ambroise Garel
085172f033 Added (disabled) helicopterPickUpInfantry task 2025-09-18 21:49:20 +02:00
Ambroise Garel
526651c6da Fixed typo in batch file menu 2025-09-18 21:34:19 +02:00
Ambroise Garel
b343d700a0 LAND objectives can now only be completed by helicopters, and force removal of target objective group 2025-09-18 17:03:19 +02:00
Ambroise Garel
da20586d51 Added missing net.allow_unsafe_api value in autoexec.cfg that prevented some advanced scripts from working 2025-09-18 16:58:21 +02:00
Ambroise Garel
69ea6b6d19 Added DCSEx.enums.taskFlag.FRIENDLY_TARGET 2025-09-18 15:02:34 +02:00
Ambroise Garel
5f5b940ef7 Minor fixes to helicopter-specific tasking 2025-09-18 14:50:37 +02:00
Ambroise Garel
014495246d Updated completed features in README 2025-09-18 14:44:03 +02:00
Ambroise Garel
3484e2544c AWACS aircraft now generated on mission start 2025-09-17 16:30:25 +02:00
Ambroise Garel
185685b706 Added helicopterDestroyInfantry task 2025-09-17 16:19:48 +02:00
Ambroise Garel
6b52970520 Added DCSEx.enums.taskFamily.HELICOPTER 2025-09-17 16:19:37 +02:00
Ambroise Garel
1c6b06a1a6 Replaced "ocaBomberStrike" with "ocaStrategicAircraftStrike" (can be bomber OR transport) 2025-09-17 16:08:34 +02:00
Ambroise Garel
525484385a Fixed bug with parking spot generation 2025-09-17 16:07:59 +02:00
Ambroise Garel
6ce6a8d2a1 Now checks DCSEx.enums.taskEvent.DAMAGE when checking objective completion 2025-09-17 15:32:05 +02:00
Ambroise Garel
7bfe0813d8 Parked aircraft OCA tasks now use DCSEx.enums.taskEvent.DAMAGE 2025-09-17 15:31:33 +02:00
Ambroise Garel
12e20a32ce Added DAMAGE to DCSEx.enums.taskEvent enum 2025-09-17 15:31:08 +02:00
Ambroise Garel
13d341c4a8 Check that objectives are spawned on unique parking spots 2025-09-17 14:24:11 +02:00
Ambroise Garel
0bc9780dd4 Temporarily disabled "ocaFighterStrike" task 2025-09-16 22:53:49 +02:00
Ambroise Garel
aa544e6c0c Parked aircraft now explode when hit to make them easier to kill 2025-09-16 22:42:43 +02:00
Ambroise Garel
c80d036feb Parked aircraft are now invisible to AI 2025-09-16 22:42:17 +02:00
Ambroise Garel
c557c3d74d Added new mission types to manual and README 2025-09-16 22:19:35 +02:00
Ambroise Garel
1892d97d15 Improved wording 2025-09-16 22:19:20 +02:00
Ambroise Garel
db5ee882c4 Added check so OCA missions can't be launched in zones without enemy airfields 2025-09-16 22:16:10 +02:00
Ambroise Garel
83ddfe7598 Fixed invalid parameter name in DCSEx.zones.getAirbases 2025-09-16 22:15:53 +02:00
Ambroise Garel
d0355af8e3 Added "allowShips" parameters to DCSEx.zones.getAirbases 2025-09-16 22:08:04 +02:00
Ambroise Garel
211cb15015 Updated score value for helicopters 2025-09-16 19:55:57 +02:00
Ambroise Garel
1905b4061a Wingmen now use proper A/A loadout for "helo hunt" tasking 2025-09-16 19:54:39 +02:00
Ambroise Garel
b93bcb4734 Updated bug fixes 2025-09-16 19:51:21 +02:00
Ambroise Garel
61e017fbe8 AWACS can now detect helicopters 2025-09-16 19:50:56 +02:00
Ambroise Garel
655b5bc6aa Added "Helicopter hunt" mission objective 2025-09-16 19:49:35 +02:00
Ambroise Garel
d13d94f1bb Added "helo hunt" tasks for attack and transport helicopters 2025-09-16 19:48:58 +02:00
Ambroise Garel
0be508c42c Updated wording 2025-09-16 19:48:25 +02:00
Ambroise Garel
b9ce5ef340 Added "OCA bomber strike" task 2025-09-16 19:48:01 +02:00
Ambroise Garel
3453bccf85 Added "ocaBomberStrike" task 2025-09-16 19:47:38 +02:00
Ambroise Garel
b392c55828 Added function DCSEx.zones.getAirbases(zoneTable, coalition) 2025-09-16 19:05:10 +02:00
Ambroise Garel
ff15793f06 Updated wording 2025-09-16 18:59:08 +02:00
Ambroise Garel
e7f9ba4f92 Added getEnemyAirbaseInZone(zone) and parked aircraft target generation 2025-09-16 18:44:09 +02:00
Ambroise Garel
98baf8a3c0 Added support for parked aircraft generation 2025-09-16 18:43:50 +02:00
Ambroise Garel
4a94770322 Added ocaFighterStrike task 2025-09-16 18:43:40 +02:00
Ambroise Garel
885b14315f Temporary disabled "ocaAirbase" task 2025-09-16 18:43:14 +02:00
Ambroise Garel
4f1ad38eeb Added "airbase strike" OCA mission objective 2025-09-16 12:16:58 +02:00
Ambroise Garel
404095967d Added OCA to DCSEx.enums.taskFamily 2025-09-16 12:07:13 +02:00
Ambroise Garel
4ebbf398d2 Added AIRBASE_TARGET and PARKED_AIRCRAFT_TARGET to DCSEx.enums.taskFlag 2025-09-16 12:00:35 +02:00
Ambroise Garel
babc9a183e Added out-of-bounds check on names array 2025-09-15 16:21:26 +02:00
Ambroise Garel
0b792e4b25 Fixed errors in README 2025-09-15 12:13:28 +02:00
34 changed files with 866 additions and 68 deletions

View File

@ -11,8 +11,9 @@
if not net then net = {} end
net.allow_unsafe_api = { -- this defines the secure zones where net.dostring_in() can be called from
"scripting",
}
net.allow_dostring_in = { -- and this defines the zones that should be addressed from net.dostring_in()
"mission"
"mission",
}

View File

@ -0,0 +1,51 @@
-- do
-- Library.factions.tables["NATO"] = {}
-- Library.factions.tables["NATO"].theaters = {}
-- Library.factions.tables["NATO"].timePeriods = {}
-- Library.factions.tables["NATO"].units = {}
-- Library.factions.tables["NATO"].units[DCSEx.enums.timePeriod.WORLD_WAR_2] = {}
-- Library.factions.tables["NATO"].units[DCSEx.enums.timePeriod.KOREA_WAR] = {}
-- Library.factions.tables["NATO"].units[DCSEx.enums.timePeriod.VIETNAM_WAR] = {}
-- Library.factions.tables["NATO"].units[DCSEx.enums.timePeriod.COLD_WAR] = {}
-- Library.factions.tables["NATO"].units[DCSEx.enums.timePeriod.MODERN] = {
-- [DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Gepard", "Vulcan" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_AAA_STATIC] = { "Gepard", "Vulcan" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS] = { "Soldier stinger" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*Patriot" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = { "*HAWK", "*NASAMS" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "rapier_fsa", "Roland ADS" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR] = { "M6 Linebacker", "M48 Chaparral", "M1097 Avenger" },
-- [DCSEx.enums.unitFamily.GROUND_APC] = { "AAV7", "Cobra", "LAV-25", "M-2 Bradley", "M-113", "M1045 HMMWV TOW", "M1126 Stryker ICV", "M1128 Stryker MGS", "Marder", "MCV-80", "MLRS FDDM", "TPZ", "CHAP_M1130", "CHAP_MATV" },
-- [DCSEx.enums.unitFamily.GROUND_ARTILLERY] = { "M-109", "MLRS", "CHAP_M142_ATACMS_M48", "CHAP_M142_GMLRS_M31" },
-- [DCSEx.enums.unitFamily.GROUND_INFANTRY] = { "Soldier M4 GRG", "Soldier M4", "Soldier M249", "Soldier RPG" },
-- [DCSEx.enums.unitFamily.GROUND_MBT] = { "Challenger2", "Leclerc", "Leopard-2", "Leopard1A3", "M-1 Abrams", "Merkava_Mk4" },
-- [DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
-- [DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Land_Rover_101_FC", "Land_Rover_109_S3", "M 818", "CHAP_M1083" },
-- [DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "AH-1W", "AH-64D", "OH-58D", "SA342L", "SA342M", "SA342Minigun", "SA342Mistral" },
-- [DCSEx.enums.unitFamily.HELICOPTER_TRANSPORT] = { "CH-47D", "CH-53E", "SH-60B", "UH-60A" },
-- [DCSEx.enums.unitFamily.PLANE_ATTACK] = { "A-10C_2" },
-- [DCSEx.enums.unitFamily.PLANE_AWACS] = { "E-2C", "E-3A" },
-- [DCSEx.enums.unitFamily.PLANE_BOMBER] = { "B-1B Lancer", "B-52H" },
-- [DCSEx.enums.unitFamily.PLANE_FIGHTER] = { "F-16C_50", "FA-18C_hornet" },
-- [DCSEx.enums.unitFamily.PLANE_TANKER] = { "KC-135", "KC135MPRS" },
-- [DCSEx.enums.unitFamily.PLANE_TRANSPORT] = { "C-17A", "C-130" },
-- [DCSEx.enums.unitFamily.PLANE_UAV] = { "RQ-1A Predator" },
-- [DCSEx.enums.unitFamily.SHIP_CARGO] = { "Dry-cargo ship-1", "Dry-cargo ship-2", "ELNYA", "Ship_Tilde_Supply" },
-- [DCSEx.enums.unitFamily.SHIP_CARRIER] = { "CVN_71", "CVN_72", "CVN_73", "CVN_75", "hms_invincible", "LHA_Tarawa", "Stennis" },
-- [DCSEx.enums.unitFamily.SHIP_CRUISER] = { "TICONDEROG" },
-- [DCSEx.enums.unitFamily.SHIP_FRIGATE] = { "PERRY", "USS_Arleigh_Burke_IIa" },
-- [DCSEx.enums.unitFamily.SHIP_LIGHT] = { "speedboat" },
-- [DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "CastleClass_01", "La_Combattante_II" },
-- [DCSEx.enums.unitFamily.SHIP_SUBMARINE] = { "santafe" },
-- -- [DCSEx.enums.unitFamily.STATIC_STRUCTURE] = { "af_hq", ".Command Center", "Building01_PBR", "Building02_PBR", "Building03_PBR", "Building04_PBR", "Building05_PBR", "Bunker", "Chemical tank A", "Comms tower M", "FARP Fuel Depot", "outpost", "Sandbox", "Workshop A" },
-- [DCSEx.enums.unitFamily.STATIC_STRUCTURE] = { "af_hq", ".Command Center", "Building01_PBR", "Building02_PBR", "Building03_PBR", "Building04_PBR", "Building05_PBR", "Chemical tank A", "Comms tower M", "FARP Fuel Depot", "outpost", "Workshop A" },
-- }
-- end

View File

@ -21,7 +21,7 @@ do
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Ural-375 ZU-23", "ZSU_57_2", "ZSU-23-4 Shilka" },
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_STATIC] = { "ZU-23 Emplacement Closed", "ZU-23 Emplacement" },
[DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS] = { "SA-18 Igla manpad", "SA-18 Igla-S manpad" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*SA-2", "*SA-10" },
-- [DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*SA-2", "*SA-10" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*SA-10" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = { "*SA-3", "*SA-6", "*SA-11" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "2S6 Tunguska", "Osa 9A33 ln", "Tor 9A331", "CHAP_PantsirS1", "CHAP_TorM2" },
@ -45,8 +45,6 @@ do
[DCSEx.enums.unitFamily.PLANE_TRANSPORT] = { "An-26B", "An-30M", "IL-76MD" },
[DCSEx.enums.unitFamily.PLANE_UAV] = { "WingLoong-I" },
[DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "Ka-50", "Mi-24V", "Mi-28N" },
[DCSEx.enums.unitFamily.SHIP_CARGO] = { "Dry-cargo ship-1", "Dry-cargo ship-2", "ELNYA", "Ship_Tilde_Supply" },
[DCSEx.enums.unitFamily.SHIP_CARRIER] = { "CV_1143_5", "KUZNECOW" },
[DCSEx.enums.unitFamily.SHIP_CRUISER] = { "MOSCOW", "PIOTR" },

View File

@ -10,7 +10,45 @@ do
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.WORLD_WAR_2] = {}
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.KOREA_WAR] = {}
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.VIETNAM_WAR] = {}
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.COLD_WAR] = {}
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.COLD_WAR] = {
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Gepard", "Vulcan" },
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_STATIC] = { "Gepard", "Vulcan" },
[DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS] = { "Soldier stinger" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*Patriot" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = { "*HAWK", "*NASAMS" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "rapier_fsa", "Roland ADS" },
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR] = { "M6 Linebacker", "M48 Chaparral", "M1097 Avenger" },
[DCSEx.enums.unitFamily.GROUND_APC] = { "AAV7", "Cobra", "LAV-25", "M-2 Bradley", "M-113", "M1045 HMMWV TOW", "M1126 Stryker ICV", "M1128 Stryker MGS", "Marder", "MCV-80", "MLRS FDDM", "TPZ", "CHAP_M1130", "CHAP_MATV" },
[DCSEx.enums.unitFamily.GROUND_ARTILLERY] = { "M-109", "MLRS", "CHAP_M142_ATACMS_M48", "CHAP_M142_GMLRS_M31" },
[DCSEx.enums.unitFamily.GROUND_INFANTRY] = { "Soldier M4 GRG", "Soldier M4", "Soldier M249", "Soldier RPG" },
[DCSEx.enums.unitFamily.GROUND_MBT] = { "Challenger2", "Leclerc", "Leopard-2", "Leopard1A3", "M-1 Abrams", "Merkava_Mk4" },
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
[DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Land_Rover_101_FC", "Land_Rover_109_S3", "M 818", "CHAP_M1083" },
[DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "AH-1W", "AH-64D", "OH-58D", "SA342L", "SA342M", "SA342Minigun", "SA342Mistral" },
[DCSEx.enums.unitFamily.HELICOPTER_TRANSPORT] = { "CH-47D", "CH-53E", "SH-60B", "UH-60A" },
[DCSEx.enums.unitFamily.PLANE_ATTACK] = { "A-10C_2" },
[DCSEx.enums.unitFamily.PLANE_AWACS] = { "E-2C", "E-3A" },
[DCSEx.enums.unitFamily.PLANE_BOMBER] = { "B-1B Lancer", "B-52H" },
[DCSEx.enums.unitFamily.PLANE_FIGHTER] = { "F-16C_50", "FA-18C_hornet" },
[DCSEx.enums.unitFamily.PLANE_TANKER] = { "KC-135", "KC135MPRS" },
[DCSEx.enums.unitFamily.PLANE_TRANSPORT] = { "C-17A", "C-130" },
[DCSEx.enums.unitFamily.PLANE_UAV] = { "RQ-1A Predator" },
[DCSEx.enums.unitFamily.SHIP_CARGO] = { "Dry-cargo ship-1", "Dry-cargo ship-2", "ELNYA", "Ship_Tilde_Supply" },
[DCSEx.enums.unitFamily.SHIP_CARRIER] = { "CVN_71", "CVN_72", "CVN_73", "CVN_75", "hms_invincible", "LHA_Tarawa", "Stennis" },
[DCSEx.enums.unitFamily.SHIP_CRUISER] = { "TICONDEROG" },
[DCSEx.enums.unitFamily.SHIP_FRIGATE] = { "PERRY", "USS_Arleigh_Burke_IIa" },
[DCSEx.enums.unitFamily.SHIP_LIGHT] = { "speedboat" },
[DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "CastleClass_01", "La_Combattante_II" },
[DCSEx.enums.unitFamily.SHIP_SUBMARINE] = { "santafe" },
-- [DCSEx.enums.unitFamily.STATIC_STRUCTURE] = { "af_hq", ".Command Center", "Building01_PBR", "Building02_PBR", "Building03_PBR", "Building04_PBR", "Building05_PBR", "Bunker", "Chemical tank A", "Comms tower M", "FARP Fuel Depot", "outpost", "Sandbox", "Workshop A" },
[DCSEx.enums.unitFamily.STATIC_STRUCTURE] = { "af_hq", ".Command Center", "Building01_PBR", "Building02_PBR", "Building03_PBR", "Building04_PBR", "Building05_PBR", "Chemical tank A", "Comms tower M", "FARP Fuel Depot", "outpost", "Workshop A" },
}
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.MODERN] = {
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Gepard", "Vulcan" },

View File

@ -0,0 +1,22 @@
-- Library.tasks.helicopterDestroyInfantry = {
-- taskFamily = DCSEx.enums.taskFamily.HELICOPTER,
-- description =
-- {
-- briefing = {
-- "",
-- },
-- short = "Neutralize enemy infantry",
-- },
-- conditions = {
-- difficultyMinimum = 0,
-- eras = {},
-- },
-- completionEvent = DCSEx.enums.taskEvent.DESTROY,
-- flags = { },
-- minimumDistance = DCSEx.converter.nmToMeters(5.0),
-- safeRadius = 100,
-- surfaceType = nil,
-- targetCount = { 6, 8 },
-- targetFamilies = { DCSEx.enums.unitFamily.GROUND_INFANTRY },
-- waypointInaccuracy = DCSEx.converter.nmToMeters(1.5)
-- }

View File

@ -0,0 +1,22 @@
-- Library.tasks.helicopterPickUpInfantry = {
-- taskFamily = DCSEx.enums.taskFamily.HELICOPTER,
-- description =
-- {
-- briefing = {
-- "",
-- },
-- short = "Land and pick up friendly infantry",
-- },
-- conditions = {
-- difficultyMinimum = 0,
-- eras = {},
-- },
-- completionEvent = DCSEx.enums.taskEvent.LAND,
-- flags = { DCSEx.enums.taskFlag.FRIENDLY_TARGET },
-- minimumDistance = DCSEx.converter.nmToMeters(5.0),
-- safeRadius = 100,
-- surfaceType = nil,
-- targetCount = { 6, 8 },
-- targetFamilies = { DCSEx.enums.unitFamily.GROUND_INFANTRY },
-- waypointInaccuracy = DCSEx.converter.nmToMeters(1.5)
-- }

View File

@ -0,0 +1,25 @@
Library.tasks.heloHuntAttack = {
taskFamily = DCSEx.enums.taskFamily.HELO_HUNT,
description =
{
briefing = {
"Locate and neutralize all enemy rotary-wing assets in the area.",
"Enemy attack helicopters are staging nearby, you are to eliminate them before they launch their attack.",
"Intel confirms a group of hostile gunships in the area. You must render them combat-ineffective.",
"Engage and destroy rotary assets nearby, crippling enemy air support.",
},
short = "Destroy enemy attack helicopters",
},
conditions = {
difficultyMinimum = 0,
eras = {},
},
completionEvent = DCSEx.enums.taskEvent.DESTROY,
flags = { },
minimumDistance = DCSEx.converter.nmToMeters(10.0),
safeRadius = 100,
surfaceType = nil,
targetCount = { 2, 3 },
targetFamilies = { DCSEx.enums.unitFamily.HELICOPTER_ATTACK },
waypointInaccuracy = DCSEx.converter.nmToMeters(6.0)
}

View File

@ -0,0 +1,22 @@
Library.tasks.heloHuntTransport = {
taskFamily = DCSEx.enums.taskFamily.HELO_HUNT,
description =
{
briefing = {
"",
},
short = "Destroy enemy transport helicopters",
},
conditions = {
difficultyMinimum = 0,
eras = {},
},
completionEvent = DCSEx.enums.taskEvent.DESTROY,
flags = { },
minimumDistance = DCSEx.converter.nmToMeters(10.0),
safeRadius = 100,
surfaceType = nil,
targetCount = { 2, 3 },
targetFamilies = { DCSEx.enums.unitFamily.HELICOPTER_TRANSPORT },
waypointInaccuracy = DCSEx.converter.nmToMeters(6.0)
}

View File

@ -0,0 +1,26 @@
-- Library.tasks.ocaAirbase = {
-- taskFamily = DCSEx.enums.taskFamily.OCA,
-- description =
-- {
-- briefing = {
-- "Neutralizing this enemy airbase will eliminate their ability to conduct sustained air sorties and restore local air superiority.",
-- "Taking out this airbase will disrupt their logistics and sortie tempo, buying time for our ground forces to consolidate.",
-- "This airbase is the hub of enemy reconnaissance and close air support—removing it reduces battlefield intelligence and strike pressure.",
-- "Outlasting enemy air campaign requires degrading that facility's operational capacity to prevent rotational sorties.",
-- "Disabling this node of their air network constrains their command-and-control reach and limits coordinated strikes."
-- },
-- short = "Destroy enemy airbase",
-- },
-- conditions = {
-- difficultyMinimum = 0,
-- eras = {},
-- },
-- completionEvent = DCSEx.enums.taskEvent.DESTROY,
-- flags = { DCSEx.enums.taskFlag.ALLOW_JTAC, DCSEx.enums.taskFlag.AIRBASE_TARGET },
-- minimumDistance = 0.0,
-- safeRadius = 0,
-- surfaceType = land.SurfaceType.LAND,
-- targetCount = { 1, 1 },
-- targetFamilies = { DCSEx.enums.unitFamily.STATIC_SCENERY },
-- waypointInaccuracy = 0.0
-- }

View File

@ -0,0 +1,25 @@
-- Library.tasks.ocaFighterStrike = {
-- taskFamily = DCSEx.enums.taskFamily.OCA,
-- description =
-- {
-- briefing = {
-- "Destroying enemy fighters on the ramp will prevent immediate sortie generation and blunt their ability to provide CAS against our advancing forces.",
-- "A ramp strike against enemy aircraft will pre-empt an imminent scramble warning we have intel for, preventing a coordinated mass launch.",
-- "Removing enemy fighters on the ramp will degrade enemy air superiority over the battlespace and protects our medevac and resupply corridors.",
-- "Priority is to eliminate immediate airborne threats on the ramp to safeguard coalition force freedom of maneuver.",
-- },
-- short = "Destroy enemy fighter on the ramp",
-- },
-- conditions = {
-- difficultyMinimum = 0,
-- eras = {},
-- },
-- completionEvent = DCSEx.enums.taskEvent.DAMAGE,
-- flags = { DCSEx.enums.taskFlag.ALLOW_JTAC, DCSEx.enums.taskFlag.PARKED_AIRCRAFT_TARGET },
-- minimumDistance = 0.0,
-- safeRadius = 0,
-- surfaceType = land.SurfaceType.LAND,
-- targetCount = { 1, 1 },
-- targetFamilies = { DCSEx.enums.unitFamily.PLANE_FIGHTER },
-- waypointInaccuracy = 0.0
-- }

View File

@ -0,0 +1,22 @@
Library.tasks.ocaStrategicAircraftStrike = {
taskFamily = DCSEx.enums.taskFamily.OCA,
description =
{
briefing = {
""
},
short = "Destroy enemy aircraft on the ramp",
},
conditions = {
difficultyMinimum = 0,
eras = {},
},
completionEvent = DCSEx.enums.taskEvent.DAMAGE,
flags = { DCSEx.enums.taskFlag.ALLOW_JTAC, DCSEx.enums.taskFlag.PARKED_AIRCRAFT_TARGET },
minimumDistance = 0.0,
safeRadius = 0,
surfaceType = land.SurfaceType.LAND,
targetCount = { 1, 1 },
targetFamilies = { DCSEx.enums.unitFamily.PLANE_BOMBER, DCSEx.enums.unitFamily.PLANE_TRANSPORT },
waypointInaccuracy = 0.0
}

View File

@ -8,7 +8,7 @@ IF %ERRORLEVEL% NEQ 0 goto ERROR-NO-PHP
@REM CREATE SCENARIOS
@REM -------------------------------------------
set buildConfig=p
echo THE ULTIMATE MISSION BUILDER SCRIPT:
echo THE UNIVERSAL MISSION BUILDER SCRIPT:
echo - Build [P]ersianGulf debug theater only (default)
echo - Build all [D]ebug theaters
echo - Build all [R]elease theaters

View File

@ -1,6 +1,6 @@
# The Universal Mission for DCS World
**Current version: open beta 0.3.250914** (see the "version history" section at the end of this file for a list of the latest changes)
**Current version: open beta 0.3.251019** (see the "version history" section at the end of this file for a list of the latest changes)
**This is a BETA version, there may be bugs and there WILL be unbalanced stuff.**
@ -26,7 +26,7 @@ The Universal Mission for DCS World is a fully dynamic single-player/PvE mission
**Please read the "planned development" section below for more information.**
- The current version supports only modern (post-Cold War) units and Caucasus, Kola, Marianas, Persian Gulf and Syria theaters
- The current version supports only modern (post-Cold War) units and Caucasus, Germany, Kola, Marianas, Persian Gulf and Syria theaters
- Not all mission types are supported yet
- Career progress may be lost because of future updates, don't get too attached to it
@ -42,7 +42,6 @@ The Universal Mission for DCS World is a fully dynamic single-player/PvE mission
- Download the latest release from this GitHub page.
- Copy the provided autoexec.cfg file to your **[Saved Games]\DCS\Config directory**
- Please note: as of DCS 2.9.18.12899, it seems the autoexec.cfg file [is no longer needed](https://www.digitalcombatsimulator.com/en/news/changelog/release/2.9.18.12899/) but I advise you to copy it anyway, ED might change its mind again.
- Copy the .miz files for your theater(s) of choice to your **[Saved Games]\DCS\Missions directory**
- _**(Optional but strongly recommended)**_ Unsanitize the Lua IO module. You don't have to do this, but the persistent career system won't work if you don't. To do it, open the file **[DCS World installation directory]\Scripts\MissionScripting.lua** with a text editor and comment or remove the line "sanitizeModule('io')". Make sure you restart DCS World once you've modified the file.
- Please note: should you want to backup, delete or transfer it, career progress is saved in **[DCS World installation directory]\TheUniversalMission.sav**
@ -75,14 +74,17 @@ Please also note that PvP is not supported at the moment and that the mission wi
- Additional content
- [ ] More objectives types
- [ ] Close air support
- [ ] Helicopter-specific tasking (land and pick up units, suppress infantry...)
- [ ] Improved OCA missions: bomb enemy airbases
- Balance improvements
- [ ] Night missions should award more XP
- [ ] Tweaked XP requirements for medals/promotions
- Bug fixes
- [ ] AWACS datalinked contacts not showing on SA pages
- Extras
- [ ] GitHub page
- Improvements
- [ ] Better weather reports (cloud cover, rain, etc)
- Misc
- New features
- [ ] Administrative settings menu
- [ ] Friendly air defenses
@ -94,17 +96,15 @@ Please also note that PvP is not supported at the moment and that the mission wi
- Additional/improved radio messages
- More "flavor" radio messages ("fence in" when player approaches the AO, etc) so the world will feel more alive
- Better balancing of the player career awards and promotions
- Better use of context for "ambient" radio messages (should only warn of a SAM launch if an AI pilot is there to witness it, etc)
- Improved score multiplier taking into account various aspects of mission difficulty (weather, nighttime ops...)
- Laser designation of targets by JTAC
- New objectives: helicopter (drop/pickup units...), CAP, CAS, OCA (airbase attack)
- New objectives: CAP
- Support for all missing DCS World theaters
- Support for more factions and five different time periods (World War 2, Korea war, Vietnam war, late Cold war, Modern)
### Medium priority
- Combined Arms support
- Combined arms support
- Modded units support (other than player-controlled aircraft, those are already supported: just add them to the mission)
- Spawning of tankers for long-range missions
- (maybe) Text (not voiceover) localization, if there's enough popular demand
@ -154,15 +154,31 @@ The core script is quite simple and small, I probably won't need too much help w
## Version history
- **0.3.251019** (10/19/2025)
- Additional content
- New mission types
- "Helicopter hunt" missions: locate and intercept enemy attack and transport helicopters
- Offensive counter-air: destroy enemy aircraft on the ramp before they take off **(Requires a target location with at least one enemy land airbase, or mission type will automatically be changed to ground attack)**
- Balance improvements
- Missions with higher cloud cover and wind speed now award more XP
- Bug fixes
- AWACS datalinked now showing on SA pages
- AWACS can now detect enemy helicopters
- Added missing net.allow_unsafe_api value in autoexec.cfg that prevented some advanced scripts from working
- "On landing" actions (medal/promotions check, AI wingmen removal, etc) no longer triggered when player lands away from an airfield (e.g. in a Harrier on a helicopter)
- Improvements
- ATC weather reports now include informations about cloud cover and rain
- Misc
- AWACS unit now spawned when mission is loaded
- **0.3.250914** (09/14/2025)
- **MAJOR CHANGE:**
- Use of "Client" slot instead of "Player" slot even in single-player missions, allowing the player to respawn on death/ejection instead of having to start the whole mission again
- "Player" should not be used anymore. Single-player missions must use a single "Client" slot instead
- Use of "Client" slots instead of "Player" slots even in single-player missions, allowing the player to respawn on death/ejection instead of having to start the whole mission again
- "Player" slots should not be used anymore. Single-player missions must now use a single "Client" slot instead
- Additional content
- Added new units (Currenthill unit pack) from DCS 2.9.19.13478
- Support for "Cold War Germany" theater
- Balance improvements
- Enemy CAP respawn rate now decreases the more enemy planes are shot
- Enemy CAP respawn rate now decreases as more enemy planes are shot down
- Bug fixes
- Some player callsigns were causing a script error at startup
- Fixed wrong filename in "enemy infantry killed" messages
@ -176,7 +192,7 @@ The core script is quite simple and small, I probably won't need too much help w
- Mission now autostarts (if it wasn't started yet) when all players have taken off
- Quality of life/minor tweaks
- Increased AWACS aircraft spawn altitude
- Target coordinates radio message displayed for a longer time so players have the time to write them down or enter them in their flight computer
- Target coordinates radio message displayed for a longer time so players have time to write them down or enter them in their flight computer
- **0.2.250729** (07/29/2025)
- **MAJOR CHANGE:** Added all new wingman system
- Far for perfect but a lot better than DCS's default wingmen

View File

@ -29,9 +29,10 @@ DCSEx.enums.lineType = {
-- Event to check to see if a task/objective is complete
-------------------------------------
DCSEx.enums.taskEvent = {
DESTROY = 1,
DESTROY_SCENERY = 2,
LAND = 3,
DAMAGE = 1,
DESTROY = 2,
DESTROY_SCENERY = 3,
LAND = 4,
}
-------------------------------------
@ -39,26 +40,29 @@ DCSEx.enums.taskEvent = {
-------------------------------------
DCSEx.enums.taskFamily = {
ANTISHIP = 1,
-- CAP = 2, -- TODO
-- CAS = 3, -- TODO
GROUND_ATTACK = 2, -- 4
-- HELICOPTER = XXX, -- 5
-- HELO_HUNT = XXX, -- 6
INTERCEPTION = 3, -- 7
-- OCA = XXX, -- 8
SEAD = 4, --9
STRIKE = 5, -- 10
-- CAP = XXX,
-- CAS = XXX,
GROUND_ATTACK = 2,
-- HELICOPTER = 3,
HELO_HUNT = 3,
INTERCEPTION = 4,
OCA = 5,
SEAD = 6,
STRIKE = 7,
}
-------------------------------------
-- Special events for tasks
-------------------------------------
DCSEx.enums.taskFlag = {
ALLOW_JTAC = 1,
DESTROY_TRACK_RADARS_ONLY = 2,
MOVING = 3,
ON_ROADS = 4,
SCENERY_TARGET = 5
AIRBASE_TARGET = 1,
ALLOW_JTAC = 2,
DESTROY_TRACK_RADARS_ONLY = 3,
MOVING = 4,
ON_ROADS = 5,
PARKED_AIRCRAFT_TARGET = 6,
SCENERY_TARGET = 7,
FRIENDLY_TARGET = 8
}
-------------------------------------

View File

@ -374,6 +374,14 @@ do
setAircraftTaskOrbit(groupTable, options)
end
-- For parked aircraft
if options.airbaseID and options.parkingID then
groupTable.route.points[1].action = "From Parking Area"
groupTable.route.points[1].airdromeId = options.airbaseID
groupTable.route.points[1].type = "TakeOffParking"
groupTable.uncontrolled = true
end
if options.callsign then
groupCallsign = options.callsign
else
@ -449,6 +457,12 @@ do
if aircraftDB.properties then
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
end
-- For parked aircraft
if options.airbaseID and options.parkingID then
unitTable.parking = tostring(options.parkingID)
end
-- Setup datalink
local datalinkString = tostring(dataLinkID)
if #datalinkString == 3 then

View File

@ -2,6 +2,7 @@
-- DCSEX.ZONES - FUNCTIONS RELATED TO MAP TRIGGER ZONES
-- ====================================================================================
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
-- DCSEx.zones.getAirbases(zone, coalID, allowShips)
-- DCSEx.zones.getAll()
-- DCSEx.zones.getByName(name)
-- DCSEx.zones.getCenter(zoneTable)
@ -72,6 +73,43 @@ function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawNa
return markerID
end
-------------------------------------
-- Returns all airbases in the zone
-------------------------------------
-- @param zoneTable Table of the zone in which to search
-- @param coalID Coalition (from the coalition.side enum) the airbase must belong to. Default is nil, which means "all coalitions"
-- @param allowShips Should ships be allowed?
-- @return Table of airbases
-------------------------------------
function DCSEx.zones.getAirbases(zoneTable, coalID, allowShips)
coalID = coalID or nil
allowShips = allowShips or false
local coalitionSides = { coalition.side.RED, coalition.side.BLUE }
if coalID then coalitionSides = { coalID } end
local validAirbases = {}
for _,side in ipairs(coalitionSides) do
for _,ab in ipairs(coalition.getAirbases(side)) do
local abDesc = ab:getDesc()
local isValid = true
if ab:getDesc().category == Airbase.Category.HELIPAD then
isValid = false
elseif ab:getDesc().category == Airbase.Category.SHIP and not allowShips then
isValid = false
end
if isValid then
if DCSEx.zones.isPointInside(zoneTable, ab:getPoint()) then
table.insert(validAirbases, ab)
end
end
end
end
return validAirbases
end
-------------------------------------
-- Returns all trigger zones
-------------------------------------

File diff suppressed because one or more lines are too long

View File

@ -167,6 +167,8 @@ function TUM.initialize()
timer.scheduleFunction(TUM.onClockTick, nil, timer.getTime() + math.random(10, 15))
end
end
TUM.supportAWACS.create()
end
if TUM.administrativeSettings.getValue(TUM.administrativeSettings.INITIALIZE_AUTOMATICALLY) then

View File

@ -132,14 +132,17 @@ do
commonWeatherInfo = commonWeatherInfo.." (day, sunset at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, true))..")\n"
end
commonWeatherInfo = commonWeatherInfo.."- Cloud cover: "..TUM.weather.getWeatherName(nil, true).."\n"
commonWeatherInfo = commonWeatherInfo.."- Wind: "..TUM.weather.getWindName()..", with avg. speed of "..tostring(math.floor(Library.environment.getWindAverage())).."m/s\n"
commonWeatherInfo = commonWeatherInfo.."- Average ground-level temperature is "..getTemperatureCelsiusAndFarenheit(env.mission.weather.season.temperature).."\n"
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
for _,p in ipairs(players) do
local lTemperature, _ = atmosphere.getTemperatureAndPressure(p:getPoint())
local localWeatherInfo = "- Average windspeed is "..tostring(math.floor(Library.environment.getWindAverage())).."m/s\n"
localWeatherInfo = localWeatherInfo.."- Windspeed at your location is "..DCSEx.math.getLength3D(atmosphere.getWind(p:getPoint())).."m/s\n"
localWeatherInfo = localWeatherInfo.."- Average ground-level temperature is "..getTemperatureCelsiusAndFarenheit(env.mission.weather.season.temperature).."\n"
localWeatherInfo = localWeatherInfo.."- Temperature at your location is "..getTemperatureCelsiusAndFarenheit(lTemperature, true)
local localWeatherInfo = ""
localWeatherInfo = localWeatherInfo.."- Wind speed at your location is "..tostring(math.floor(DCSEx.math.getLength3D(atmosphere.getWind(p:getPoint())))).."m/s\n"
localWeatherInfo = localWeatherInfo.."- Temperature at your location is "..getTemperatureCelsiusAndFarenheit(lTemperature, true).."\n"
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcWeatherUpdate", { commonWeatherInfo..localWeatherInfo }, "Control", delayRadioAnswer)
end

View File

@ -225,11 +225,9 @@ do
if not event.initiator then return end -- No event initiator
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end -- Initiator isn't an unit
if event.initiator:getCoalition() ~= TUM.settings.getPlayerCoalition() then return end -- Not a friendly
if not event.place then return end -- Not landed at an airbase (e.g. helicopter landing on the ground)
local baseName = "AIRBASE"
if event.place then
baseName = event.place:getName():upper()
end
local baseName = event.place:getName():upper()
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) or not event.initiator:getPlayerName() then
doAmbientChatter("atcSafeLanding", {event.initiator:getCallsign(), baseName}, baseName.." ATC", 1)

View File

@ -32,7 +32,7 @@ do
end
-- Called when a unit is destroyed
-- Called when an unit is destroyed
local function onEventDead(event)
if not event.initiator then return end -- Nothing was hit
@ -53,6 +53,17 @@ do
)
end
-- Called when an unit takes damage
local function onEventHit(event)
if not event.initiator then return end -- Nothing was hit
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end -- Target wasn't an unit
if event.initiator:getCoalition() ~= TUM.settings.getEnemyCoalition() then return end -- Unit is not an enemy
if event.initiator:getDesc().category ~= Unit.Category.AIRPLANE and event.initiator:getDesc().category ~= Unit.Category.HELICOPTER then return end -- Wasn't an aircraft
if event.initiator:inAir() then return end -- Unit is currently in air
trigger.action.explosion(event.initiator:getPoint(), 100) -- Detonate the parked aircraft, to make it easier to kill
end
function TUM.ambientWorld.removeAll()
for _,id in ipairs(groupIDs) do
DCSEx.world.destroyGroupByID(id)
@ -70,6 +81,8 @@ do
if event.id == world.event.S_EVENT_DEAD then
onEventDead(event)
elseif event.id == world.event.S_EVENT_HIT then
onEventHit(event)
end
end
end

View File

@ -68,7 +68,10 @@ do
local runwayTouchEvent = { id = world.event.S_EVENT_RUNWAY_TOUCH, initiator = playerUnit }
TUM.onEvent(runwayTouchEvent)
local landingEvent = { id = world.event.S_EVENT_LAND, initiator = playerUnit }
local friendlyAirbases = coalition.getAirbases(TUM.settings.getPlayerCoalition())
if not friendlyAirbases or #friendlyAirbases == 0 then return end
local landingEvent = { id = world.event.S_EVENT_LAND, place = friendlyAirbases[1], initiator = playerUnit }
timer.scheduleFunction(TUM.onEvent, landingEvent, timer.getTime() + 1)
end

View File

@ -69,7 +69,16 @@ do
end
end
trigger.action.outText("Generating mission and loading assets, this can take some time...", 5)
-- If an OCA mission has been selected and the selected target zone doesn't contain any enemy airfield, default to ground attack
if TUM.settings.getValue(TUM.settings.id.TASKING) == DCSEx.enums.taskFamily.OCA then
local zone = DCSEx.zones.getByName(TUM.settings.getValue(TUM.settings.id.TARGET_LOCATION, true))
if #DCSEx.zones.getAirbases(zone, TUM.settings.getEnemyCoalition()) == 0 then
trigger.action.outText("OCA tasking selected in a zone without any enemy airfields, defaulting to GROUND ATTACK tasking.", 3)
TUM.settings.setValue(TUM.settings.id.TASKING, DCSEx.enums.taskFamily.GROUND_ATTACK, true)
end
end
trigger.action.outText("Generating mission and loading assets, this can take some time...", 3)
-- Add a little delay for the "Generating mission..." message be printed out. Once generation begins, the main DCS thread will be to busy to output anything.
timer.scheduleFunction(TUM.mission.beginMission, false, timer.getTime() + 1)

View File

@ -62,6 +62,7 @@ do
closeMission(true)
TUM.intermission.removeMissionZonesMarkers()
TUM.objectivesMaker.clear()
for _=1,TUM.settings.getValue(TUM.settings.id.TARGET_COUNT) do
TUM.objectives.add()
end
@ -72,7 +73,7 @@ do
return
end
TUM.supportAWACS.create() -- Create the AWACS aircraft if it wasn't airborne already
-- 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

View File

@ -15,7 +15,6 @@ do
-- @param event A DCS World event, possibly a S_EVENT_LAND event
-------------------------------------
local function removeAIAircraftOnLandEvent(event)
if event.id ~= world.event.S_EVENT_LAND then return end
if not event.initiator then return end
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end -- Not an unit
if event.initiator:getPlayerName() then return end -- Don't remove player aircraft, that would cause horrendous bugs
@ -73,6 +72,8 @@ do
-- @param event The DCS World event
-------------------------------------
function TUM.mizCleaner.onEvent(event)
removeAIAircraftOnLandEvent(event)
if event.id == world.event.S_EVENT_LAND and event.place then
removeAIAircraftOnLandEvent(event)
end
end
end

View File

@ -168,13 +168,44 @@ do
end
local function onObjectiveEvent(index, event)
if not event.initiator then return end
if index < 1 or index > #objectives then return end -- Out of bounds
if objectives[index].completed then return end -- Objective already completed
if event.id ~= world.event.S_EVENT_DEAD and event.id ~= world.event.S_EVENT_UNIT_LOST then return end
if not event.initiator then return end
local completionEvent = Library.tasks[objectives[index].taskID].completionEvent
if objectives[index].isSceneryTarget then
if completionEvent == DCSEx.enums.taskEvent.DAMAGE then
if event.id ~= world.event.S_EVENT_DEAD and event.id ~= world.event.S_EVENT_HIT and event.id ~= world.event.S_EVENT_UNIT_LOST then return end
elseif completionEvent == DCSEx.enums.taskEvent.DESTROY then
if event.id ~= world.event.S_EVENT_DEAD and event.id ~= world.event.S_EVENT_UNIT_LOST then return end
elseif completionEvent == DCSEx.enums.taskEvent.LAND then
if event.id ~= world.event.S_EVENT_LAND then return end
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end
if event.initiator:getDesc().category ~= Unit.Category.HELICOPTER then return end
if event.initiator:getCoalition() ~= TUM.settings.getPlayerCoalition() then return end
if DCSEx.math.getDistance2D(DCSEx.math.vec3ToVec2(event.initiator:getPoint()), objectives[index].point2) > 500 then return end -- Too far from objective
-- Remove target group if it exists (to simulate it was picked up/captured)
if objectives[index].groupID then
local targetGroup = DCSEx.world.getGroupByID(objectives[index].groupID)
if targetGroup then
targetGroup:destroy()
end
end
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)
updateObjectiveText(index)
return
end
if objectives[index].isAirbaseTarget then
if Object.getCategory(event.initiator) == Object.Category.BASE then
if DCSEx.math.isSamePoint(event.initiator:getPoint(), objectives[index].point3) then
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)
end
end
elseif objectives[index].isSceneryTarget then
if Object.getCategory(event.initiator) == Object.Category.SCENERY then
if DCSEx.math.isSamePoint(event.initiator:getPoint(), objectives[index].point3) then
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)

View File

@ -6,6 +6,8 @@
TUM.objectivesMaker = {}
do
local usedParkingSpots = {}
local function pickRandomTask()
local taskFamily = TUM.settings.getValue(TUM.settings.id.TASKING)
@ -41,6 +43,10 @@ do
return possiblePoints[1]
end
function TUM.objectivesMaker.clear()
usedParkingSpots = {}
end
function TUM.objectivesMaker.create()
local zone = DCSEx.zones.getByName(TUM.settings.getValue(TUM.settings.id.TARGET_LOCATION, true))
@ -51,8 +57,10 @@ do
end
local objectiveDB = Library.tasks[taskID]
local parkingInfo = nil
local spawnPoint2 = nil
local spawnPoint3 = nil
local isAirbaseTarget = false
local isSceneryTarget = false
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
@ -66,6 +74,40 @@ do
spawnPoint3 = DCSEx.table.deepCopy(pickedScenery:getPoint())
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
isSceneryTarget = true
elseif DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.AIRBASE_TARGET) then
local validAirbases = DCSEx.zones.getAirbases(zone, TUM.settings.getEnemyCoalition())
if #validAirbases == 0 then
TUM.log("Failed to find a valid airbase to use as target.", TUM.logger.logLevel.WARNING)
return nil
end
local pickedAirbase = DCSEx.table.getRandom(validAirbases)
spawnPoint3 = DCSEx.table.deepCopy(pickedAirbase:getPoint())
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
isAirbaseTarget = true
elseif DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.PARKED_AIRCRAFT_TARGET) then
local validAirbases = DCSEx.zones.getAirbases(zone, TUM.settings.getEnemyCoalition())
if #validAirbases == 0 then
TUM.log("Failed to find a valid airbase to use as target.", TUM.logger.logLevel.WARNING)
return nil
end
local pickedAirbase = DCSEx.table.getRandom(validAirbases)
local parkings = pickedAirbase:getParking()
local validParkings = {}
for _,p in pairs(parkings) do
local parkingUniqueID = pickedAirbase:getID() * 10000 + p.Term_Index
if p.Term_Type == 104 and not DCSEx.table.contains(usedParkingSpots, parkingUniqueID) then
table.insert(validParkings, p)
end
end
if #validParkings == 0 then
TUM.log("Failed to find a valid airbase parking to spawn a target.", TUM.logger.logLevel.WARNING)
return nil
end
local pickedParking = DCSEx.table.getRandom(validParkings)
table.insert(usedParkingSpots, pickedAirbase:getID() * 10000 + pickedParking.Term_Index) -- Mark parking spot as used so it won't be taken by another objective
parkingInfo = { airbaseID = pickedAirbase:getID(), parkingID = pickedParking.Term_Index }
spawnPoint3 = pickedParking.vTerminalPos
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
elseif objectiveDB.surfaceType == land.SurfaceType.WATER then
spawnPoint2 = pickWaterPoint(zone)
if not spawnPoint2 then
@ -89,10 +131,12 @@ do
local objective = {
completed = false,
completedUnitsID = {},
isAirbaseTarget = isAirbaseTarget,
isSceneryTarget = isSceneryTarget,
markerID = DCSEx.world.getNextMarkerID(),
markerTextID = DCSEx.world.getNextMarkerID(),
name = Library.objectiveNames.get():upper(),
parkingInfo = parkingInfo,
point2 = DCSEx.table.deepCopy(spawnPoint2),
point3 = DCSEx.table.deepCopy(spawnPoint3),
preciseCoordinates = objectiveDB.waypointInaccuracy <= 0,
@ -108,7 +152,7 @@ do
objective.waypoint3 = DCSEx.math.vec2ToVec3(objective.waypoint2, "land")
end
if not DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
if not isAirbaseTarget and not isSceneryTarget then
-- Check group options
local groupOptions = {}
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.MOVING) then
@ -123,7 +167,26 @@ do
end
end
local units = Library.factions.getUnits(TUM.settings.getEnemyFaction(), objectiveDB.targetFamilies, math.random(objectiveDB.targetCount[1], objectiveDB.targetCount[2]))
-- Parked aircraft only
if parkingInfo then
groupOptions.airbaseID = parkingInfo.airbaseID
groupOptions.invisible = true -- Not ideal because wingmen can't be tasked with attacking targets, but only way I've found to prevent friendly CAP from attacking parked aircraft
groupOptions.parkingID = parkingInfo.parkingID
end
-- Target group belongs to the enemy coalition, unless DCSEx.enums.taskFlag.FRIENDLY_TARGET is set
local groupCoalition = TUM.settings.getEnemyCoalition()
local groupFaction = TUM.settings.getEnemyFaction()
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.FRIENDLY_TARGET) then
groupCoalition = TUM.settings.getPlayerCoalition()
groupFaction = TUM.settings.getPlayerFaction()
-- Friendly target groups are immortal and invisible, so AI won't kill them before the player got a chance to interact with them
groupOptions.immortal = true
groupOptions.invisible = true
end
local units = Library.factions.getUnits(groupFaction, objectiveDB.targetFamilies, math.random(objectiveDB.targetCount[1], objectiveDB.targetCount[2]))
local groupInfo = nil
if objectiveDB.targetFamilies[1] == DCSEx.enums.unitFamily.STATIC_STRUCTURE then
@ -132,7 +195,7 @@ do
groupInfo.unitsID = { DCSEx.unitGroupMaker.createStatic(TUM.settings.getEnemyCoalition(), objective.point2, units[1], "") }
end
else
groupInfo = DCSEx.unitGroupMaker.create(TUM.settings.getEnemyCoalition(), DCSEx.dcs.getUnitCategoryFromFamily(objectiveDB.targetFamilies[1]), objective.point2, units, groupOptions)
groupInfo = DCSEx.unitGroupMaker.create(groupCoalition, DCSEx.dcs.getUnitCategoryFromFamily(objectiveDB.targetFamilies[1]), objective.point2, units, groupOptions)
end
if not groupInfo then

View File

@ -55,7 +55,12 @@ do
if not objectDesc or not objectDesc.attributes then return 10 end -- No description, assume a default value of 10 points
local groundMultiplier = 1
if not killedObject:inAir() then groundMultiplier = 0.5 end -- Aircraft killed on the ground are worth less points
if not killedObject:inAir() then
-- Aircraft killed on the ground are worth less points, except AWACS, bombers and transports
if not objectDesc.attributes["AWACS"] and not objectDesc.attributes["Transports"] and not objectDesc.attributes["Strategic bombers"] then
groundMultiplier = 0.5
end
end
-- Misc
if objectDesc.attributes["Missiles"] then return 10 end
@ -68,8 +73,8 @@ do
if objectDesc.attributes["Planes"] then return math.floor(25 * groundMultiplier) end
-- Rotary wing
if objectDesc.attributes["Attack helicopters"] then return math.floor(30 * groundMultiplier) end
if objectDesc.attributes["Helicopters"] then return math.floor(25 * groundMultiplier) end
if objectDesc.attributes["Attack helicopters"] then return math.floor(25 * groundMultiplier) end
if objectDesc.attributes["Helicopters"] then return math.floor(15 * groundMultiplier) end
-- Default air
if objectDesc.attributes["Air"] then return math.floor(20 * groundMultiplier) end
@ -295,11 +300,18 @@ do
if not DCSEx.io.canReadAndWrite() then return 1.0 end -- IO disabled, career and scoring disabled
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return 1.0 end -- No scoring in multiplayer
-- Base XP multiplier is 1.0 (100%)
local scoreMultiplier = 1.0
-- Add XP multipliers for game settings
for _,v in pairs(TUM.settings.id) do
scoreMultiplier = scoreMultiplier + (TUM.playerScore.getScoreMultiplier(v) or 0.0)
end
-- Add XP multipliers for weather
scoreMultiplier = scoreMultiplier + TUM.weather.getWeatherXPModifier()
scoreMultiplier = scoreMultiplier + TUM.weather.getWindXPModifier()
return math.max(0.0, scoreMultiplier)
end
@ -335,7 +347,7 @@ do
return
end
if event.id == world.event.S_EVENT_LAND then
if event.id == world.event.S_EVENT_LAND and event.place then
onLandEvent(event)
return
end

View File

@ -52,7 +52,8 @@ do
[TUM.settings.id.PLAYER_COALITION] = { "Red", "Blue" }, -- Must match values in the coalition.side enum
[TUM.settings.id.TARGET_COUNT] = { "1", "2", "3", "4" },
[TUM.settings.id.TARGET_LOCATION] = { },
[TUM.settings.id.TASKING] = { "Antiship strike", "Ground attack", "Interception", "SEAD", "Strike" }, -- Must match values in the DCSEx.enums.taskFamily enum
-- [TUM.settings.id.TASKING] = { "Antiship strike", "Ground attack", "Helicopter-specific tasks", "Helo hunt", "Interception", "Offensive counter-air", "SEAD", "Strike" }, -- Must match values in the DCSEx.enums.taskFamily enum
[TUM.settings.id.TASKING] = { "Antiship strike", "Ground attack", "Helo hunt", "Interception", "Offensive counter-air", "SEAD", "Strike" }, -- Must match values in the DCSEx.enums.taskFamily enum
[TUM.settings.id.TIME_PERIOD] = { "World War 2", "Korea War", "Vietnam War", "Late Cold War", "Modern" }, -- Must match values in the DCSEx.enums.timePeriod enum
[TUM.settings.id.WINGMEN] = { "None", "1", "2", "3" }
}
@ -193,6 +194,10 @@ do
end
end
-- Weather
summary = summary.."\n\nWEATHER: "..TUM.weather.getWeatherName().." (+"..tostring(math.ceil(TUM.weather.getWeatherXPModifier() * 100)).."% xp)"
summary = summary.."\nWIND: "..DCSEx.string.firstToUpper(TUM.weather.getWindName()).." (+"..tostring(math.ceil(TUM.weather.getWindXPModifier() * 100)).."% xp)"
if showScoreMultiplier then
summary = summary.."\n\nTotal XP modifier: "..tostring(math.ceil(TUM.playerScore.getTotalScoreMultiplier() * 100)).."%"
end

View File

@ -43,6 +43,15 @@ do
end
end
end
detectedGroups = coalition.getGroups(TUM.settings.getEnemyCoalition(), Group.Category.HELICOPTER)
for _,g in pairs(detectedGroups) do
local units = g:getUnits()
for _,u in pairs(units) do
if u:inAir() then
table.insert(detectedAircraft, u)
end
end
end
-- No aircraft on picture
if #detectedAircraft == 0 then

View File

@ -60,7 +60,7 @@ do
return
end
if obj.isSceneryTarget then
if obj.isAirbaseTarget or obj.isSceneryTarget then
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacSmokeOK", { jtacName[index], smokeColorName }, jtacName[index], true, spawnSmoke, { point3 = obj.point3, smokeColor = smokeColor })
else
for _,id in ipairs(obj.unitsID) do

View File

@ -0,0 +1,322 @@
-- ====================================================================================
-- TUM.WEATHER - HANDLES THE MISSION'S WEATHER SETTINGS
-- ====================================================================================
-- ====================================================================================
TUM.weather = {}
do
local cloudPresets = {
-- "Light Scattered 1",
Preset1 = {
readableName = "Few scattered clouds (METAR: FEW/SCT 7/8)",
readableNameShort = "Light scattered",
xpBonus = 0,
},
-- "Light Scattered 2",
Preset2 = {
readableName = "Two layers few and scattered (METAR: FEW/SCT 8/10 SCT 23/24)",
readableNameShort = "Light scattered",
xpBonus = 0,
},
-- "High Scattered 1",
Preset3 = {
readableName = "Two layers scattered (METAR: SCT 8/9 FEW 21)",
readableNameShort = "High Scattered",
xpBonus = 0,
},
-- "High Scattered 2",
Preset4 = {
readableName = "Two layers scattered (METAR: SCT 8/10 FEW/SCT 24/26)",
readableNameShort = "High Scattered",
xpBonus = 0,
},
-- "Scattered 1",
Preset5 = {
readableName = "Three layers high altitude scattered (METAR: SCT 14/17 FEW 27/29 BKN 40)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Scattered 2",
Preset6 = {
readableName = "One layer scattered/broken (METAR: SCT/BKN 8/10 FEW 40)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Scattered 3",
Preset7 = {
readableName = "Two layers scattered/broken (METAR: BKN 7.5/12 SCT/BKN 21/23 SCT 40)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "High Scattered 3",
Preset8 = {
readableName = "Two layers scattered/broken high altitude (METAR: SCT/BKN 18/20 FEW 36/38 FEW 40)",
readableNameShort = "High Scattered",
xpBonus = 0,
},
-- "Scattered 4",
Preset9 = {
readableName = "Two layers broken/scattered (METAR: BKN 7.5/10 SCT 20/22 FEW41)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Scattered 5",
Preset10 = {
readableName = "Two layers scattered large thick clouds (METAR: SCT/BKN 18/20 FEW36/38 FEW 40)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Scattered 6",
Preset11 = {
readableName = "Two layers scattered large clouds high ceiling (METAR: BKN 18/20 BKN 32/33 FEW 41)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Scattered 7",
Preset12 = {
readableName = "Two layers scattered large clouds high ceiling (METAR: BKN 12/14 SCT 22/23 FEW 41)",
readableNameShort = "Scattered",
xpBonus = 0,
},
-- "Broken 1",
Preset13 = {
readableName = "Two layers broken clouds (METAR: BKN 12/14 BKN 26/28 FEW 41)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 2",
Preset14 = {
readableName = "Broken thick low layer with few high layers\nMETAR: BKN LYR 7/16 FEW 41)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 3",
Preset15 = {
readableName = "Two layers broken large clouds (METAR: SCT/BKN 14/18 BKN 24/27 FEW 40)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 4",
Preset16 = {
readableName = "Two layers broken large clouds (METAR: BKN 14/18 BKN 28/30 FEW 40)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 5",
Preset17 = {
readableName = "Three layers broken/overcast (METAR: BKN/OVC LYR 7/13 20/22 32/34)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 6",
Preset18 = {
readableName = "Three layers broken/overcast (METAR: BKN/OVC LYR 13/15 25/29 38/41)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 7",
Preset19 = {
readableName = "Three layers overcast at low level (METAR: OVC 9/16 BKN/OVC LYR 23/24 31/33)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Broken 8",
Preset20 = {
readableName = "Three layers overcast low level (METAR: BKN/OVC 13/18 BKN 28/30 SCT FEW 38)",
readableNameShort = "Broken",
xpBonus = 0.05,
},
-- "Overcast 1",
Preset21 = {
readableName = "Overcast low level (METAR: BKN/OVC LYR 7/8 17/19)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 2",
Preset22 = {
readableName = "Overcast low level (METAR: BKN LYR 7/10 17/20)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 3",
Preset23 = {
readableName = "Three layers broken low level scattered high (METAR: BKN LYR 11/14 18/25 SCT 32/35)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 4",
Preset24 = {
readableName = "Three layers overcast (METAR: BKN/OVC 3/7 17/22 BKN 34)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 5",
Preset25 = {
readableName = "Three layers overcast (METAR: OVC LYR 12/14 22/25 40/42)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 6",
Preset26 = {
readableName = "Three layers overcast (METAR: OVC 9/15 BKN 23/25 SCT 32)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast 7",
Preset27 = {
readableName = "Three layer overcast (METAR: OVC 8/15 SCT/BKN 25/26 34/36)",
readableNameShort = "Overcast",
xpBonus = 0.10,
},
-- "Overcast And Rain 1",
RainyPreset1 = {
readableName = "Overcast with rain (METAR: VIS 3-5KM RA OVC 3/15 28/30 FEW 40)",
readableNameShort = "Overcast and rain",
xpBonus = 0.25,
},
-- "Overcast And Rain 2",
RainyPreset2 = {
readableName = "Overcast with rain (METAR: VIS 1-5KM RA BKN/OVC 3/11 SCT 18/29 FEW 40)",
readableNameShort = "Overcast and rain",
xpBonus = 0.25,
},
-- "Overcast And Rain 3",
RainyPreset3 = {
readableName = "Overcast with rain (METAR: VIS 3-5KM RA OVC LYR 6/18 19/21 SCT 34)",
readableNameShort = "Overcast and rain",
xpBonus = 0.25,
},
-- "Light Rain 1",
RainyPreset4 = {
readableName = "Two layers scattered large thick clouds (METAR: SCT/BKN 18/20 FEW36/38 FEW 40)",
readableNameShort = "Light rain",
xpBonus = 0.15,
},
-- "Light Rain 2",
RainyPreset5 = {
readableName = "Three layers broken/overcast (METAR: BKN/OVC LYR 7/13 20/22 32/34)",
readableNameShort = "Light rain",
xpBonus = 0.15,
},
-- "Light Rain 3",
RainyPreset6 = {
readableName = "Three layers overcast at low level (METAR: OVC 9/16 BKN/OVC LYR 23/24 31/33)",
readableNameShort = "Light rain",
xpBonus = 0.15,
},
-- "Light Rain 4",
NEWRAINPRESET4 = {
readableName = "Two layers overcast at low level (METAR: OVC 9/16 BKN/OVC LYR 23/24 31/33)",
readableNameShort = "Light rain",
xpBonus = 0.15,
},
}
local function getWindBeaufortScale(speedInMS)
local speedInKMH = DCSEx.converter.mpsToKmph(speedInMS or Library.environment.getWindAverage())
if speedInKMH < 1 then return 0
elseif speedInKMH <= 5 then return 1
elseif speedInKMH <= 11 then return 2
elseif speedInKMH <= 19 then return 3
elseif speedInKMH <= 28 then return 4
elseif speedInKMH <= 38 then return 5
elseif speedInKMH <= 49 then return 6
elseif speedInKMH <= 61 then return 7
elseif speedInKMH <= 74 then return 8
elseif speedInKMH <= 88 then return 9
elseif speedInKMH <= 102 then return 10
elseif speedInKMH <= 117 then return 11
else return 12
end
end
function TUM.weather.getWeatherName(presetID, longForm)
presetID = presetID or env.mission.weather.clouds.preset
longForm = longForm or false
if cloudPresets[presetID] == nil then return "Unknown" end
if longForm then return cloudPresets[presetID].readableName end
return cloudPresets[presetID].readableNameShort
end
function TUM.weather.getWeatherXPModifier(presetID)
presetID = presetID or env.mission.weather.clouds.preset
if cloudPresets[presetID] == nil then return 0 end
return cloudPresets[presetID].xpBonus
end
function TUM.weather.getWindName(speedInMS)
local windBeaufort = getWindBeaufortScale(speedInMS)
if windBeaufort == 0 then return "calm"
elseif windBeaufort == 1 then return "light air"
elseif windBeaufort == 2 then return "light breeze"
elseif windBeaufort == 3 then return "gentle breeze"
elseif windBeaufort == 4 then return "moderate breeze"
elseif windBeaufort == 5 then return "fresh breeze"
elseif windBeaufort == 6 then return "strong breeze"
elseif windBeaufort == 7 then return "moderate gale"
elseif windBeaufort == 8 then return "fresh gale"
elseif windBeaufort == 9 then return "strong gale"
elseif windBeaufort == 10 then return "storm"
elseif windBeaufort == 11 then return "violent storm"
elseif windBeaufort == 12 then return "hurricane"
end
end
function TUM.weather.getWindXPModifier(speedInMS)
local windBeaufort = getWindBeaufortScale(speedInMS)
if windBeaufort == 0 then return 0.00
elseif windBeaufort == 1 then return 0.02
elseif windBeaufort == 2 then return 0.04
elseif windBeaufort == 3 then return 0.08
elseif windBeaufort == 4 then return 0.10
elseif windBeaufort == 5 then return 0.12
elseif windBeaufort == 6 then return 0.15
elseif windBeaufort == 7 then return 0.18
elseif windBeaufort == 8 then return 0.21
elseif windBeaufort == 9 then return 0.24
elseif windBeaufort == 10 then return 0.27
elseif windBeaufort == 11 then return 0.30
elseif windBeaufort == 12 then return 0.33
end
end
end

View File

@ -23,11 +23,11 @@ do
elseif taskingID == DCSEx.enums.taskFamily.GROUND_ATTACK then
return "attack"
-- elseif taskingID == DCSEx.enums.taskFamily.HELICOPTER then
-- elseif taskingID == DCSEx.enums.taskFamily.HELO_HUN then
elseif taskingID == DCSEx.enums.taskFamily.HELO_HUNT then
elseif taskingID == DCSEx.enums.taskFamily.INTERCEPTION then
return "cap"
-- elseif taskingID == DCSEx.enums.taskFamily.OCA then
elseif taskingID == DCSEx.enums.taskFamily.SEAD then
-- elseif taskingID == DCSEx.enums.taskFamily.OCA then
return "sead"
-- elseif taskingID == DCSEx.enums.taskFamily.STRIKE then
-- return "strike"
@ -177,7 +177,7 @@ do
if not event.initiator:getPlayerName() then return end
if TUM.mission.getStatus() == TUM.mission.status.NONE then return end -- Mission not in progress, no wingman needed
TUM.wingmen.create()
elseif event.id == world.event.S_EVENT_LAND then -- Remove wingmen on player landing
elseif event.id == world.event.S_EVENT_LAND and event.place then -- Remove wingmen on player landing
if not event.initiator:getPlayerName() then return end
TUM.wingmen.removeAll()
elseif event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then -- Remove wingmen when player takes control of a new unit

View File

@ -5,7 +5,7 @@
User's manual
</p>
<p class="heavy">
The Universal Mission v0.3.250914<br />
The Universal Mission v0.3.251019<br />
Created and maintained by Ambroise Garel (<a href='mailto:akaagarmail@gmail.com'>akaagarmail@gmail.com</a>)
</p>
<p class="heavy">
@ -73,7 +73,6 @@ I hope you'll like it.
- Download the latest release from this GitHub page.
- Copy the provided autoexec.cfg file to your **[Saved Games]\DCS\Config directory**
- Please note: as of DCS 2.9.18.12899, it seems the autoexec.cfg file [is no longer needed](https://www.digitalcombatsimulator.com/en/news/changelog/release/2.9.18.12899/) but I advise you to copy it anyway, ED might change its mind again.
- Copy the .miz files for your theater(s) of choice to your **[Saved Games]\DCS\Missions directory**
- _**(Optional but strongly recommended)**_ Unsanitize the Lua IO module. You don't have to do this, but the persistent career system won't work if you don't. To do it, open the file **[DCS World installation directory]\Scripts\MissionScripting.lua** with a text editor and comment or remove the line "sanitizeModule('io')". Make sure you restart DCS World once you've modified the file.
- Please note: should you want to backup, delete or transfer it, career progress is saved in **[DCS World installation directory]\TheUniversalMission.sav**
@ -113,7 +112,10 @@ The exact content of the menu will depend on the current phase of the mission.
- **Mission type**: What will your mission be?
- **Antiship strike**: Sink enemy warships and cargo ships.
- **Ground attack**: Interdiction missions against armor, artillery and convoys.
- **Helicopter-specific tasks**: Tasks specifically designed for helicopters (lift/pick up friendly units, suppress infantry...)
- **Helicopter hunt**: Shoot down enemy transport and attack helicopters.
- **Interception**: Shoot down strategic airplanes (bombers, transports...) and enemy attack planes on interdiction missions.
- **Offensive counter-air**: Bomb enemy airbases and destroy parked aircraft. **(Requires a target area with at least one enemy land airbase, or mission type will automatically be changed to ground attack)**
- **SEAD**: Destroy enemy SAM sites.
- **Strike**: Destroy enemy structures and civilian buildings occupied by enemy forces.
- **Target location**: Where on the map will the targets be spawned? Approximate distance to possible regions is displayed in the menu.