mirror of
https://github.com/akaAgar/the-universal-mission-for-dcs-world.git
synced 2025-11-25 19:31:01 +00:00
Compare commits
111 Commits
v0.2.25072
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c49bdd1f | ||
|
|
d7941c3485 | ||
|
|
3fd0d39be0 | ||
|
|
a51ff5cff0 | ||
|
|
a7bf94de5f | ||
|
|
a66c3000c2 | ||
|
|
2d96f23d2f | ||
|
|
392f06d4e2 | ||
|
|
32248a9b72 | ||
|
|
5b2b17a8b0 | ||
|
|
806421062b | ||
|
|
591ec6e10a | ||
|
|
92a00160ef | ||
|
|
085172f033 | ||
|
|
526651c6da | ||
|
|
b343d700a0 | ||
|
|
da20586d51 | ||
|
|
69ea6b6d19 | ||
|
|
5f5b940ef7 | ||
|
|
014495246d | ||
|
|
3484e2544c | ||
|
|
185685b706 | ||
|
|
6b52970520 | ||
|
|
1c6b06a1a6 | ||
|
|
525484385a | ||
|
|
6ce6a8d2a1 | ||
|
|
7bfe0813d8 | ||
|
|
12e20a32ce | ||
|
|
13d341c4a8 | ||
|
|
0bc9780dd4 | ||
|
|
aa544e6c0c | ||
|
|
c80d036feb | ||
|
|
c557c3d74d | ||
|
|
1892d97d15 | ||
|
|
db5ee882c4 | ||
|
|
83ddfe7598 | ||
|
|
d0355af8e3 | ||
|
|
211cb15015 | ||
|
|
1905b4061a | ||
|
|
b93bcb4734 | ||
|
|
61e017fbe8 | ||
|
|
655b5bc6aa | ||
|
|
d13d94f1bb | ||
|
|
0be508c42c | ||
|
|
b9ce5ef340 | ||
|
|
3453bccf85 | ||
|
|
b392c55828 | ||
|
|
ff15793f06 | ||
|
|
e7f9ba4f92 | ||
|
|
98baf8a3c0 | ||
|
|
4a94770322 | ||
|
|
885b14315f | ||
|
|
4f1ad38eeb | ||
|
|
404095967d | ||
|
|
4ebbf398d2 | ||
|
|
babc9a183e | ||
|
|
0b792e4b25 | ||
|
|
2eee4320d3 | ||
|
|
2d0503e44e | ||
|
|
e0a2612572 | ||
|
|
e357040865 | ||
|
|
67d71334f9 | ||
|
|
ffcbbd0402 | ||
|
|
638985b86c | ||
|
|
b8b5611e32 | ||
|
|
3af259f048 | ||
|
|
104fee86e9 | ||
|
|
d5577ac551 | ||
|
|
5f220884bd | ||
|
|
e522d110bb | ||
|
|
64bde651c3 | ||
|
|
a9edd4a819 | ||
|
|
b140238aa0 | ||
|
|
814fbccb00 | ||
|
|
81b0be5645 | ||
|
|
fdb5090e40 | ||
|
|
99133326b9 | ||
|
|
994c4d9193 | ||
|
|
f1a87bcfa8 | ||
|
|
9d64113241 | ||
|
|
adee5411e1 | ||
|
|
4232ee723c | ||
|
|
c3ecc403e2 | ||
|
|
dca67aa13c | ||
|
|
6658dbecf9 | ||
|
|
52ad4156a4 | ||
|
|
b4701a98e2 | ||
|
|
24a73b73c7 | ||
|
|
b94cdaa0ef | ||
|
|
6b765e7c80 | ||
|
|
de3e3df840 | ||
|
|
c5743c993a | ||
|
|
54ff069711 | ||
|
|
e840bc3b0d | ||
|
|
25ba1ccd2e | ||
|
|
8e7dc3ba7a | ||
|
|
548d81a5a6 | ||
|
|
0c20433ce4 | ||
|
|
051d548c8e | ||
|
|
aab5ea1688 | ||
|
|
a25e8e6bc1 | ||
|
|
c16041f58b | ||
|
|
e66fe93ea0 | ||
|
|
ebffc5313a | ||
|
|
39039430c1 | ||
|
|
8d05b98a95 | ||
|
|
3e1bd1e52e | ||
|
|
33f8986317 | ||
|
|
a55012e383 | ||
|
|
6964bf2543 | ||
|
|
b45caaa4c4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
_[Dd]ebug[Oo]utput/
|
_[Dd]ebug[Oo]utput/
|
||||||
[Ii]nclude/[Ss]cript.lua
|
[Ii]nclude/[Ss]cript.lua
|
||||||
*.miz
|
*.miz
|
||||||
|
*.pdf
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"files.exclude": { "*.miz": true },
|
"files.exclude": { "*.miz": true },
|
||||||
"Lua.workspace.library": ["Libraries/dcs-world-api.lua"],
|
"Lua.workspace.library": ["Libraries/dcs-world-api.lua"],
|
||||||
"Lua.diagnostics.disable": ["deprecated"]
|
"Lua.diagnostics.disable": ["deprecated"],
|
||||||
|
"markdown-pdf.convertOnSave": true,
|
||||||
|
"markdown-pdf.convertOnSaveExclude": ["README.md"],
|
||||||
|
"markdown-pdf.headerTemplate": "<span class='title' style='display: none;'></span>",
|
||||||
|
"markdown-pdf.styles": ["docs/style.css"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,15 +11,9 @@
|
|||||||
if not net then net = {} end
|
if not net then net = {} end
|
||||||
|
|
||||||
net.allow_unsafe_api = { -- this defines the secure zones where net.dostring_in() can be called from
|
net.allow_unsafe_api = { -- this defines the secure zones where net.dostring_in() can be called from
|
||||||
"userhooks",
|
|
||||||
"scripting",
|
"scripting",
|
||||||
"gui",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
net.allow_dostring_in = { -- and this defines the zones that should be addressed from net.dostring_in()
|
net.allow_dostring_in = { -- and this defines the zones that should be addressed from net.dostring_in()
|
||||||
"mission",
|
"mission",
|
||||||
"scripting",
|
|
||||||
"gui",
|
|
||||||
"export",
|
|
||||||
"config",
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
Database/Factions/Europe.lua
Normal file
6
Database/Factions/Europe.lua
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- TODO
|
||||||
|
|
||||||
|
-- [DCSEx.enums.unitFamily.GROUND_APC] = { "CHAP_FV107" },
|
||||||
|
|
||||||
|
|
||||||
|
-- CHAP_IRISTSLM_CP, CHAP_IRISTSLM_LN, CHAP_IRISTSLM_STR
|
||||||
51
Database/Factions/NATO.lua
Normal file
51
Database/Factions/NATO.lua
Normal 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
|
||||||
@ -21,17 +21,17 @@ do
|
|||||||
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Ural-375 ZU-23", "ZSU_57_2", "ZSU-23-4 Shilka" },
|
[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_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_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_LONG] = { "*SA-10" },
|
||||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = { "*SA-3", "*SA-6", "*SA-11" },
|
[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" },
|
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "2S6 Tunguska", "Osa 9A33 ln", "Tor 9A331", "CHAP_PantsirS1", "CHAP_TorM2" },
|
||||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR] = { "Strela-1 9P31", "Strela-10M3" },
|
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR] = { "Strela-1 9P31", "Strela-10M3" },
|
||||||
|
|
||||||
[DCSEx.enums.unitFamily.GROUND_APC] = { "BMD-1", "BMP-1", "BMP-2", "BMP-3", "Boman", "BRDM-2", "BTR_D", "BTR-80", "BTR-82A", "Grad_FDDM", "MTLB" },
|
[DCSEx.enums.unitFamily.GROUND_APC] = { "BMD-1", "BMP-1", "BMP-2", "BMP-3", "Boman", "BRDM-2", "BTR_D", "BTR-80", "BTR-82A", "Grad_FDDM", "MTLB", "CHAP_BMPT" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_ARTILLERY] = { "Grad-URAL", "SAU 2-C9", "SAU Akatsia", "SAU Gvozdika", "SAU Msta", "Smerch", "SpGH_Dana", "Uragan_BM-27" },
|
[DCSEx.enums.unitFamily.GROUND_ARTILLERY] = { "Grad-URAL", "SAU 2-C9", "SAU Akatsia", "SAU Gvozdika", "SAU Msta", "Smerch", "SpGH_Dana", "Uragan_BM-27", "CHAP_TOS1A" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_INFANTRY] = { "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Paratrooper AKS-74", "Paratrooper RPG-16", "Soldier AK" },
|
[DCSEx.enums.unitFamily.GROUND_INFANTRY] = { "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Paratrooper AKS-74", "Paratrooper RPG-16", "Soldier AK" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_MBT] = { "T-55", "T-72B", "T-80UD", "T-90" },
|
[DCSEx.enums.unitFamily.GROUND_MBT] = { "T-55", "T-72B", "T-80UD", "T-90" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
|
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B", "CHAP_9K720_HE" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Ural-375", "Ural-4320 APA-5D", "Ural-4320T" },
|
[DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Ural-375", "Ural-4320 APA-5D", "Ural-4320T" },
|
||||||
|
|
||||||
[DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "Ka-50", "Mi-24V", "Mi-28N" },
|
[DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "Ka-50", "Mi-24V", "Mi-28N" },
|
||||||
@ -45,14 +45,12 @@ do
|
|||||||
[DCSEx.enums.unitFamily.PLANE_TRANSPORT] = { "An-26B", "An-30M", "IL-76MD" },
|
[DCSEx.enums.unitFamily.PLANE_TRANSPORT] = { "An-26B", "An-30M", "IL-76MD" },
|
||||||
[DCSEx.enums.unitFamily.PLANE_UAV] = { "WingLoong-I" },
|
[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_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_CARRIER] = { "CV_1143_5", "KUZNECOW" },
|
||||||
[DCSEx.enums.unitFamily.SHIP_CRUISER] = { "MOSCOW", "PIOTR" },
|
[DCSEx.enums.unitFamily.SHIP_CRUISER] = { "MOSCOW", "PIOTR" },
|
||||||
[DCSEx.enums.unitFamily.SHIP_FRIGATE] = { "NEUSTRASH", "REZKY" },
|
[DCSEx.enums.unitFamily.SHIP_FRIGATE] = { "NEUSTRASH", "REZKY" },
|
||||||
[DCSEx.enums.unitFamily.SHIP_LIGHT] = { "speedboat" },
|
[DCSEx.enums.unitFamily.SHIP_LIGHT] = { "speedboat" },
|
||||||
[DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "ALBATROS", "BDK-775", "MOLNIYA" },
|
[DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "ALBATROS", "BDK-775", "MOLNIYA", "CHAP_Project22160_TorM2KM" },
|
||||||
[DCSEx.enums.unitFamily.SHIP_SUBMARINE] = { "IMPROVED_KILO", "KILO", "SOM" },
|
[DCSEx.enums.unitFamily.SHIP_SUBMARINE] = { "IMPROVED_KILO", "KILO", "SOM" },
|
||||||
|
|
||||||
-- [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", "Bunker", "Chemical tank A", "Comms tower M", "FARP Fuel Depot", "outpost", "Sandbox", "Workshop A" },
|
||||||
|
|||||||
@ -10,7 +10,45 @@ do
|
|||||||
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.WORLD_WAR_2] = {}
|
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.KOREA_WAR] = {}
|
||||||
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.VIETNAM_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] = {
|
Library.factions.tables["USA"].units[DCSEx.enums.timePeriod.MODERN] = {
|
||||||
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Gepard", "Vulcan" },
|
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = { "Gepard", "Vulcan" },
|
||||||
@ -21,12 +59,12 @@ do
|
|||||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "rapier_fsa", "Roland ADS" },
|
[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.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" },
|
[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" },
|
[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_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_MBT] = { "Challenger2", "Leclerc", "Leopard-2", "Leopard1A3", "M-1 Abrams", "Merkava_Mk4" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
|
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
|
||||||
[DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Land_Rover_101_FC", "Land_Rover_109_S3", "M 818" },
|
[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_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.HELICOPTER_TRANSPORT] = { "CH-47D", "CH-53E", "SH-60B", "UH-60A" },
|
||||||
|
|||||||
22
Database/Tasks/HelicopterDestroyInfantry.lua
Normal file
22
Database/Tasks/HelicopterDestroyInfantry.lua
Normal 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)
|
||||||
|
-- }
|
||||||
22
Database/Tasks/HelicopterPickUpInfantry.lua
Normal file
22
Database/Tasks/HelicopterPickUpInfantry.lua
Normal 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)
|
||||||
|
-- }
|
||||||
25
Database/Tasks/HeloHuntAttack.lua
Normal file
25
Database/Tasks/HeloHuntAttack.lua
Normal 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)
|
||||||
|
}
|
||||||
22
Database/Tasks/HeloHuntTransport.lua
Normal file
22
Database/Tasks/HeloHuntTransport.lua
Normal 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)
|
||||||
|
}
|
||||||
26
Database/Tasks/OCAAirbase.lua
Normal file
26
Database/Tasks/OCAAirbase.lua
Normal 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
|
||||||
|
-- }
|
||||||
25
Database/Tasks/OCAFighterStrike.lua
Normal file
25
Database/Tasks/OCAFighterStrike.lua
Normal 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
|
||||||
|
-- }
|
||||||
22
Database/Tasks/OCAStrategicAircraftStrike.lua
Normal file
22
Database/Tasks/OCAStrategicAircraftStrike.lua
Normal 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
|
||||||
|
}
|
||||||
25
Make.bat
25
Make.bat
@ -1,26 +1,28 @@
|
|||||||
@echo off
|
@echo off
|
||||||
cls
|
cls
|
||||||
|
|
||||||
|
WHERE php >nul 2>nul
|
||||||
|
IF %ERRORLEVEL% NEQ 0 goto ERROR-NO-PHP
|
||||||
|
|
||||||
@REM -------------------------------------------
|
@REM -------------------------------------------
|
||||||
@REM CREATE SCENARIOS
|
@REM CREATE SCENARIOS
|
||||||
@REM -------------------------------------------
|
@REM -------------------------------------------
|
||||||
set buildConfig=p
|
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 [P]ersianGulf debug theater only (default)
|
||||||
echo - Build all [D]ebug theaters
|
echo - Build all [D]ebug theaters
|
||||||
echo - Build all [R]elease theaters
|
echo - Build all [R]elease theaters
|
||||||
|
echo - [C]ancel
|
||||||
echo.
|
echo.
|
||||||
set /p buildConfig=Your choice:
|
set /p buildConfig=Your choice:
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
IF "%buildConfig%" == "d" goto BUILD-DEBUG-ALL
|
|
||||||
IF "%buildConfig%" == "D" goto BUILD-DEBUG-ALL
|
|
||||||
IF "%buildConfig%" == "p" goto BUILD-DEBUG-PERSIAN
|
IF "%buildConfig%" == "p" goto BUILD-DEBUG-PERSIAN
|
||||||
IF "%buildConfig%" == "P" goto BUILD-DEBUG-PERSIAN
|
IF "%buildConfig%" == "P" goto BUILD-DEBUG-PERSIAN
|
||||||
|
IF "%buildConfig%" == "d" goto BUILD-DEBUG-ALL
|
||||||
|
IF "%buildConfig%" == "D" goto BUILD-DEBUG-ALL
|
||||||
IF "%buildConfig%" == "r" goto BUILD-RELEASE
|
IF "%buildConfig%" == "r" goto BUILD-RELEASE
|
||||||
IF "%buildConfig%" == "R" goto BUILD-RELEASE
|
IF "%buildConfig%" == "R" goto BUILD-RELEASE
|
||||||
IF "%buildConfig%" == "s" goto START-DEBUG
|
|
||||||
IF "%buildConfig%" == "S" goto START-DEBUG
|
|
||||||
goto CANCEL-END
|
goto CANCEL-END
|
||||||
|
|
||||||
:BUILD-DEBUG-ALL
|
:BUILD-DEBUG-ALL
|
||||||
@ -38,10 +40,16 @@ if exist *.miz del *.miz
|
|||||||
c:\php\php Make.php release
|
c:\php\php Make.php release
|
||||||
goto COPY-TO-DCS
|
goto COPY-TO-DCS
|
||||||
|
|
||||||
:COPY-TO-DCS
|
:BUILD-MANUAL
|
||||||
|
WHERE pandoc >nul 2>nul
|
||||||
|
IF %ERRORLEVEL% NEQ 0 goto ERROR-NO-PANDOC
|
||||||
|
pandoc README.md -o README.pdf
|
||||||
|
goto END
|
||||||
|
|
||||||
@REM -------------------------------------------
|
@REM -------------------------------------------
|
||||||
@REM COPY OUTPUT MIZ FILES TO DCS'S MISSIONS DIRECTORY
|
@REM COPY OUTPUT MIZ FILES TO DCS'S MISSIONS DIRECTORY
|
||||||
@REM -------------------------------------------
|
@REM -------------------------------------------
|
||||||
|
:COPY-TO-DCS
|
||||||
if not exist "%userprofile%\Saved Games\DCS\Missions\" goto END
|
if not exist "%userprofile%\Saved Games\DCS\Missions\" goto END
|
||||||
if not exist *.miz goto END
|
if not exist *.miz goto END
|
||||||
echo Copying output MIZ files to %userprofile%\Saved Games\DCS\Missions...
|
echo Copying output MIZ files to %userprofile%\Saved Games\DCS\Missions...
|
||||||
@ -54,5 +62,10 @@ echo.
|
|||||||
echo Build cancelled.
|
echo Build cancelled.
|
||||||
goto END
|
goto END
|
||||||
|
|
||||||
|
:ERROR-NO-PHP
|
||||||
|
echo.
|
||||||
|
echo CRITICAL ERROR: PHP not found. Please install PHP and add it to the PATH.
|
||||||
|
goto END
|
||||||
|
|
||||||
:END
|
:END
|
||||||
echo.
|
echo.
|
||||||
|
|||||||
BIN
Media/Radio-atcRequireNearestAirbase1.ogg
Normal file
BIN
Media/Radio-atcRequireNearestAirbase1.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcRequireNearestAirbase2.ogg
Normal file
BIN
Media/Radio-atcRequireNearestAirbase2.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcRequireNearestAirbase3.ogg
Normal file
BIN
Media/Radio-atcRequireNearestAirbase3.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcRequireNearestAirbaseNone.ogg
Normal file
BIN
Media/Radio-atcRequireNearestAirbaseNone.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcWeatherUpdate1.ogg
Normal file
BIN
Media/Radio-atcWeatherUpdate1.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcWeatherUpdate2.ogg
Normal file
BIN
Media/Radio-atcWeatherUpdate2.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcWeatherUpdate3.ogg
Normal file
BIN
Media/Radio-atcWeatherUpdate3.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-atcWeatherUpdate4.ogg
Normal file
BIN
Media/Radio-atcWeatherUpdate4.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCRequireNearestAirbase1.ogg
Normal file
BIN
Media/Radio-playerATCRequireNearestAirbase1.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCRequireNearestAirbase2.ogg
Normal file
BIN
Media/Radio-playerATCRequireNearestAirbase2.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCRequireNearestAirbase3.ogg
Normal file
BIN
Media/Radio-playerATCRequireNearestAirbase3.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCRequireNearestAirbase4.ogg
Normal file
BIN
Media/Radio-playerATCRequireNearestAirbase4.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCWeatherUpdate1.ogg
Normal file
BIN
Media/Radio-playerATCWeatherUpdate1.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCWeatherUpdate2.ogg
Normal file
BIN
Media/Radio-playerATCWeatherUpdate2.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCWeatherUpdate3.ogg
Normal file
BIN
Media/Radio-playerATCWeatherUpdate3.ogg
Normal file
Binary file not shown.
BIN
Media/Radio-playerATCWeatherUpdate4.ogg
Normal file
BIN
Media/Radio-playerATCWeatherUpdate4.ogg
Normal file
Binary file not shown.
@ -95,7 +95,7 @@ mission =
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
["pictureFileNameN"] = {},
|
["pictureFileNameN"] = {},
|
||||||
["descriptionNeutralsTask"] = "DictKey_descriptionNeutralsTask_4",
|
["descriptionNeutralsTask"] = "",
|
||||||
["pictureFileNameServer"] = {},
|
["pictureFileNameServer"] = {},
|
||||||
["weather"] =
|
["weather"] =
|
||||||
{
|
{
|
||||||
|
|||||||
@ -86,7 +86,7 @@
|
|||||||
["alt"] = 13,
|
["alt"] = 13,
|
||||||
["alt_type"] = "BARO",
|
["alt_type"] = "BARO",
|
||||||
["livery_id"] = "default",
|
["livery_id"] = "default",
|
||||||
["skill"] = "Player",
|
["skill"] = "Client",
|
||||||
["speed"] = 138.88888888889,
|
["speed"] = 138.88888888889,
|
||||||
["AddPropAircraft"] =
|
["AddPropAircraft"] =
|
||||||
{
|
{
|
||||||
|
|||||||
@ -65,7 +65,7 @@
|
|||||||
["alt"] = 22,
|
["alt"] = 22,
|
||||||
["alt_type"] = "BARO",
|
["alt_type"] = "BARO",
|
||||||
["livery_id"] = "default",
|
["livery_id"] = "default",
|
||||||
["skill"] = "Player",
|
["skill"] = "Client",
|
||||||
["speed"] = 138.88888888889,
|
["speed"] = 138.88888888889,
|
||||||
["type"] = "Su-25T",
|
["type"] = "Su-25T",
|
||||||
["unitId"] = 1,
|
["unitId"] = 1,
|
||||||
|
|||||||
184
README.md
184
README.md
@ -1,21 +1,17 @@
|
|||||||
# The Universal Mission for DCS World
|
# The Universal Mission for DCS World
|
||||||
|
|
||||||
**Current version: open beta 0.2.250729** (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.**
|
**This is a BETA version, there may be bugs and there WILL be unbalanced stuff.**
|
||||||
|
|
||||||
The Universal Mission for DCS World is an attempt to create a fully dynamic single-player/PvE mission giving access to the whole content of DCS World in a structure similar to the one found in old "simulators", like the early Microprose games (think F-117 or the Strike Eagle serie).
|
The Universal Mission for DCS World is a fully dynamic single-player/PvE mission giving access to the whole content of DCS World.
|
||||||
These game had both fun and clear objectives, endless replayability and a career system that made sure that something was at stake: crash and die, and you'll lose all these hard-earned medals.
|
|
||||||
|
|
||||||
As the original creator of [Briefing Room](https://github.com/DCS-BR-Tools/briefing-room-for-dcs) (now maintained by the talented John Harvey), I've always wanted to create an easy-to-use, enticing and fun mission generator for DCS, capable of creating CPU-light missions without requiring an external program.
|
|
||||||
I think with The Universal Mission is, finally, the proper way to approach this problem. The current version is still an early beta but most core features are already working. I hope you'll like it.
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Can generate any kind of mission: ground attack, interception, strike, airbase attack, CAS, CAP, and more
|
- Can generate any kind of mission: ground attack, interception, strike, airbase attack, CAS, CAP, and more
|
||||||
- Completely dynamic, no two missions are ever the same
|
- Completely dynamic, no two missions are ever the same
|
||||||
- Entirely self-contained inside a .miz file, no need for any external program
|
- Entirely self-contained inside a .miz file, no need for any external program
|
||||||
- More than 300 voiced radio messages for immersive and realistic coms
|
- More than 325 voiced radio messages for immersive and realistic coms
|
||||||
- Supports both single-player and small-scale PvE on closed servers
|
- Supports both single-player and small-scale PvE on closed servers
|
||||||
- Persistent single player career mode, with awards and promotions. Dying won't reset your progress, but you have to come back to base alive for your kills and completed objectives to be saved to your profile, so watch out for SAMs on your way home
|
- Persistent single player career mode, with awards and promotions. Dying won't reset your progress, but you have to come back to base alive for your kills and completed objectives to be saved to your profile, so watch out for SAMs on your way home
|
||||||
- All new AI wingman system, smarter and more immersive than DCS's original wingmen
|
- All new AI wingman system, smarter and more immersive than DCS's original wingmen
|
||||||
@ -26,26 +22,26 @@ I think with The Universal Mission is, finally, the proper way to approach this
|
|||||||
[](./docs/target-zones.jpg)
|
[](./docs/target-zones.jpg)
|
||||||
[](./docs/career-mode.jpg)
|
[](./docs/career-mode.jpg)
|
||||||
|
|
||||||
### Limitations of current beta version
|
#### Limitations of current beta version
|
||||||
|
|
||||||
**Please read the "planned development" section below for more information.**
|
**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
|
||||||
- Germany support will come soon, others will follow later
|
|
||||||
- Not all mission types are supported yet
|
- Not all mission types are supported yet
|
||||||
- Career progress may be lost because of future updates, don't get too attached to it
|
- Career progress may be lost because of future updates, don't get too attached to it
|
||||||
|
|
||||||
### Known bugs
|
<h4>Known bugs in latest release</h4>
|
||||||
|
|
||||||
- AWACS datalink info is now displayed on SA pages
|
- AWACS datalink info not displayed properly on SA pages
|
||||||
|
|
||||||
## How to use/play The Universal Mission?
|
## How to use/play The Universal Mission?
|
||||||
|
|
||||||
|
**Please refer to the [User's manual](https://github.com/akaAgar/the-universal-mission-for-dcs-world/blob/main/The%20Universal%20Mission%20-%20User's%20Manual.md) for additional information.**
|
||||||
|
|
||||||
### First setup
|
### First setup
|
||||||
|
|
||||||
- Download the latest release from this GitHub page.
|
- Download the latest release from this GitHub page.
|
||||||
- Copy the provided autoexec.cfg file to your **[Saved Games]\DCS\Config directory**
|
- 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**
|
- 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.
|
- _**(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**
|
- Please note: should you want to backup, delete or transfer it, career progress is saved in **[DCS World installation directory]\TheUniversalMission.sav**
|
||||||
@ -67,118 +63,48 @@ Please refer to the "Advanced stuff you may want to try" section to learn all th
|
|||||||
- When you're ready, pick the "Begin mission" option, wait a few seconds (precaching all the game assets can take some time, especially if you have a slow CPU), you're ready to go!
|
- When you're ready, pick the "Begin mission" option, wait a few seconds (precaching all the game assets can take some time, especially if you have a slow CPU), you're ready to go!
|
||||||
- Use the F10 mission and check the F10 map for additional information about the mission (see "Using the mission menu" below). Don't forget to come back to base alive, all awarded XP and completed objectives will only be saved to your pilot profile once you've landed
|
- Use the F10 mission and check the F10 map for additional information about the mission (see "Using the mission menu" below). Don't forget to come back to base alive, all awarded XP and completed objectives will only be saved to your pilot profile once you've landed
|
||||||
|
|
||||||
### Using the mission menu
|
### A few notes regarding multiplayer
|
||||||
|
|
||||||
Most features of The Universal Mission require the use of the "F10. Other" menu. To access it, press the "Communication menu" key (check the key bindings), navigate to the root menu by pressing F11 ("Previous menu") if need, then press "F10" to access the "Other" menu.
|
|
||||||
The exact content of the menu will depend on the current phase of the mission.
|
|
||||||
|
|
||||||
#### On startup/when no mission is active
|
|
||||||
|
|
||||||
- **Display mission settings**: Displays the current mission settings, that will be applied if you choose to start the mission now.
|
|
||||||
- **Change mission settings**: Allows you to change the mission settings to your taste.
|
|
||||||
- **Blue coalition**: Who is the blue coalition? Determines the type of units that will be spawned. Available factions (e.g. NATO) depend on the missions's time period and theater.
|
|
||||||
- **Red coalition**: Who is the red coalition? Determines the type of units that will be spawned. Available factions (e.g. USSR) depend on the missions's time period and theater.
|
|
||||||
- **Mission type**: What will your mission be?
|
|
||||||
- **Antiship strike**: Sink enemy warships and cargo ships.
|
|
||||||
- **Ground attack**: Interdiction missions against armor, artillery and convoys.
|
|
||||||
- **Interception**: Shoot down strategic airplanes (bombers, transports...) and enemy attack planes on interdiction missions.
|
|
||||||
- **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.
|
|
||||||
- Missions taking place in enemy territory award 30% more XP to account for increased SAM threat and proximity of enemy airbases.
|
|
||||||
- Make sure to pick a region not too far away from your starting location if you don't like long ingresses.
|
|
||||||
- Picking a region very close to your starting location (for instance, the one where your airbase is located in) can also be a bad idea, as you might takeoff in range of an enemy SAM.
|
|
||||||
- Be aware that targets of antiship strikes will always be spawned in open seas, which can be quite far if you picked a landlocked target zone.
|
|
||||||
- **Target count**: How many objectives will be spawned. More objectives means potentially more xp in a single sortie, so better medals, but also more work and more risk. Be aware that you can RTB to rearm/refuel at any time between objectives, but you won't accumulate as many single-sortie XP as if you complete objectives without going back to base, because XP is awarded to your profile and reset each time you land.
|
|
||||||
- **Enemy air defense**: Amount, quality and skill of enemy surface-to-air units (AAA, MANPADS and SAM). A higher setting awards more XP.
|
|
||||||
- **Enemy air force**: Amount, quality and skill of enemy combat air patrols. A higher setting awards more XP.
|
|
||||||
- **Wingmen count**: How many wingmen will fly by your side (from zero to three). A small XP penalty is added for each additional wingman. Wingman won't get replaced if they get shot during a mission, but they will (with full payload) each time you land and takeoff again. Only shown in single-player missions.
|
|
||||||
- **Friendly AI CAP**: Should AI fighter aicraft be spawned regularly to patrol the AO and shoot down potential threats? Disabling this option will award you more XP (only if "Enemy air force" is not set to "None") but also means you and your wingmen will be alone against the whole enemy air force.
|
|
||||||
- **View pilot career stats**: Displays a list of your achievements, as well as your medal case. Only available when playing single-player missions and if the Lua IO module has been unsanitized (see "First setup" above)
|
|
||||||
- **Begin mission**: Starts a mission with the current settings.
|
|
||||||
|
|
||||||
#### During the mission
|
|
||||||
|
|
||||||
- **Mission status**: Displays a summary of the mission's status (list of objectives and progress on each objective).
|
|
||||||
- **Objectives**: Displays a list of special commands related to each of the mission's objectives. Be aware that some objectives may have no special commands associated with them.
|
|
||||||
- **Smoke marker on target**: Asks for a friendly JTAC to pop a smoke marker on the target. Makes finding the target easier, but will cost you a small XP penalty. Only available for missions where a JTAC is available (it's pretty hard to throw a smoke grenade at an airplane or a ship in the middle of the sea).
|
|
||||||
- **Navigation**: Displays a list of commands related to navigational assistance.
|
|
||||||
- **Navigation to objective [OBJECTIVE NAME]**: Displays the coordinates of the objective, its BRA ("fly X for Y") relative to the player's position and an estimated flight time and ETA. Some objectives types (e.g. strike missions) are provided with exact coordinates, but most will only have approximate coordiantes, so you'll have to search for targets yourself once in the objective area.
|
|
||||||
- **Flight**: Displays a list of commands for your wingmen. Only shown in single-player missions and if wingmen are available for this mission.
|
|
||||||
- **Cover me!**: Tasks your wingmen to immediately engage any nearby air threats.
|
|
||||||
- **Engage**: Tasks your wingmen to engage a certain type of targets. Targets must be detected by your wingmen (see "Report contacts" below), or they won't be able to engage them.
|
|
||||||
- **Report contacts**: Asks your wingmen for a list of all detected contacts. According to range and sensors capabilities, their reports can go from perfect ID (e.g. "Su-27") to very generic descriptions (e.g. "fighter" or even "aircraft")
|
|
||||||
- **Hold position**: Tasks your wingmen to orbit at their current position. All other tasking will be aborted.
|
|
||||||
- **Change altitude**: Asks your wingmen to change their altitude. This altitude will be employed when attacking on orbiting but not when rejoining/forming up with you (in that case, they'll match your altitude).
|
|
||||||
- **Status report**: Asks your wingmen for a complete report (damage sustained, fuel status, available payload).
|
|
||||||
- **Rejoin**: Asks your wingmen to rejoin and follow you. All other tasking will be aborted. This is the default tasking when wingmen take off and when they complete another task.
|
|
||||||
- **AWACS**: Displays a list of commands for the AWACS. Only shown if an AWACS aircraft is available for this mission.
|
|
||||||
- **Bogey dope**: Asks for the nearest enemy air threat
|
|
||||||
- **Picture**: Asks for a summary of all detected enemy aircraft
|
|
||||||
- **Display mission score**: Displays the number of XP gained and objectives completed since your last takeoff. They will be added to your flight log (and any promotions/medals be awarded) the next time you land. If you crash, eject or abort the mission, all currently "stowed" XP and objectives will be lost. Only available when playing single-player missions and if the Lua IO module has been unsanitized (see "First setup" above)
|
|
||||||
- **Abort mission**: Aborts the current mission and forfeit all XP/objectives gained since last landing. The game will ask for confirmation so you don't select this option by mistake.
|
|
||||||
|
|
||||||
### Advanced stuff you may want to try
|
|
||||||
|
|
||||||
The Universal Mission is designed to be easily editable to suit your preferences. Here are a few things you could do after opening the .miz file in DCS World's mission editor.
|
|
||||||
|
|
||||||
#### Player aircraft
|
|
||||||
|
|
||||||
- Change the player aircraft starting condition (runway, parking or parking hot). Air starts are not recommended as all players must be on the ground to begin a new mission
|
|
||||||
- Move it to another airbase, change its coalition (make sure blue players are spawned on an airbase located in a BLUFOR zone are red players are spawned on an airbase located in a REDFOR zone)
|
|
||||||
- You may also add an aircraft carrier or a FARP for the player to take off from
|
|
||||||
- Change its default loadout if you plan to play a specific kind of mission and don't want to lose time asking the ground crew to rearm your aircraft (e.g. if you know you want to play SEAD missions, you may as well stock up on AGM-88s)
|
|
||||||
- Change the skill level from "Player" to "Client" and add other aircraft to create a multiplayer mission to play with your friends. Keep in mind that the persistent career/player stats system will be disabled in multiplayer missions and that all player aircraft must belong to the same coalition (TUM does not support PvP)
|
|
||||||
|
|
||||||
#### Zones
|
|
||||||
|
|
||||||
- All zones whose names starts with BLUFOR or REDFOR decide the territory (and airbases) controlled by the blue and red coalitions
|
|
||||||
- Be aware that any change to the airbases coalitions will be superseded by the BLUFOR and REFOR zones
|
|
||||||
- All zones whose names starts with WATER are seas, used to spawn ships
|
|
||||||
- Zones with a name not starting with BLUFOR, REDFOR or WATER are target zones. These are zones where objectives can be spawned, who can be selected in the "objective location" setting of the intermission F10 menu
|
|
||||||
- Change, add or remove zones to create new possible target areas. A maximum of 10 target areas can be created, so they fit the F10 menu
|
|
||||||
|
|
||||||
#### A few notes regarding multiplayer
|
|
||||||
|
|
||||||
While The Universal Mission supports multiplayer and is perfectely suitable (and fun!) for playing with friends on a private server, it is **absolutely not suited for public servers** as missions settings can be edited by anyone at any time Using the mission menu.
|
While The Universal Mission supports multiplayer and is perfectely suitable (and fun!) for playing with friends on a private server, it is **absolutely not suited for public servers** as missions settings can be edited by anyone at any time Using the mission menu.
|
||||||
Please also note that PvP is not supported at the moment and that the mission will not launch if both coalitions have player slots.
|
Please also note that PvP is not supported at the moment and that the mission will not launch if both coalitions have player slots.
|
||||||
|
|
||||||
#### Other parameters
|
|
||||||
|
|
||||||
- _(Not yet implemented in this version)_ By changing the year in mission time parameters, the time period will be changed accordingly and the proper factions and AI units will be spawned during the mission. Time periods are:
|
|
||||||
- 1945 and before: World War 2
|
|
||||||
- 1946-1959: Korea War
|
|
||||||
- 1960-1974: Vietnam War
|
|
||||||
- 1975-1989: Late Cold War
|
|
||||||
- 1990-now: Modern
|
|
||||||
- _(Not yet implemented in this version)_ Changing the weather to make it more cloudy or windy, or setting the mission to nighttime, will make the mission more difficult but also award more points.
|
|
||||||
|
|
||||||
## Planned development
|
## Planned development
|
||||||
|
|
||||||
### VERY high priority
|
<h3>Planned for next version (can be subject to change)</h3>
|
||||||
|
|
||||||
- Additional "navigation" commands (vector to nearest airfield, complete weather report...)
|
- Additional content
|
||||||
- Bugfix: AWACS datalink info not showing on SA pages
|
- [ ] More objectives types
|
||||||
- Improved score multiplier taking into account various aspects of mission difficulty (weather, nighttime ops...)
|
- [ ] Close air support
|
||||||
- New objectives: helicopter (drop/pickup units...), CAP, CAS, OCA (airbase attack)
|
- [ ] Helicopter-specific tasking (land and pick up units, suppress infantry...)
|
||||||
- Support for the Germany and South America theaters
|
- [ ] Improved OCA missions: bomb enemy airbases
|
||||||
- Support for more factions and five different time periods (World War 2, Korea war, Vietnam war, late Cold war, Modern)
|
- Balance improvements
|
||||||
|
- [ ] Night missions should award more XP
|
||||||
|
- [ ] Tweaked XP requirements for medals/promotions
|
||||||
|
- Bug fixes
|
||||||
|
- Extras
|
||||||
|
- [ ] GitHub page
|
||||||
|
- Improvements
|
||||||
|
- Misc
|
||||||
|
- New features
|
||||||
|
- [ ] Administrative settings menu
|
||||||
|
- [ ] Friendly air defenses
|
||||||
|
- Quality of life/minor tweaks
|
||||||
|
- [ ] AI wingmen "Two was shot down!" call when witnessing another wingman killed
|
||||||
|
- [ ] AI wingmen "Winchester!" call when out of ammo
|
||||||
|
|
||||||
### High priority
|
### High priority
|
||||||
|
|
||||||
- Additional/improved radio messages
|
- Additional/improved radio messages
|
||||||
- More "flavor" radio messages ("fence in" when player approaches the AO, etc) so the world will feel more alive
|
- 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)
|
- 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)
|
||||||
- Friendly air defenses
|
|
||||||
- GitHub page
|
|
||||||
- Laser designation of targets by JTAC
|
- Laser designation of targets by JTAC
|
||||||
- PDF manual
|
- New objectives: CAP
|
||||||
- Support for all missing DCS World theaters
|
- 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
|
### 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)
|
- 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
|
- Spawning of tankers for long-range missions
|
||||||
- (maybe) Text (not voiceover) localization, if there's enough popular demand
|
- (maybe) Text (not voiceover) localization, if there's enough popular demand
|
||||||
@ -194,6 +120,7 @@ Please also note that PvP is not supported at the moment and that the mission wi
|
|||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- **Released under the GNU GPL 3.0 licence**
|
- **Released under the GNU GPL 3.0 licence**
|
||||||
|
- Uses YoloWingPixie's [DCS World Schema API](https://github.com/YoloWingPixie/dcs-world-schema)
|
||||||
- AI use/disclosure
|
- AI use/disclosure
|
||||||
- [ChatGPT](https://chatgpt.com/): used to generate first draft of radio messages
|
- [ChatGPT](https://chatgpt.com/): used to generate first draft of radio messages
|
||||||
- [ElevenLabs](https://elevenlabs.io/fr): used to generate radio messages voiceover
|
- [ElevenLabs](https://elevenlabs.io/fr): used to generate radio messages voiceover
|
||||||
@ -227,9 +154,48 @@ The core script is quite simple and small, I probably won't need too much help w
|
|||||||
|
|
||||||
## Version history
|
## 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" 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 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
|
||||||
|
- Removed unused zones from the "autoexec.cfg" file
|
||||||
|
- Extras
|
||||||
|
- First draft of the PDF manual
|
||||||
|
- New features
|
||||||
|
- Additional commands in the "navigation" menu
|
||||||
|
- Vector to nearest airfield
|
||||||
|
- Weather report
|
||||||
|
- 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 time to write them down or enter them in their flight computer
|
||||||
- **0.2.250729** (07/29/2025)
|
- **0.2.250729** (07/29/2025)
|
||||||
- **MAJOR CHANGE:** Added all new wingman system
|
- **MAJOR CHANGE:** Added all new wingman system
|
||||||
- Far for perfect but a lot better than AI's default wingmen
|
- Far for perfect but a lot better than DCS's default wingmen
|
||||||
- Many more engage/orbit/go to commands (see "Using the mission menu" above)
|
- Many more engage/orbit/go to commands (see "Using the mission menu" above)
|
||||||
- All new contacts report system: more realistic (see "AI units reports" changes below in this changelog) and does not spam the player with "new contact" messages
|
- All new contacts report system: more realistic (see "AI units reports" changes below in this changelog) and does not spam the player with "new contact" messages
|
||||||
- AI wingmen added using mission editor are now despawned on mission start to avoid conflict with TUM's own wingman system
|
- AI wingmen added using mission editor are now despawned on mission start to avoid conflict with TUM's own wingman system
|
||||||
@ -257,7 +223,7 @@ The core script is quite simple and small, I probably won't need too much help w
|
|||||||
- Moved "Request objective coordinates" radio commands to new "Navigation" submenu, which will include additional navigational assist in future versions
|
- Moved "Request objective coordinates" radio commands to new "Navigation" submenu, which will include additional navigational assist in future versions
|
||||||
- Lowered MANPADS count and skill (MANPADS are overpowered in DCS, especially SA-18)
|
- Lowered MANPADS count and skill (MANPADS are overpowered in DCS, especially SA-18)
|
||||||
- "New friendly/enemy aircraft taking off" radio messages now mention their BRAA relative to the player, number of bandits taking off now displayed as a word instead of digits
|
- "New friendly/enemy aircraft taking off" radio messages now mention their BRAA relative to the player, number of bandits taking off now displayed as a word instead of digits
|
||||||
- "Rifle!" and "Missile away!" radio calls now both used for any kind of A/G missiles
|
- "Rifle!" and "Missile away!" radio calls now both used for any kind of A/G missiles (except antiship and antiradiation missiles, who
|
||||||
- Tons of internal logic bugfixes and tweaks
|
- Tons of internal logic bugfixes and tweaks
|
||||||
- Tweaked XP bonus/penalty for various mission settings
|
- Tweaked XP bonus/penalty for various mission settings
|
||||||
- Vastly improved the way AI units reports on contact tracks. According to range and sensors capabilities, can go from perfect ID (e.g. "Su-27") to very generic descriptions (e.g. "fighter" or even "aircraft")
|
- Vastly improved the way AI units reports on contact tracks. According to range and sensors capabilities, can go from perfect ID (e.g. "Su-27") to very generic descriptions (e.g. "fighter" or even "aircraft")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- (DCS LUA ADD-ON) CONVERTER - UNITS CONVERSION FUNCTIONS
|
-- DCSEX.CONVERTER - UNITS CONVERSION FUNCTIONS
|
||||||
--
|
-- ====================================================================================
|
||||||
-- DCSEx.converter.celsiusToFahrenheit(t)
|
-- DCSEx.converter.celsiusToFahrenheit(t)
|
||||||
-- DCSEx.converter.degreesToRadians(degrees)
|
-- DCSEx.converter.degreesToRadians(degrees)
|
||||||
-- DCSEx.converter.fahrenheitToCelsius(fahrenheit)
|
-- DCSEx.converter.fahrenheitToCelsius(fahrenheit)
|
||||||
@ -21,6 +21,7 @@ DCSEx.converter = {}
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts Celsius degrees to Fahrenheit
|
-- Converts Celsius degrees to Fahrenheit
|
||||||
|
-------------------------------------
|
||||||
-- @param t Temperature in Celsius degrees
|
-- @param t Temperature in Celsius degrees
|
||||||
-- @return Temperature in Fahrenheit degrees
|
-- @return Temperature in Fahrenheit degrees
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -30,6 +31,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts angle in degrees to radians.
|
-- Converts angle in degrees to radians.
|
||||||
|
-------------------------------------
|
||||||
-- @param degrees Angle in degrees
|
-- @param degrees Angle in degrees
|
||||||
-- @return Angle in radians
|
-- @return Angle in radians
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -39,6 +41,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts Fahrenheit degrees to Celsius
|
-- Converts Fahrenheit degrees to Celsius
|
||||||
|
-------------------------------------
|
||||||
-- @param fahrenheit Temperature in Fahrenheit degrees
|
-- @param fahrenheit Temperature in Fahrenheit degrees
|
||||||
-- @return Temperature in Celsius degrees
|
-- @return Temperature in Celsius degrees
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -48,6 +51,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts feet to meters.
|
-- Converts feet to meters.
|
||||||
|
-------------------------------------
|
||||||
-- @param feet Distance in feet
|
-- @param feet Distance in feet
|
||||||
-- @return Distance in meters
|
-- @return Distance in meters
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -57,6 +61,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts Kelvin degrees to Celsius
|
-- Converts Kelvin degrees to Celsius
|
||||||
|
-------------------------------------
|
||||||
-- @param kelvin Temperature in Kelvin degrees
|
-- @param kelvin Temperature in Kelvin degrees
|
||||||
-- @return Temperature in Celsius degrees
|
-- @return Temperature in Celsius degrees
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -66,6 +71,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts Kelvin degrees to Fahrenheit
|
-- Converts Kelvin degrees to Fahrenheit
|
||||||
|
-------------------------------------
|
||||||
-- @param kelvin Temperature in Kelvin degrees
|
-- @param kelvin Temperature in Kelvin degrees
|
||||||
-- @return Temperature in Fahrenheit degrees
|
-- @return Temperature in Fahrenheit degrees
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -75,6 +81,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts kilometers per hour to meters per second.
|
-- Converts kilometers per hour to meters per second.
|
||||||
|
-------------------------------------
|
||||||
-- @param kmph speed in km/h
|
-- @param kmph speed in km/h
|
||||||
-- @return speed in m/s
|
-- @return speed in m/s
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -84,6 +91,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts knots to meters per second.
|
-- Converts knots to meters per second.
|
||||||
|
-------------------------------------
|
||||||
-- @param knots speed in knots
|
-- @param knots speed in knots
|
||||||
-- @return speed in m/s
|
-- @return speed in m/s
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -93,6 +101,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts meters to feet.
|
-- Converts meters to feet.
|
||||||
|
-------------------------------------
|
||||||
-- @param meters distance in meters
|
-- @param meters distance in meters
|
||||||
-- @return distance in feet
|
-- @return distance in feet
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -102,6 +111,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts meters to nautical miles.
|
-- Converts meters to nautical miles.
|
||||||
|
-------------------------------------
|
||||||
-- @param meters distance in meters
|
-- @param meters distance in meters
|
||||||
-- @return distance in nautical miles
|
-- @return distance in nautical miles
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -111,6 +121,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts meters per second to kilometers per hour.
|
-- Converts meters per second to kilometers per hour.
|
||||||
|
-------------------------------------
|
||||||
-- @param mps speed in m/s
|
-- @param mps speed in m/s
|
||||||
-- @return speed in km/h
|
-- @return speed in km/h
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -120,6 +131,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts meters per second to knots.
|
-- Converts meters per second to knots.
|
||||||
|
-------------------------------------
|
||||||
-- @param mps speed in m/s
|
-- @param mps speed in m/s
|
||||||
-- @return speed in knots
|
-- @return speed in knots
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -129,6 +141,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts nautical miles to meters.
|
-- Converts nautical miles to meters.
|
||||||
|
-------------------------------------
|
||||||
-- @param nm distance in nautical miles
|
-- @param nm distance in nautical miles
|
||||||
-- @return distance in meters
|
-- @return distance in meters
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -138,6 +151,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts angle in radians to degrees.
|
-- Converts angle in radians to degrees.
|
||||||
|
-------------------------------------
|
||||||
-- @param degrees Angle in radians
|
-- @param degrees Angle in radians
|
||||||
-- @return Angle in degrees
|
-- @return Angle in degrees
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|||||||
@ -1,33 +1,39 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSTOOLS - FUNCTIONS LINKED TO DCS WORLD RULES AND TABLES
|
-- DCSEX.DCS - FUNCTIONS HANDLING DCS WORLD'S GAME RULES AND TABLES
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
-- DCSEx.dcs.doNothing()
|
||||||
-- DCSEx.dcs.getBRAA(point, refPoint, showAltitude, metricSystem, casualFormat)
|
-- DCSEx.dcs.getBRAA(point, refPoint, showAltitude, metricSystem, casualFormat)
|
||||||
-- DCSEx.dcs.getCJTFForCoalition(coalitionID)
|
-- DCSEx.dcs.getCJTFForCoalition(coalitionID)
|
||||||
-- DCSEx.dcs.getCoalitionAsString(coalitionID)
|
-- DCSEx.dcs.getCoalitionAsString(coalitionID)
|
||||||
-- DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
-- DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
||||||
|
-- DCSEx.dcs.getFirstUnitCallsign(group)
|
||||||
-- DCSEx.dcs.getGroupCenterPoint(group)
|
-- DCSEx.dcs.getGroupCenterPoint(group)
|
||||||
-- DCSEx.dcs.getGroupIDAsNumber(group)
|
-- DCSEx.dcs.getGroupIDAsNumber(group)
|
||||||
-- DCSEx.dcs.getNearestObject(refPoint, objectTable)
|
-- DCSEx.dcs.getNearestObject(refPoint, objectTable)
|
||||||
-- DCSEx.dcs.getNearestObjects(refPoint, objectTable, maxCount)
|
-- DCSEx.dcs.getNearestObjects(refPoint, objectTable, maxCount)
|
||||||
-- DCSEx.dcs.getNearestPoints(refPoint, pointsTable, maxCount)
|
-- DCSEx.dcs.getNearestPoints(refPoint, pointsTable, maxCount)
|
||||||
|
-- DCSEx.dcs.getObjectIDAsNumber(obj)
|
||||||
-- DCSEx.dcs.getOppositeCoalition(coalitionID)
|
-- DCSEx.dcs.getOppositeCoalition(coalitionID)
|
||||||
-- DCSEx.dcs.getPlayerUnitsInGroup(group)
|
-- DCSEx.dcs.getPlayerUnitsInGroup(group)
|
||||||
-- DCSEx.dcs.getPlayerUnitsInGroupByID(groupID)
|
-- DCSEx.dcs.getPlayerUnitsInGroupByID(groupID)
|
||||||
-- DCSEx.dcs.getRadioModulationName(modulationID)
|
-- DCSEx.dcs.getRadioModulationName(modulationID)
|
||||||
-- DCSEx.dcs.getObjectIDAsNumber(obj)
|
-- DCSEx.dcs.getUnitCategoryFromFamily(unitFamily)
|
||||||
-- DCSEx.dcs.getUnitTypeFromFamily(unitFamily)
|
-- DCSEx.dcs.loadMission(fileName)
|
||||||
-- DCSEx.dcs.getUnitFamilyForDecade(unitFamily, decade) -- TODO: remove?
|
-- DCSEx.dcs.outPicture(fileName, durationSeconds, clearView, startDelay, horizontalAlign, verticalAlign, size, sizeUnits)
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.dcs = { }
|
DCSEx.dcs = { }
|
||||||
|
|
||||||
-- TODO: add description and update file header
|
-------------------------------------
|
||||||
|
-- Does nothing. Used to create commands that do nothing in the F10 menu
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.dcs.doNothing()
|
function DCSEx.dcs.doNothing()
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets a BRAA (bearing, range, altitude, aspect) string about a point
|
-- Gets a BRAA (bearing, range, altitude, aspect) string about a point
|
||||||
-- Format is "[bearing to unit] for [distance] at [altitude]"
|
-- Format is "[bearing to unit] for [distance] at [altitude]"
|
||||||
|
-------------------------------------
|
||||||
-- @param unit A unit
|
-- @param unit A unit
|
||||||
-- @param refPoint Reference point for the bearing and distance
|
-- @param refPoint Reference point for the bearing and distance
|
||||||
-- @param showAltitude Should altitude be displayed?
|
-- @param showAltitude Should altitude be displayed?
|
||||||
@ -86,6 +92,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the CJTF country for a given coalition
|
-- Returns the CJTF country for a given coalition
|
||||||
|
-------------------------------------
|
||||||
-- @param A coalition ID
|
-- @param A coalition ID
|
||||||
-- @return A country ID (country.id.CJTF_BLUE or country.id.CJTF_RED)
|
-- @return A country ID (country.id.CJTF_BLUE or country.id.CJTF_RED)
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -97,6 +104,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the name of a coalition, as a string ("blue", "red" or "neutral")
|
-- Returns the name of a coalition, as a string ("blue", "red" or "neutral")
|
||||||
|
-------------------------------------
|
||||||
-- @param A coalition ID
|
-- @param A coalition ID
|
||||||
-- @return A string
|
-- @return A string
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -109,8 +117,9 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the RGBA color table for the given coalition, accoding to NATO symbology colors
|
-- Returns the RGBA color table for the given coalition, accoding to NATO symbology colors
|
||||||
|
-------------------------------------
|
||||||
-- @param coalitionID A coalition side
|
-- @param coalitionID A coalition side
|
||||||
-- @param alpha (optional) Alpha. Default is 1
|
-- @param alpha (optional) Alpha. Default is 1.0
|
||||||
-- @return A RGBA color table
|
-- @return A RGBA color table
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
function DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
||||||
@ -123,7 +132,12 @@ function DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description
|
-------------------------------------
|
||||||
|
-- Returns the callsign table for the first unit of the group
|
||||||
|
-------------------------------------
|
||||||
|
-- @param group A group
|
||||||
|
-- @return A callsign table, or nil if no units or no group
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.dcs.getFirstUnitCallsign(group)
|
function DCSEx.dcs.getFirstUnitCallsign(group)
|
||||||
if not group then return nil end
|
if not group then return nil end
|
||||||
|
|
||||||
@ -137,6 +151,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a vec3 point at the center of all units of a group
|
-- Returns a vec3 point at the center of all units of a group
|
||||||
|
-------------------------------------
|
||||||
-- @param group A group object
|
-- @param group A group object
|
||||||
-- @return A vec3
|
-- @return A vec3
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -162,6 +177,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the ID of a group as a number (here to fix a bug where sometimes ID is returned as a string)
|
-- Returns the ID of a group as a number (here to fix a bug where sometimes ID is returned as a string)
|
||||||
|
-------------------------------------
|
||||||
-- @param group A group table
|
-- @param group A group table
|
||||||
-- @return An ID (as an number) or nil if group is nil or has no ID
|
-- @return An ID (as an number) or nil if group is nil or has no ID
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -175,6 +191,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the object nearest (in a 2D plane) from a given point
|
-- Returns the object nearest (in a 2D plane) from a given point
|
||||||
|
-------------------------------------
|
||||||
-- @param refPoint A reference point, as a vec2 or vec3
|
-- @param refPoint A reference point, as a vec2 or vec3
|
||||||
-- @param objectTable A table of DCS objects
|
-- @param objectTable A table of DCS objects
|
||||||
-- @return The object nearest from the point, or nil if no object was found
|
-- @return The object nearest from the point, or nil if no object was found
|
||||||
@ -187,6 +204,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the nearest objects (in a 2D plane) from a given point
|
-- Returns the nearest objects (in a 2D plane) from a given point
|
||||||
|
-------------------------------------
|
||||||
-- @param refPoint A reference point, as a vec2 or vec3
|
-- @param refPoint A reference point, as a vec2 or vec3
|
||||||
-- @param objectTable A table of DCS objects
|
-- @param objectTable A table of DCS objects
|
||||||
-- @param maxCount (optional) Maximum number of objects to return
|
-- @param maxCount (optional) Maximum number of objects to return
|
||||||
@ -218,6 +236,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the nearest points (in a 2D plane) from a given point
|
-- Returns the nearest points (in a 2D plane) from a given point
|
||||||
|
-------------------------------------
|
||||||
-- @param refPoint A reference point, as a vec2 or vec3
|
-- @param refPoint A reference point, as a vec2 or vec3
|
||||||
-- @param objectTable A table of points (vec2 or vec3)
|
-- @param objectTable A table of points (vec2 or vec3)
|
||||||
-- @param maxCount (optional) Maximum number of points to return
|
-- @param maxCount (optional) Maximum number of points to return
|
||||||
@ -249,6 +268,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the coalition opposed to the provided coalition (coalition.side.NEUTRAL still returns NEUTRAL)
|
-- Returns the coalition opposed to the provided coalition (coalition.side.NEUTRAL still returns NEUTRAL)
|
||||||
|
-------------------------------------
|
||||||
-- @param group A coalition
|
-- @param group A coalition
|
||||||
-- @return Another coalition
|
-- @return Another coalition
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -260,6 +280,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns all player-controlled units in a group
|
-- Returns all player-controlled units in a group
|
||||||
|
-------------------------------------
|
||||||
-- @param group A group object
|
-- @param group A group object
|
||||||
-- @return A table of unit objects
|
-- @return A table of unit objects
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -280,6 +301,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns all player-controlled units in the group with the given ID
|
-- Returns all player-controlled units in the group with the given ID
|
||||||
|
-------------------------------------
|
||||||
-- @param groupID A group ID
|
-- @param groupID A group ID
|
||||||
-- @return A table of unit objects
|
-- @return A table of unit objects
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -289,6 +311,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a radio modulation type as a string
|
-- Returns a radio modulation type as a string
|
||||||
|
-------------------------------------
|
||||||
-- @param modulationID A modulation ID (from radio.modulation enum)
|
-- @param modulationID A modulation ID (from radio.modulation enum)
|
||||||
-- @return A string
|
-- @return A string
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -297,66 +320,9 @@ function DCSEx.dcs.getRadioModulationName(modulationID)
|
|||||||
return "AM"
|
return "AM"
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
-- Returns a remplacement unit family for given family if it's not available in this decade (e.g. SAMs in the 1940s). Else returns the original family.
|
|
||||||
-- @param unitFamily An unit family
|
|
||||||
-- @param decade (optional) A decade, or the current decade from env.mission.date.Year
|
|
||||||
-- @return An unit family
|
|
||||||
-------------------------------------
|
|
||||||
function DCSEx.dcs.getUnitFamilyForDecade(unitFamily, decade)
|
|
||||||
-- TODO
|
|
||||||
-- decade = decade or envMission.getDecade()
|
|
||||||
|
|
||||||
-- if decade < 1990 then
|
|
||||||
-- if unitFamily == DCSEx.enums.unitFamily.UAVs then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.AttackHelicopters
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if decade < 1970 then
|
|
||||||
-- if unitFamily == DCSEx.enums.unitFamily.AWACS then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMShort then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.MobileAAA
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMShortIR then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.MobileAAA
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if decade < 1960 then
|
|
||||||
-- if unitFamily == DCSEx.enums.unitFamily.AttackHelicopters then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Fighters
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.MANPADS then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Infantry
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMLong then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.StaticAAA
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMMedium then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.StaticAAA
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SSMissiles then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Artillery
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.TransportHelicopters then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if decade < 1950 then
|
|
||||||
-- if unitFamily == DCSEx.enums.unitFamily.MobileAAA then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.APC
|
|
||||||
-- elseif unitFamily == DCSEx.enums.unitFamily.Tankers then
|
|
||||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
return unitFamily
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO: description
|
|
||||||
function DCSEx.dcs.getUnitTypeFromFamily(unitFamily)
|
|
||||||
return math.floor(unitFamily / 100)
|
|
||||||
end
|
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the ID of an object as a number (here to fix a bug where sometimes ID is returned as a string)
|
-- Returns the ID of an object as a number (here to fix a bug where sometimes ID is returned as a string)
|
||||||
|
-------------------------------------
|
||||||
-- @param obj An object (unit, static object...)
|
-- @param obj An object (unit, static object...)
|
||||||
-- @return An ID (as an number) or nil if unit is nil or has no ID
|
-- @return An ID (as an number) or nil if unit is nil or has no ID
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -365,21 +331,36 @@ function DCSEx.dcs.getObjectIDAsNumber(obj)
|
|||||||
return tonumber(obj:getID())
|
return tonumber(obj:getID())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description & file header
|
-------------------------------------
|
||||||
|
-- Returns a the unit category (Unit.Category enum) an unit family (DCSEx.enums.unitFamily) belongs to
|
||||||
|
-------------------------------------
|
||||||
|
-- @param unitFamily A value from the Unit.Category enum
|
||||||
|
-- @return A value from the DCSEx.enums.unitFamily enum
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.dcs.getUnitCategoryFromFamily(unitFamily)
|
||||||
|
return math.floor(unitFamily / 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Loads another DCS World mission
|
||||||
|
-------------------------------------
|
||||||
|
-- @param fileName Filename of the mission
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.dcs.loadMission(fileName)
|
function DCSEx.dcs.loadMission(fileName)
|
||||||
net.dostring_in("mission", string.format("a_load_mission(\"%s\")", fileName))
|
net.dostring_in("mission", string.format("a_load_mission(\"%s\")", fileName))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description & file header
|
-------------------------------------
|
||||||
-- function DCSEx.dcs.isMultiplayer()
|
-- Displays a picture on the screen of ALL players
|
||||||
-- if #net.get_player_list() > 0 then return true end
|
-------------------------------------
|
||||||
-- if dcs and dcs.isServer() == true then return true end
|
-- @param fileName Filename/ResourceName of the image in the mission resources
|
||||||
-- return false
|
-- @param durationSeconds Duration (in seconds) during which the image should be displayed
|
||||||
-- end
|
-- @param startDelay After how many seconds should the image be displayed? (default: 0)
|
||||||
|
-- @param horizontalAlign Horizontal alignment of the image (0/1/2=left/center/right) (default: 1)
|
||||||
-- TODO: a_end_mission
|
-- @param verticalAlign Vertical alignment of the image (0/1/2=top/center/bottom) (default: 1)
|
||||||
|
-- @param size Size of the image, in pixels or % of the screen (see sizeUnits) (default: 100)
|
||||||
-- TODO: description & file header
|
-- @param sizeUnits If 0, the size parameter is in pixels. If 1, it's in % of screen size (default: 0)
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.dcs.outPicture(fileName, durationSeconds, clearView, startDelay, horizontalAlign, verticalAlign, size, sizeUnits)
|
function DCSEx.dcs.outPicture(fileName, durationSeconds, clearView, startDelay, horizontalAlign, verticalAlign, size, sizeUnits)
|
||||||
clearView = clearView or false
|
clearView = clearView or false
|
||||||
startDelay = startDelay or 0
|
startDelay = startDelay or 0
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
|
-- ====================================================================================
|
||||||
|
-- DCSEX.ENUMS - VARIOUS ENUMS
|
||||||
|
-- ====================================================================================
|
||||||
|
-- DCSEx.enums.lineType
|
||||||
|
-- DCSEx.enums.taskEvent
|
||||||
|
-- DCSEx.enums.taskFamily
|
||||||
|
-- DCSEx.enums.taskFlag
|
||||||
|
-- DCSEx.enums.timePeriod
|
||||||
|
-- DCSEx.enums.unitFamily
|
||||||
|
-- DCSEx.enums.victoryCondition
|
||||||
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.enums = {}
|
DCSEx.enums = {}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Line types for map markers. The enum is missing from DCS
|
-- Line types for map markers. This enum is missing from DCS
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
DCSEx.enums.lineType = {
|
DCSEx.enums.lineType = {
|
||||||
NO_LINE = 0,
|
NO_LINE = 0,
|
||||||
@ -13,23 +25,14 @@ DCSEx.enums.lineType = {
|
|||||||
TWO_DASH = 6,
|
TWO_DASH = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
-- Event to check to see if a task/objective is complete
|
|
||||||
-------------------------------------
|
|
||||||
DCSEx.enums.spawnPointType = {
|
|
||||||
LAND_LARGE = 1,
|
|
||||||
LAND_MEDIUM = 2,
|
|
||||||
LAND_SMALL = 3,
|
|
||||||
SEA = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Event to check to see if a task/objective is complete
|
-- Event to check to see if a task/objective is complete
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
DCSEx.enums.taskEvent = {
|
DCSEx.enums.taskEvent = {
|
||||||
DESTROY = 1,
|
DAMAGE = 1,
|
||||||
DESTROY_SCENERY = 2,
|
DESTROY = 2,
|
||||||
LAND = 3,
|
DESTROY_SCENERY = 3,
|
||||||
|
LAND = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -37,26 +40,29 @@ DCSEx.enums.taskEvent = {
|
|||||||
-------------------------------------
|
-------------------------------------
|
||||||
DCSEx.enums.taskFamily = {
|
DCSEx.enums.taskFamily = {
|
||||||
ANTISHIP = 1,
|
ANTISHIP = 1,
|
||||||
-- CAP = 2, -- TODO
|
-- CAP = XXX,
|
||||||
-- CAS = 3, -- TODO
|
-- CAS = XXX,
|
||||||
GROUND_ATTACK = 2, -- 4
|
GROUND_ATTACK = 2,
|
||||||
-- HELICOPTER = XXX, -- 5
|
-- HELICOPTER = 3,
|
||||||
-- HELO_HUNT = XXX, -- 6
|
HELO_HUNT = 3,
|
||||||
INTERCEPTION = 3, -- 7
|
INTERCEPTION = 4,
|
||||||
-- OCA = XXX, -- 8
|
OCA = 5,
|
||||||
SEAD = 4, --9
|
SEAD = 6,
|
||||||
STRIKE = 5, -- 10
|
STRIKE = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Special events for tasks
|
-- Special events for tasks
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
DCSEx.enums.taskFlag = {
|
DCSEx.enums.taskFlag = {
|
||||||
ALLOW_JTAC = 1,
|
AIRBASE_TARGET = 1,
|
||||||
DESTROY_TRACK_RADARS_ONLY = 2,
|
ALLOW_JTAC = 2,
|
||||||
MOVING = 3,
|
DESTROY_TRACK_RADARS_ONLY = 3,
|
||||||
ON_ROADS = 4,
|
MOVING = 4,
|
||||||
SCENERY_TARGET = 5
|
ON_ROADS = 5,
|
||||||
|
PARKED_AIRCRAFT_TARGET = 6,
|
||||||
|
SCENERY_TARGET = 7,
|
||||||
|
FRIENDLY_TARGET = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -113,11 +119,13 @@ DCSEx.enums.unitFamily = {
|
|||||||
STATIC_STRUCTURE = 402
|
STATIC_STRUCTURE = 402
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Victory conditions for tasks/objectives
|
||||||
|
-------------------------------------
|
||||||
DCSEx.enums.victoryCondition = {
|
DCSEx.enums.victoryCondition = {
|
||||||
DESTROY = 1,
|
DESTROY = 1,
|
||||||
DESTROY_NO_AIR_DEFENSE = 2,
|
DESTROY_NO_AIR_DEFENSE = 2,
|
||||||
DESTROY_SCENERY = 3,
|
DESTROY_SCENERY = 3,
|
||||||
DESTROY_TRACK_RADARS_ONLY = 4, -- for SEAD tasks
|
DESTROY_TRACK_RADARS_ONLY = 4, -- for SEAD tasks
|
||||||
|
LAND_NEAR = 5
|
||||||
LAND_NEAR = 5,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- (DCS LUA ADD-ON) ENVMISSION - FUNCTIONS RELATED TO THE ENV.MISSION TABLE
|
-- DCSEX.ENVMISSION - FUNCTIONS RELATED TO THE ENV.MISSION TABLE
|
||||||
--
|
-- ====================================================================================
|
||||||
-- DCSEx.envMission.getDecade(yearOffset)
|
-- DCSEx.envMission.getDecade(yearOffset)
|
||||||
-- DCSEx.envMission.getDistanceToNearestPlayerSpawnPoint(point)
|
-- DCSEx.envMission.getDistanceToNearestPlayerSpawnPoint(coalition, point)
|
||||||
-- DCSEx.envMission.getGroup(groupID)
|
-- DCSEx.envMission.getGroup(groupID)
|
||||||
-- DCSEx.envMission.getGroups(sideID)
|
-- DCSEx.envMission.getGroups(sideID)
|
||||||
-- DCSEx.envMission.getPlayerGroups(coalitionId)
|
-- DCSEx.envMission.getPlayerGroups(coalitionId)
|
||||||
-- DCSEx.envMission.getPlayerGroupsCenterPoint(coalitionId)
|
-- DCSEx.envMission.getPlayerGroupsCenterPoint(coalitionId)
|
||||||
|
-- DCSEx.envMission.setBriefing(side, text, picture)
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.envMission = {}
|
DCSEx.envMission = {}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the decade during which the mission takes place (1940 to 2010)
|
-- Returns the decade during which the mission takes place (1940 to 2010)
|
||||||
|
-------------------------------------
|
||||||
-- @param yearOffset An offset to apply to the actual year
|
-- @param yearOffset An offset to apply to the actual year
|
||||||
-- @return The decade, as a number
|
-- @return The decade, as a number
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -22,6 +24,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the distance to the nearest player spawn point
|
-- Returns the distance to the nearest player spawn point
|
||||||
|
-------------------------------------
|
||||||
-- @param coalition Coalition the players belong to
|
-- @param coalition Coalition the players belong to
|
||||||
-- @param point A vec3 or vec2
|
-- @param point A vec3 or vec2
|
||||||
-- @return The distance, in meters, to the nearest player spawn point, or nil if no player spawn points are present
|
-- @return The distance, in meters, to the nearest player spawn point, or nil if no player spawn points are present
|
||||||
@ -42,6 +45,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets information about a group
|
-- Gets information about a group
|
||||||
|
-------------------------------------
|
||||||
-- @param groupID Group ID
|
-- @param groupID Group ID
|
||||||
-- @return Missiondata group table or nil if ID doesn't exist
|
-- @return Missiondata group table or nil if ID doesn't exist
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -59,6 +63,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets all unit groups
|
-- Gets all unit groups
|
||||||
|
-------------------------------------
|
||||||
-- @param sideID Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
-- @param sideID Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
||||||
-- @return Table of missiondata group tables
|
-- @return Table of missiondata group tables
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -98,6 +103,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets all player groups
|
-- Gets all player groups
|
||||||
|
-------------------------------------
|
||||||
-- @param coalitionId Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
-- @param coalitionId Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
||||||
-- @return Table of missiondata group tables
|
-- @return Table of missiondata group tables
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -126,6 +132,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Return the center 2D point of all player groups
|
-- Return the center 2D point of all player groups
|
||||||
|
-------------------------------------
|
||||||
-- @param coalitionId Coalition ID (coalition.side.*), or nil to use unit groups from all coalitions
|
-- @param coalitionId Coalition ID (coalition.side.*), or nil to use unit groups from all coalitions
|
||||||
-- @return A 2D point, or nil if no player groups
|
-- @return A 2D point, or nil if no player groups
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -145,7 +152,13 @@ function DCSEx.envMission.getPlayerGroupsCenterPoint(coalitionId)
|
|||||||
return center
|
return center
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description & file header
|
-------------------------------------
|
||||||
|
-- Sets the text for the briefing description in the briefing panel
|
||||||
|
-------------------------------------
|
||||||
|
-- @param side Coalition ID (coalition.side.*) of the coalition
|
||||||
|
-- @param text Text of the briefing
|
||||||
|
-- @param picture Resource name of the picture to use for the briefing
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.envMission.setBriefing(side, text, picture)
|
function DCSEx.envMission.setBriefing(side, text, picture)
|
||||||
text = text or ""
|
text = text or ""
|
||||||
text = text:gsub("\n", "\\n")
|
text = text:gsub("\n", "\\n")
|
||||||
|
|||||||
@ -1,106 +1,56 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSEx.IO - HANDLES READING/WRITING FILES
|
-- DCSEX.IO - HANDLES READING/WRITING FILES
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSEx.io.canReadAndWrite()
|
-- DCSEx.io.canReadAndWrite()
|
||||||
-- DCSEx.io.load(fileName)
|
-- DCSEx.io.load(fileName)
|
||||||
-- DCSEx.io.save(fileName, values)
|
-- DCSEx.io.save(fileName, str)
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.io = {}
|
DCSEx.io = {}
|
||||||
|
|
||||||
do
|
-------------------------------------
|
||||||
-------------------------------------
|
-- Returns true if the IO table has been unsanitized (allowing IO operations) and false if it hasn't been
|
||||||
-- Returns true if the IO table has been unsanitized (allowing IO operations)
|
-------------------------------------
|
||||||
-- and false if it hasn't been
|
-- @return A boolean
|
||||||
--
|
-------------------------------------
|
||||||
-- @return A boolean
|
function DCSEx.io.canReadAndWrite()
|
||||||
-------------------------------------
|
return io ~= nil
|
||||||
function DCSEx.io.canReadAndWrite()
|
end
|
||||||
return io ~= nil
|
|
||||||
end
|
-------------------------------------
|
||||||
|
-- Loads a string from a text file
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Loads a table from a text file
|
-- @param fileName Name of the file to read
|
||||||
--
|
-- @return A string, or nil if something went wrong
|
||||||
-- @param fileName Name of the file to read
|
-------------------------------------
|
||||||
-- @param obfuscate Should the file contents be obfuscated?
|
function DCSEx.io.load(fileName)
|
||||||
-- @return A table, or nil if something went wrong
|
-- IO table is sanitized, cannot read/write to disk
|
||||||
-------------------------------------
|
if not DCSEx.io.canReadAndWrite() then return nil end
|
||||||
-- function DCSEx.io.load(fileName, obfuscate)
|
|
||||||
-- obfuscate = obfuscate or false -- TODO: obfuscation
|
local saveFile = io.open(fileName, "r")
|
||||||
|
if not saveFile then return nil end
|
||||||
-- -- IO table is sanitized, cannot read/write to disk
|
local str = saveFile:read("*all")
|
||||||
-- if not DCSEx.io.canReadAndWrite() then return nil end
|
saveFile:close()
|
||||||
|
|
||||||
-- local saveFile = io.open(fileName, "r")
|
return str
|
||||||
-- if not saveFile then return nil end
|
end
|
||||||
|
|
||||||
-- local values = {}
|
-------------------------------------
|
||||||
-- local rawText = saveFile:read("*all")
|
-- Writes a string to a text file
|
||||||
-- for k, v in string.gmatch(rawText, "(%w+)=(%w+)") do
|
-------------------------------------
|
||||||
-- local numval = tonumber(v)
|
-- @param fileName Name of the file to write to. It will be overwritten if it exists.
|
||||||
-- if numval then
|
-- @param values Key/value table containing the values to save
|
||||||
-- values[k] = tonumber(v)
|
-- @param str String to write
|
||||||
-- else
|
-- @return True if everything went right, false otherwise
|
||||||
-- values[k] = v
|
-------------------------------------
|
||||||
-- -- trigger.action.outText("GET value \""..k.."\" AT \""..tostring(v).."\"", 1)
|
function DCSEx.io.save(fileName, str)
|
||||||
-- end
|
-- IO table is sanitized, cannot read/write to disk
|
||||||
-- end
|
if not DCSEx.io.canReadAndWrite() then return false end
|
||||||
-- saveFile:close()
|
|
||||||
|
local saveFile = io.open(fileName, "w")
|
||||||
-- return values
|
if not saveFile then return false end
|
||||||
-- end
|
saveFile:write(str)
|
||||||
|
saveFile:close()
|
||||||
function DCSEx.io.load(fileName)
|
|
||||||
-- IO table is sanitized, cannot read/write to disk
|
return true
|
||||||
if not DCSEx.io.canReadAndWrite() then return nil end
|
|
||||||
|
|
||||||
local saveFile = io.open(fileName, "r")
|
|
||||||
if not saveFile then return nil end
|
|
||||||
local str = saveFile:read("*all")
|
|
||||||
saveFile:close()
|
|
||||||
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
-------------------------------------
|
|
||||||
-- Saves a table to a text file
|
|
||||||
--
|
|
||||||
-- @param fileName Name of the file to write to
|
|
||||||
-- @param values Key/value table containing the values to save
|
|
||||||
-- @param obfuscate Should the file contents be obfuscated?
|
|
||||||
-- @return True if everything went right, false otherwise
|
|
||||||
-------------------------------------
|
|
||||||
-- function DCSEx.io.save(fileName, values, obfuscate)
|
|
||||||
-- obfuscate = obfuscate or false -- TODO: obfuscation
|
|
||||||
|
|
||||||
-- -- IO table is sanitized, cannot read/write to disk
|
|
||||||
-- if not DCSEx.io.canReadAndWrite() then return false end
|
|
||||||
|
|
||||||
-- -- No values or not a table
|
|
||||||
-- if values == nil then return false end
|
|
||||||
-- if type(values) ~= "table" then return false end
|
|
||||||
|
|
||||||
-- local saveFile = io.open(fileName, "w")
|
|
||||||
-- if not saveFile then return false end
|
|
||||||
|
|
||||||
-- for k,v in pairs(values) do
|
|
||||||
-- saveFile:write(k.."="..tostring(v).."\n")
|
|
||||||
-- -- trigger.action.outText("SET value \""..k.."\" TO \""..tostring(v).."\"", 1)
|
|
||||||
-- end
|
|
||||||
-- saveFile:close()
|
|
||||||
|
|
||||||
-- return true
|
|
||||||
-- end
|
|
||||||
function DCSEx.io.save(fileName, str)
|
|
||||||
-- IO table is sanitized, cannot read/write to disk
|
|
||||||
if not DCSEx.io.canReadAndWrite() then return false end
|
|
||||||
|
|
||||||
local saveFile = io.open(fileName, "w")
|
|
||||||
if not saveFile then return false end
|
|
||||||
saveFile:write(str)
|
|
||||||
saveFile:close()
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- (DCS LUA ADD-ON) MATH - EXTENSION TO THE "MATH" TABLE
|
-- DCSEX.MATH - MATH AND MATH-RELATED FUNCTIONS
|
||||||
--
|
-- ====================================================================================
|
||||||
-- (Constant) DCSEx.math.TWO_PI
|
-- (Constant) DCSEx.math.TWO_PI
|
||||||
-- DCSEx.math.addVec(vecA, vecB)
|
-- DCSEx.math.addVec(vecA, vecB)
|
||||||
-- DCSEx.math.clamp(val, min, max)
|
-- DCSEx.math.clamp(val, min, max)
|
||||||
-- DCSEx.math.getBearing(point, refPoint, returnAsNESWstring)
|
-- DCSEx.math.getBearing(point, refPoint, returnAsNESWstring)
|
||||||
-- DCSEx.math.getDistance2D(vec2a, vec2b)
|
-- DCSEx.math.getDistance2D(vec2a, vec2b)
|
||||||
-- DCSEx.math.getDistance3D(vec3a, vec3b)
|
-- DCSEx.math.getDistance3D(vec3a, vec3b)
|
||||||
-- DCSEx.math.getRelativeHeading(point, refObject)
|
-- DCSEx.math.getLength3D(vec3)
|
||||||
|
-- DCSEx.math.getRelativeHeading(point, refObject, format)
|
||||||
-- DCSEx.math.getVec2FromAngle(angle)
|
-- DCSEx.math.getVec2FromAngle(angle)
|
||||||
-- DCSEx.math.isPointInsideCircle(center, radius, vec2)
|
-- DCSEx.math.isPointInsideCircle(center, radius, vec2)
|
||||||
-- DCSEx.math.isPointInsidePolygon(polygon, vec2)
|
-- DCSEx.math.isPointInsidePolygon(polygon, vec2)
|
||||||
@ -28,12 +29,13 @@
|
|||||||
DCSEx.math = {}
|
DCSEx.math = {}
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Constants
|
-- Two times Pi
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
DCSEx.math.TWO_PI = math.pi * 2
|
DCSEx.math.TWO_PI = math.pi * 2
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the sum of two vec2 or vec3
|
-- Returns the sum of two vec2 or vec3
|
||||||
|
-------------------------------------
|
||||||
-- @param vecA A vector
|
-- @param vecA A vector
|
||||||
-- @param vecB Another vector
|
-- @param vecB Another vector
|
||||||
-- @return The sum of both vectors
|
-- @return The sum of both vectors
|
||||||
@ -48,6 +50,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Clamp a number value between min and max
|
-- Clamp a number value between min and max
|
||||||
|
-------------------------------------
|
||||||
-- @param value The value to clamp
|
-- @param value The value to clamp
|
||||||
-- @param min Minimum allowed value
|
-- @param min Minimum allowed value
|
||||||
-- @param max Maximum allowed value
|
-- @param max Maximum allowed value
|
||||||
@ -59,6 +62,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets the bearing between two vectors, in degrees
|
-- Gets the bearing between two vectors, in degrees
|
||||||
|
-------------------------------------
|
||||||
-- @param point A vec2/vec3
|
-- @param point A vec2/vec3
|
||||||
-- @param refPoint Vec2/vec3 to use as a reference point
|
-- @param refPoint Vec2/vec3 to use as a reference point
|
||||||
-- @param returnAsNESWstring Should the value be returned as a N/S/E/W string instead of a numeric value
|
-- @param returnAsNESWstring Should the value be returned as a N/S/E/W string instead of a numeric value
|
||||||
@ -90,6 +94,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the pythagorean distance between two 2D points or the length of a single vector
|
-- Returns the pythagorean distance between two 2D points or the length of a single vector
|
||||||
|
-------------------------------------
|
||||||
-- @param vec2a A 2D point
|
-- @param vec2a A 2D point
|
||||||
-- @param vec2b (optional) Another 2D point
|
-- @param vec2b (optional) Another 2D point
|
||||||
-- @return Distance between the points
|
-- @return Distance between the points
|
||||||
@ -104,6 +109,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the pythagorean distance between two 3D points or the length of a single vector
|
-- Returns the pythagorean distance between two 3D points or the length of a single vector
|
||||||
|
-------------------------------------
|
||||||
-- @param vec3a A 3D point
|
-- @param vec3a A 3D point
|
||||||
-- @param vec3b (optional) Another 3D point
|
-- @param vec3b (optional) Another 3D point
|
||||||
-- @return Distance between the points
|
-- @return Distance between the points
|
||||||
@ -114,8 +120,19 @@ function DCSEx.math.getDistance3D(vec3a, vec3b)
|
|||||||
return math.sqrt((vec3a.x - vec3b.x) ^ 2 + (vec3a.y - vec3b.y) ^ 2 + (vec3a.z - vec3b.z) ^ 2)
|
return math.sqrt((vec3a.x - vec3b.x) ^ 2 + (vec3a.y - vec3b.y) ^ 2 + (vec3a.z - vec3b.z) ^ 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns the length of a 3D vector
|
||||||
|
-------------------------------------
|
||||||
|
-- @param vec3 A 3D vector
|
||||||
|
-- @return Length of the vector
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.math.getLength3D(vec3)
|
||||||
|
return math.sqrt(vec3.x ^ 2 + vec3.y ^ 2 + vec3.z ^ 2)
|
||||||
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the relative heading difference between refObject and a given point
|
-- Returns the relative heading difference between refObject and a given point
|
||||||
|
-------------------------------------
|
||||||
-- @param point The point for which to check the relative heading
|
-- @param point The point for which to check the relative heading
|
||||||
-- @param refObject The reference object against which relative heading should be measured
|
-- @param refObject The reference object against which relative heading should be measured
|
||||||
-- @param format (optional) Return format. Possible formats are "clock" (1 o'clock...) or "cardinal" (NNW...)
|
-- @param format (optional) Return format. Possible formats are "clock" (1 o'clock...) or "cardinal" (NNW...)
|
||||||
@ -149,6 +166,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns an normalized vec2 from an angle/bearing in radians
|
-- Returns an normalized vec2 from an angle/bearing in radians
|
||||||
|
-------------------------------------
|
||||||
-- @param unit Angle/bearing in radians
|
-- @param unit Angle/bearing in radians
|
||||||
-- @return A normalized vec2
|
-- @return A normalized vec2
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -158,6 +176,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Is a point inside a circle?
|
-- Is a point inside a circle?
|
||||||
|
-------------------------------------
|
||||||
-- @param center The center of the circle, as a vec2
|
-- @param center The center of the circle, as a vec2
|
||||||
-- @param radius The radius of the circle
|
-- @param radius The radius of the circle
|
||||||
-- @param vec2 A vec2
|
-- @param vec2 A vec2
|
||||||
@ -169,6 +188,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Is a point inside a polygon?
|
-- Is a point inside a polygon?
|
||||||
|
-------------------------------------
|
||||||
-- @param vec2[] A polygon, as a table of vec2
|
-- @param vec2[] A polygon, as a table of vec2
|
||||||
-- @param vec2 A vec2
|
-- @param vec2 A vec2
|
||||||
-- @return True if vec2 is inside the polygon, false otherwise
|
-- @return True if vec2 is inside the polygon, false otherwise
|
||||||
@ -195,6 +215,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Compares two 2D or 3D points
|
-- Compares two 2D or 3D points
|
||||||
|
-------------------------------------
|
||||||
-- @param pointA a Point2 or Point3
|
-- @param pointA a Point2 or Point3
|
||||||
-- @param pointB another Point2 or Point3
|
-- @param pointB another Point2 or Point3
|
||||||
-- @return True if points are the same, false otherwise
|
-- @return True if points are the same, false otherwise
|
||||||
@ -210,7 +231,8 @@ function DCSEx.math.isSamePoint(pointA, pointB)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Linearly interpolates between two numbers
|
-- Linearly interpolates two numbers
|
||||||
|
-------------------------------------
|
||||||
-- @param val0 Value vers l=0
|
-- @param val0 Value vers l=0
|
||||||
-- @param val1 Value vers l=1
|
-- @param val1 Value vers l=1
|
||||||
-- @param t Interpolation between 0 and 1
|
-- @param t Interpolation between 0 and 1
|
||||||
@ -222,6 +244,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Multiplies both the x and y components of a vec2 by a floating-point value
|
-- Multiplies both the x and y components of a vec2 by a floating-point value
|
||||||
|
-------------------------------------
|
||||||
-- @param vec2 A vec2
|
-- @param vec2 A vec2
|
||||||
-- @param mult A floating-point value
|
-- @param mult A floating-point value
|
||||||
-- @return A vec2
|
-- @return A vec2
|
||||||
@ -232,6 +255,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns an normalized vec2
|
-- Returns an normalized vec2
|
||||||
|
-------------------------------------
|
||||||
-- @param unit A vec2
|
-- @param unit A vec2
|
||||||
-- @return A normalized vec2
|
-- @return A normalized vec2
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -242,6 +266,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random boolean
|
-- Returns a random boolean
|
||||||
|
-------------------------------------
|
||||||
-- @return A boolean
|
-- @return A boolean
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.math.randomBoolean()
|
function DCSEx.math.randomBoolean()
|
||||||
@ -250,20 +275,19 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random floating-point number between min and max
|
-- Returns a random floating-point number between min and max
|
||||||
|
-------------------------------------
|
||||||
-- @param min Minimum floating-point value
|
-- @param min Minimum floating-point value
|
||||||
-- @param max Maximum floating-point value
|
-- @param max Maximum floating-point value
|
||||||
-- @return A number
|
-- @return A number
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.math.randomFloat(min, max)
|
function DCSEx.math.randomFloat(min, max)
|
||||||
if min >= max then
|
if min >= max then return min end
|
||||||
return min
|
|
||||||
end
|
|
||||||
|
|
||||||
return min + math.random() * (max - min)
|
return min + math.random() * (max - min)
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random vec2 at a given distance of another vec2
|
-- Returns a random vec2 at a given distance of another vec2
|
||||||
|
-------------------------------------
|
||||||
-- @param point Reference point
|
-- @param point Reference point
|
||||||
-- @param distance Distance from the reference point
|
-- @param distance Distance from the reference point
|
||||||
-- @return A vec2
|
-- @return A vec2
|
||||||
@ -278,6 +302,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random vec2 in circle of a given center and radius
|
-- Returns a random vec2 in circle of a given center and radius
|
||||||
|
-------------------------------------
|
||||||
-- @param center Center of the circle as a vec2
|
-- @param center Center of the circle as a vec2
|
||||||
-- @param radius Radius of the circle
|
-- @param radius Radius of the circle
|
||||||
-- @param minRadius (optional) Minimum inner radius circle in which points should not be spawned
|
-- @param minRadius (optional) Minimum inner radius circle in which points should not be spawned
|
||||||
@ -306,6 +331,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random sign as a number, -1 or 1
|
-- Returns a random sign as a number, -1 or 1
|
||||||
|
-------------------------------------
|
||||||
-- @return -1 50% of the time, 1 50% of the time
|
-- @return -1 50% of the time, 1 50% of the time
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.math.randomSign()
|
function DCSEx.math.randomSign()
|
||||||
@ -317,6 +343,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts a value to a boolean
|
-- Converts a value to a boolean
|
||||||
|
-------------------------------------
|
||||||
-- @param val Value to convert
|
-- @param val Value to convert
|
||||||
-- @return A boolean, or nil if val was nil
|
-- @return A boolean, or nil if val was nil
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -331,6 +358,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts a vec2 to a vec3
|
-- Converts a vec2 to a vec3
|
||||||
|
-------------------------------------
|
||||||
-- @param vec2 A vec2
|
-- @param vec2 A vec2
|
||||||
-- @param y (Optional) A value for the vec3's y component or "land" to use land height
|
-- @param y (Optional) A value for the vec3's y component or "land" to use land height
|
||||||
-- @return A vec3 where v3.x=v2.x, v3.y=y and v3.z=v2.y
|
-- @return A vec3 where v3.x=v2.x, v3.y=y and v3.z=v2.y
|
||||||
@ -349,6 +377,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Converts a vec3 to a vec2
|
-- Converts a vec3 to a vec2
|
||||||
|
-------------------------------------
|
||||||
-- @param vec3 A vec3
|
-- @param vec3 A vec3
|
||||||
-- @return A vec2 where v2.x=v3.x and v2.y=v3.z
|
-- @return A vec2 where v2.x=v3.x and v2.y=v3.z
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- (DCS LUA ADD-ON) STRING - EXTENSION TO THE "STRING" TABLE
|
-- DCSEX.STRING - FUNCTIONS RELATED TO STRING MANIPULATION
|
||||||
--
|
-- ====================================================================================
|
||||||
-- DCSEx.string.firstToUpper(str)
|
-- DCSEx.string.firstToUpper(str)
|
||||||
-- DCSEx.string.getReadingTime(message)
|
-- DCSEx.string.getReadingTime(message)
|
||||||
|
-- DCSEx.string.join(table, separator)
|
||||||
|
-- DCSEx.string.getTimeString(timeInSeconds, separator)
|
||||||
|
-- DCSEx.string.toStringNumber(number, firstToUpper)
|
||||||
|
-- DCSEx.string.toStringThousandsSeparator(number)
|
||||||
-- DCSEx.string.split(str, separator)
|
-- DCSEx.string.split(str, separator)
|
||||||
-- DCSEx.string.startsWith(haystack, needle)
|
-- DCSEx.string.startsWith(haystack, needle)
|
||||||
-- DCSEx.string.trim(str)
|
-- DCSEx.string.trim(str)
|
||||||
@ -12,6 +16,7 @@ DCSEx.string = {}
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Uppercases the fist letter of a string
|
-- Uppercases the fist letter of a string
|
||||||
|
-------------------------------------
|
||||||
-- @param str A string
|
-- @param str A string
|
||||||
-- @return A string, with the first letter cast to upper case
|
-- @return A string, with the first letter cast to upper case
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -21,6 +26,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Estimates the time (in seconds) required to read a string
|
-- Estimates the time (in seconds) required to read a string
|
||||||
|
-------------------------------------
|
||||||
-- @param message A text message
|
-- @param message A text message
|
||||||
-- @return A duration in seconds
|
-- @return A duration in seconds
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -31,7 +37,13 @@ function DCSEx.string.getReadingTime(message)
|
|||||||
return DCSEx.math.clamp(#message / 8.7, 3.0, 15.0) -- 10.7 letters per second, minimum length 3 seconds, max length 15 seconds
|
return DCSEx.math.clamp(#message / 8.7, 3.0, 15.0) -- 10.7 letters per second, minimum length 3 seconds, max length 15 seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, update file header
|
-------------------------------------
|
||||||
|
-- Joins a table of string into a single string
|
||||||
|
-------------------------------------
|
||||||
|
-- @param table A table of strings
|
||||||
|
-- @param separator Separator used to glue table entries (default: "")
|
||||||
|
-- @return A string
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.string.join(table, separator)
|
function DCSEx.string.join(table, separator)
|
||||||
local joinedString = ""
|
local joinedString = ""
|
||||||
|
|
||||||
@ -45,10 +57,16 @@ function DCSEx.string.join(table, separator)
|
|||||||
return joinedString
|
return joinedString
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, file header
|
-------------------------------------
|
||||||
function DCSEx.string.getTimeString(timeInSeconds, useColon)
|
-- Converts a time of day (in seconds since midnight) to a human-readable time string
|
||||||
|
-------------------------------------
|
||||||
|
-- @param timeInSeconds Number of seconds since midnight (default: current time)
|
||||||
|
-- @param separator Separator between minutes and seconds (":", "h"...) (default: "")
|
||||||
|
-- @return The time, as as string
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.string.getTimeString(timeInSeconds, separator)
|
||||||
timeInSeconds = timeInSeconds or timer.getAbsTime()
|
timeInSeconds = timeInSeconds or timer.getAbsTime()
|
||||||
useColon = useColon or false
|
separator = separator or ""
|
||||||
|
|
||||||
timeInSeconds = math.max(0, timeInSeconds) % 86400
|
timeInSeconds = math.max(0, timeInSeconds) % 86400
|
||||||
|
|
||||||
@ -61,13 +79,15 @@ function DCSEx.string.getTimeString(timeInSeconds, useColon)
|
|||||||
local minutesStr = tostring(minutes)
|
local minutesStr = tostring(minutes)
|
||||||
if #minutesStr == 1 then minutesStr = "0"..minutesStr end
|
if #minutesStr == 1 then minutesStr = "0"..minutesStr end
|
||||||
|
|
||||||
local separator = ""
|
|
||||||
if useColon then separator = ":" end
|
|
||||||
|
|
||||||
return hoursStr..separator..minutesStr
|
return hoursStr..separator..minutesStr
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, file header
|
-------------------------------------
|
||||||
|
-- Converts a numeric value between 0 and 20 into its word/string representation
|
||||||
|
-------------------------------------
|
||||||
|
-- @param number A number (>=0, <=20)
|
||||||
|
-- @return A string
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.string.toStringNumber(number, firstToUpper)
|
function DCSEx.string.toStringNumber(number, firstToUpper)
|
||||||
firstToUpper = firstToUpper or false
|
firstToUpper = firstToUpper or false
|
||||||
local NUMBERS = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty" }
|
local NUMBERS = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty" }
|
||||||
@ -81,8 +101,13 @@ function DCSEx.string.toStringNumber(number, firstToUpper)
|
|||||||
return DCSEx.string.toStringThousandsSeparator(number)
|
return DCSEx.string.toStringThousandsSeparator(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, file header
|
-------------------------------------
|
||||||
-- Code from https://stackoverflow.com/questions/10989788/format-integer-in-lua
|
-- Converts a numeric value to a string, with proper thousands separators
|
||||||
|
-- (Code taken from https://stackoverflow.com/questions/10989788/format-integer-in-lua)
|
||||||
|
-------------------------------------
|
||||||
|
-- @param number A number
|
||||||
|
-- @return A string
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.string.toStringThousandsSeparator(number)
|
function DCSEx.string.toStringThousandsSeparator(number)
|
||||||
local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
|
local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
|
||||||
int = int:reverse():gsub("(%d%d%d)", "%1,")
|
int = int:reverse():gsub("(%d%d%d)", "%1,")
|
||||||
@ -90,7 +115,8 @@ function DCSEx.string.toStringThousandsSeparator(number)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Splits a string
|
-- Splits a string into a table
|
||||||
|
-------------------------------------
|
||||||
-- @param str The string to split
|
-- @param str The string to split
|
||||||
-- @param separator The string to split
|
-- @param separator The string to split
|
||||||
-- @return A table of split strings
|
-- @return A table of split strings
|
||||||
@ -105,6 +131,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Does a string starts with the given substring?
|
-- Does a string starts with the given substring?
|
||||||
|
-------------------------------------
|
||||||
-- @param haystack The string
|
-- @param haystack The string
|
||||||
-- @param needle The substring to look for
|
-- @param needle The substring to look for
|
||||||
-- @return True if it starts with the substring, false otherwise
|
-- @return True if it starts with the substring, false otherwise
|
||||||
@ -115,6 +142,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Trims a string
|
-- Trims a string
|
||||||
|
-------------------------------------
|
||||||
-- @param str A string
|
-- @param str A string
|
||||||
-- @return A string
|
-- @return A string
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- (DCS LUA ADD-ON) TABLE - EXTENSION TO THE "TABLE" TABLE
|
-- DCSEX.TABLE - FUNCTIONS RELATED TO TABLE MANIPULATION
|
||||||
--
|
-- ====================================================================================
|
||||||
-- DCSEx.table.contains(t, val)
|
-- DCSEx.table.contains(t, val)
|
||||||
-- DCSEx.table.containsKey(t, k)
|
-- DCSEx.table.containsKey(t, k)
|
||||||
-- DCSEx.table.containsAll(t, values)
|
-- DCSEx.table.containsAll(t, values)
|
||||||
-- DCSEx.table.containsAny(t, values)
|
-- DCSEx.table.containsAny(t, values)
|
||||||
|
-- DCSEx.table.containsAllKeys(t, keys)
|
||||||
|
-- DCSEx.table.containsAnyKeys(t, keys)
|
||||||
-- DCSEx.table.countNonNils(t)
|
-- DCSEx.table.countNonNils(t)
|
||||||
-- DCSEx.table.deepCopy(orig)
|
-- DCSEx.table.deepCopy(orig)
|
||||||
-- DCSEx.table.dump(t)
|
-- DCSEx.table.dump(t)
|
||||||
-- DCSEx.table.getKeys(t)
|
|
||||||
-- DCSEx.table.getKeyFromValue(t, val)
|
-- DCSEx.table.getKeyFromValue(t, val)
|
||||||
|
-- DCSEx.table.getKeys(t)
|
||||||
-- DCSEx.table.getRandom(t)
|
-- DCSEx.table.getRandom(t)
|
||||||
-- DCSEx.table.getRandomIndex(t)
|
-- DCSEx.table.getRandomIndex(t)
|
||||||
-- DCSEx.table.shuffle(t)
|
-- DCSEx.table.shuffle(t)
|
||||||
@ -19,6 +21,7 @@ DCSEx.table = {}
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns true if table t contains value val
|
-- Returns true if table t contains value val
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @param val A value
|
-- @param val A value
|
||||||
-- @return True if the table contains the value, false otherwise
|
-- @return True if the table contains the value, false otherwise
|
||||||
@ -35,6 +38,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns true if table t contains key k
|
-- Returns true if table t contains key k
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @param k A key
|
-- @param k A key
|
||||||
-- @return True if the table contains the key, false otherwise
|
-- @return True if the table contains the key, false otherwise
|
||||||
@ -47,10 +51,11 @@ function DCSEx.table.containsKey(t, k)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns true if table t contains all values in table values
|
-- Returns true if table t contains ALL values from the "values" table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @param values A table of values
|
-- @param values A table of values
|
||||||
-- @return True if the table contains all values, false otherwise
|
-- @return True if the table contains ALL values, false otherwise
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.table.containsAll(t, values)
|
function DCSEx.table.containsAll(t, values)
|
||||||
if not t then return false end
|
if not t then return false end
|
||||||
@ -65,10 +70,11 @@ function DCSEx.table.containsAll(t, values)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns true if table t contains at least one value in table values
|
-- Returns true if table t contains AT LEAST ONE value from the "values" table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @param values A table of values
|
-- @param values A table of values
|
||||||
-- @return True if the table contains at least one value, false otherwise
|
-- @return True if the table contains AT LEAST ONE value, false otherwise
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.table.containsAny(t, values)
|
function DCSEx.table.containsAny(t, values)
|
||||||
if not t then return false end
|
if not t then return false end
|
||||||
@ -82,6 +88,13 @@ function DCSEx.table.containsAny(t, values)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns true if table t contains ALL keys from the "keys" table
|
||||||
|
-------------------------------------
|
||||||
|
-- @param t A table
|
||||||
|
-- @param keys A table of keys
|
||||||
|
-- @return True if the table contains ALL keys, false otherwise
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.table.containsAllKeys(t, keys)
|
function DCSEx.table.containsAllKeys(t, keys)
|
||||||
if not t then return false end
|
if not t then return false end
|
||||||
|
|
||||||
@ -94,6 +107,13 @@ function DCSEx.table.containsAllKeys(t, keys)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns true if table t contains AT LEAST ONE key from the "keys" table
|
||||||
|
-------------------------------------
|
||||||
|
-- @param t A table
|
||||||
|
-- @param values A table of keys
|
||||||
|
-- @return True if the table contains AT LEAST ONE key, false otherwise
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.table.containsAnyKeys(t, keys)
|
function DCSEx.table.containsAnyKeys(t, keys)
|
||||||
if not t then return false end
|
if not t then return false end
|
||||||
|
|
||||||
@ -108,6 +128,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the number of non-nils elements in a table
|
-- Returns the number of non-nils elements in a table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @return A number
|
-- @return A number
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -122,6 +143,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a deep copy of the table, doesn't work with recursive tables (code from http://lua-users.org/wiki/CopyTable)
|
-- Returns a deep copy of the table, doesn't work with recursive tables (code from http://lua-users.org/wiki/CopyTable)
|
||||||
|
-------------------------------------
|
||||||
-- @param orig A table
|
-- @param orig A table
|
||||||
-- @return A deep copied clone of the table
|
-- @return A deep copied clone of the table
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -142,6 +164,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Dumps the content of a table as a string
|
-- Dumps the content of a table as a string
|
||||||
|
-------------------------------------
|
||||||
-- @param orig A table
|
-- @param orig A table
|
||||||
-- @return A string representaton of the table
|
-- @return A string representaton of the table
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -160,6 +183,7 @@ function DCSEx.table.dump(t)
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the key associated to a value in a table, or nil if not found
|
-- Returns the key associated to a value in a table, or nil if not found
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @param val A value
|
-- @param val A value
|
||||||
-- @return The key associated to this value in the table, or nil
|
-- @return The key associated to this value in the table, or nil
|
||||||
@ -173,6 +197,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns all the keys in an associative table
|
-- Returns all the keys in an associative table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @return An array of keys
|
-- @return An array of keys
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -191,6 +216,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random value from a numerically-indexed table
|
-- Returns a random value from a numerically-indexed table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @return A random element from the table
|
-- @return A random element from the table
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -200,6 +226,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a random index from a numerically-indexed table
|
-- Returns a random index from a numerically-indexed table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @return A random index from the table
|
-- @return A random index from the table
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -209,6 +236,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Randomly shuffles a numerically-indexed table
|
-- Randomly shuffles a numerically-indexed table
|
||||||
|
-------------------------------------
|
||||||
-- @param t A table
|
-- @param t A table
|
||||||
-- @return A table with shuffled values
|
-- @return A table with shuffled values
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
-- ====================================================================================
|
||||||
|
-- DCSEX.UNITCALLSIGNMAKER - GENERATES CALLSIGNS FOR NEW UNITS
|
||||||
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.unitCallsignMaker = {}
|
DCSEx.unitCallsignMaker = {}
|
||||||
|
|
||||||
do
|
do
|
||||||
@ -139,7 +143,7 @@ do
|
|||||||
|
|
||||||
local currentCallsigns = {}
|
local currentCallsigns = {}
|
||||||
for _,i in pairs(CALLSIGN_TYPE) do
|
for _,i in pairs(CALLSIGN_TYPE) do
|
||||||
currentCallsigns[i] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
currentCallsigns[i] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getCallsignTypeByUnitType(unitType)
|
local function getCallsignTypeByUnitType(unitType)
|
||||||
@ -225,7 +229,7 @@ do
|
|||||||
for _,g in ipairs(missionGroups) do
|
for _,g in ipairs(missionGroups) do
|
||||||
if g.units and g.units[1] then
|
if g.units and g.units[1] then
|
||||||
local unit = g.units[1]
|
local unit = g.units[1]
|
||||||
if unit.callsign and unit.callsign[1] and unit.callsign.name then
|
if unit.callsign and type(unit.callsign) == "table" and unit.callsign.name then
|
||||||
local callsignName = unit.callsign.name:sub(1, #unit.callsign.name - 2)
|
local callsignName = unit.callsign.name:sub(1, #unit.callsign.name - 2)
|
||||||
incrementCallsign(callsignName, unit.callsign[2])
|
incrementCallsign(callsignName, unit.callsign[2])
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSEX.UNITGROUMAKER - CREATES AND ADDS GROUPS TO THE GAME WORLD
|
-- DCSEX.UNITGROUPMAKER - CREATES AND ADDS GROUPS TO THE GAME WORLD
|
||||||
--
|
-- ====================================================================================
|
||||||
-- (local) createGroupTable(groupID, groupCategory, options)
|
-- (local) createGroupTable(groupID, groupCategory, options)
|
||||||
-- (local) getDefaultUnitSpread(groupCategory)
|
-- (local) getDefaultUnitSpread(groupCategory)
|
||||||
-- (local) getNextGroupID()
|
-- (local) getNextGroupID()
|
||||||
-- (local) getNextUnitID()
|
-- (local) getNextUnitID()
|
||||||
-- (local) setAircraftTaskAwacs(groupTable)
|
-- (local) setAircraftTaskAwacs(groupTable)
|
||||||
|
-- (local) setAircraftTaskCAP(groupTable)
|
||||||
|
-- (local) setAircraftTaskFollow(groupTable, followedGroupID, xyDistance)
|
||||||
-- (local) setAircraftTaskOrbit(groupTable, options)
|
-- (local) setAircraftTaskOrbit(groupTable, options)
|
||||||
-- (local) setCommand(groupTable, actionID, actionValue)
|
-- (local) setCommand(groupTable, actionID, actionValue)
|
||||||
-- (local) setOption(groupTable, optionID, optionValue)
|
-- (local) setOption(groupTable, optionID, optionValue)
|
||||||
|
-- DCSEx.unitGroupMaker.createStatic(side, point2, typeName, shapeName, heading, dead)
|
||||||
-- DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
-- DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
||||||
-- DCSEx.unitGroupMaker.initialize()
|
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.unitGroupMaker = {}
|
DCSEx.unitGroupMaker = {}
|
||||||
@ -18,6 +20,7 @@ DCSEx.unitGroupMaker = {}
|
|||||||
do
|
do
|
||||||
local nextGroupID = 1 -- ID of the next generated group
|
local nextGroupID = 1 -- ID of the next generated group
|
||||||
local nextUnitID = 1 -- ID of the next generated unit
|
local nextUnitID = 1 -- ID of the next generated unit
|
||||||
|
local dataLinkID = 201 -- Next datalink ID
|
||||||
|
|
||||||
local function createGroupTable(groupID, groupCategory, options)
|
local function createGroupTable(groupID, groupCategory, options)
|
||||||
local groupTable = {
|
local groupTable = {
|
||||||
@ -371,6 +374,14 @@ do
|
|||||||
setAircraftTaskOrbit(groupTable, options)
|
setAircraftTaskOrbit(groupTable, options)
|
||||||
end
|
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
|
if options.callsign then
|
||||||
groupCallsign = options.callsign
|
groupCallsign = options.callsign
|
||||||
else
|
else
|
||||||
@ -442,10 +453,26 @@ do
|
|||||||
unitTable.name = unitTable.callsign.name
|
unitTable.name = unitTable.callsign.name
|
||||||
|
|
||||||
-- Special properties for unit
|
-- Special properties for unit
|
||||||
|
unitTable.AddPropAircraft = {}
|
||||||
if aircraftDB.properties then
|
if aircraftDB.properties then
|
||||||
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
|
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
|
||||||
end
|
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
|
||||||
|
datalinkString = "00"..datalinkString
|
||||||
|
elseif #datalinkString == 4 then
|
||||||
|
datalinkString = "0"..datalinkString
|
||||||
|
end
|
||||||
|
unitTable.AddPropAircraft["STN_L16"] = datalinkString
|
||||||
|
dataLinkID = dataLinkID + 1
|
||||||
|
|
||||||
-- Common payload (fuel, gun ammo, etc)
|
-- Common payload (fuel, gun ammo, etc)
|
||||||
if aircraftDB.payload then
|
if aircraftDB.payload then
|
||||||
unitTable.payload = DCSEx.table.deepCopy(aircraftDB.payload)
|
unitTable.payload = DCSEx.table.deepCopy(aircraftDB.payload)
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
-- ====================================================================================
|
||||||
|
-- DCSEX.UNITNAMESMAKER - GENERATE CREDIBLE AND UNIT NAMES FOR UNIT GROUPS
|
||||||
|
-- ====================================================================================
|
||||||
DCSEx.unitNamesMaker = {}
|
DCSEx.unitNamesMaker = {}
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|||||||
@ -1,20 +1,28 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- WORLDTOOLS - FUNCTIONS RELATED TO THE GAME WORLD
|
-- DCSEX.WORLD - FUNCTIONS RELATED TO THE GAME WORLD
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSEx.world.collidesWithScenery(vec2, radius)
|
-- DCSEx.world.collidesWithScenery(vec2, radius)
|
||||||
-- DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery)
|
-- DCSEx.world.destroyGroupByID(groupID)
|
||||||
|
-- DCSEx.world.explodeUnit(unitID, amount)
|
||||||
-- DCSEx.world.getAllPlayers()
|
-- DCSEx.world.getAllPlayers()
|
||||||
-- DCSEx.world.getAllSceneryBuildings(minHealth)
|
-- DCSEx.world.getAllSceneryBuildings(minHealth)
|
||||||
-- DCSEx.world.getAllUnits(unitCategory)
|
-- DCSEx.world.getAllUnits(coalitionID, unitCategory)
|
||||||
-- DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
-- DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||||
-- DCSEx.world.getCoordinatesAsString(point)
|
-- DCSEx.world.getCoordinatesAsString(point, hideElevation)
|
||||||
-- DCSEx.world.getCurrentMarkerID()
|
-- DCSEx.world.getCurrentMarkerID()
|
||||||
|
-- DCSEx.world.getFirstPlayer(side)
|
||||||
-- DCSEx.world.getGroupByID(groupID)
|
-- DCSEx.world.getGroupByID(groupID)
|
||||||
|
-- DCSEx.world.getGroupCenter(group)
|
||||||
|
-- DCSEx.world.getMarkerByText(text, coalition)
|
||||||
-- DCSEx.world.getNextMarkerID()
|
-- DCSEx.world.getNextMarkerID()
|
||||||
|
-- DCSEx.world.getPlayersInAir(side)
|
||||||
|
-- DCSEx.world.getPlayersOnGround(side)
|
||||||
-- DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
-- DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||||
|
-- DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
||||||
-- DCSEx.world.getStaticObjectByID(staticID)
|
-- DCSEx.world.getStaticObjectByID(staticID)
|
||||||
-- DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
-- DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
||||||
-- DCSEx.world.getUnitByID(unitID)
|
-- DCSEx.world.getUnitByID(unitID)
|
||||||
|
-- DCSEx.world.getUnitsCenter(units)
|
||||||
-- DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
-- DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||||
-- DCSEx.world.setUnitLifePercent(unitID, life)
|
-- DCSEx.world.setUnitLifePercent(unitID, life)
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
@ -25,9 +33,16 @@ do
|
|||||||
-- TODO: get max marker already in use from envMission
|
-- TODO: get max marker already in use from envMission
|
||||||
local nextMarkerId = 1 -- Next map marker ID
|
local nextMarkerId = 1 -- Next map marker ID
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns true if vec2 is less than radius meters away from any scenery object
|
||||||
|
-------------------------------------
|
||||||
|
-- @param vec2 A 2d point
|
||||||
|
-- @param radius A range, in meters
|
||||||
|
-- @return True if vec2 is closer than radius meters from any object, false otherwise
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.collidesWithScenery(vec2, radius)
|
function DCSEx.world.collidesWithScenery(vec2, radius)
|
||||||
local foundOne = false
|
|
||||||
radius = radius or 8
|
radius = radius or 8
|
||||||
|
local foundOne = false
|
||||||
|
|
||||||
local volS = {
|
local volS = {
|
||||||
id = world.VolumeType.SPHERE,
|
id = world.VolumeType.SPHERE,
|
||||||
@ -47,46 +62,30 @@ do
|
|||||||
return foundOne
|
return foundOne
|
||||||
end
|
end
|
||||||
|
|
||||||
-- function DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery, territorySide, expandSearch)
|
-------------------------------------
|
||||||
-- expandSearch = expandSearch or true
|
-- Destroys a group
|
||||||
|
-------------------------------------
|
||||||
|
-- @param groupID ID of the group to destroy
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.world.destroyGroupByID(groupID)
|
||||||
|
if not groupID then return end
|
||||||
|
local g = DCSEx.world.getGroupByID(groupID)
|
||||||
|
if g then g:destroy() end
|
||||||
|
end
|
||||||
|
|
||||||
-- for _=0,16 do
|
-------------------------------------
|
||||||
-- for _=0,16 do
|
-- Spawns an explosion where an unit is located
|
||||||
-- local spawnPoint = nil
|
-------------------------------------
|
||||||
|
-- @param unitID ID of the unit
|
||||||
|
-- @param amount Intensity of the explosion
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.world.explodeUnit(unitID, amount)
|
||||||
|
net.dostring_in("mission", string.format("a_explosion_unit(%d, %f)", unitID, amount))
|
||||||
|
end
|
||||||
|
|
||||||
-- spawnPoint = DCSEx.math.randomPointInCircle(
|
|
||||||
-- vec2,
|
|
||||||
-- DCSEx.converter.nmToMeters(maxRadius),
|
|
||||||
-- DCSEx.converter.nmToMeters(minRadius),
|
|
||||||
-- surfaceType)
|
|
||||||
|
|
||||||
-- if spawnPoint and radiusWithoutScenery then
|
|
||||||
-- if DCSEx.world.collidesWithScenery(spawnPoint, radiusWithoutScenery) then
|
|
||||||
-- spawnPoint = nil
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if spawnPoint and territorySide then
|
|
||||||
-- if scramble.territories.getOwner(spawnPoint) ~= territorySide then
|
|
||||||
-- spawnPoint = nil
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if spawnPoint then return spawnPoint end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- if not expandSearch then return nil end
|
|
||||||
|
|
||||||
-- minRadius = minRadius * 0.9
|
|
||||||
-- maxRadius = maxRadius * 1.2
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- return nil
|
|
||||||
-- end
|
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a table of all player-controlled units currently in the game
|
-- Returns a table of all player-controlled units currently in the game
|
||||||
|
-------------------------------------
|
||||||
-- @return A table of unit objects
|
-- @return A table of unit objects
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.world.getAllPlayers()
|
function DCSEx.world.getAllPlayers()
|
||||||
@ -104,7 +103,8 @@ do
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a table of all map scenery buildings.
|
-- Returns a table of all map scenery buildings.
|
||||||
-- This function is rather CPU-consuming, better run it once on mission start and store the result in table.
|
-- This function is rather CPU-heavy, better run it once on mission start and store the results in a table.
|
||||||
|
-------------------------------------
|
||||||
-- @param minHealth Minimum health a building must have to be included in the table
|
-- @param minHealth Minimum health a building must have to be included in the table
|
||||||
-- @return A table of scenery objects
|
-- @return A table of scenery objects
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -134,6 +134,7 @@ do
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns all units belonging to a given category
|
-- Returns all units belonging to a given category
|
||||||
|
-------------------------------------
|
||||||
-- @param coalitionID Coalition ID (coalition.side.XXX) or nil to search all coalitions
|
-- @param coalitionID Coalition ID (coalition.side.XXX) or nil to search all coalitions
|
||||||
-- @param unitCategory An unit category (Group.Category.XXX)
|
-- @param unitCategory An unit category (Group.Category.XXX)
|
||||||
-- @return A table of unit tables
|
-- @return A table of unit tables
|
||||||
@ -156,7 +157,9 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the closest point to roads as a vec2
|
-- Returns the closest point to roads as a vec2.
|
||||||
|
-- An alternative to ED's zany land.getClosestPointOnRoads which returns two integers (!!???)
|
||||||
|
-------------------------------------
|
||||||
-- @param vec2 Coordinates to look for
|
-- @param vec2 Coordinates to look for
|
||||||
-- @return A vec2 with the closest point on roads
|
-- @return A vec2 with the closest point on roads
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -168,6 +171,7 @@ do
|
|||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the LL/MGRS coordinates of a point, as a string
|
-- Returns the LL/MGRS coordinates of a point, as a string
|
||||||
-- Based on code by Bushmanni - https://forums.eagle.ru/showthread.php?t=99480
|
-- Based on code by Bushmanni - https://forums.eagle.ru/showthread.php?t=99480
|
||||||
|
-------------------------------------
|
||||||
-- @param point The point, as a vec2 or vec3
|
-- @param point The point, as a vec2 or vec3
|
||||||
-- @param hideElevation (optional) Show elevation NOT be displayed? Default: false
|
-- @param hideElevation (optional) Show elevation NOT be displayed? Default: false
|
||||||
-- @return A string
|
-- @return A string
|
||||||
@ -222,6 +226,7 @@ do
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the last map marker ID generated by DCSEx.world.getNextMarkerID(), if any
|
-- Returns the last map marker ID generated by DCSEx.world.getNextMarkerID(), if any
|
||||||
|
-------------------------------------
|
||||||
-- @return A numeric ID, or nil
|
-- @return A numeric ID, or nil
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.world.getCurrentMarkerID()
|
function DCSEx.world.getCurrentMarkerID()
|
||||||
@ -229,8 +234,27 @@ do
|
|||||||
return nextMarkerId - 1
|
return nextMarkerId - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns the first player found
|
||||||
|
-------------------------------------
|
||||||
|
-- @param side The coalition the player must belong to, or nil to search for any player
|
||||||
|
-- @return A player unit object, or nil if no player was found
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.world.getFirstPlayer(side)
|
||||||
|
local players = {}
|
||||||
|
if side then
|
||||||
|
players = coalition.getPlayers(side)
|
||||||
|
else
|
||||||
|
players = DCSEx.world.getAllPlayers()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not players or #players == 0 then return nil end
|
||||||
|
return players[1]
|
||||||
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Searches and return a group by its ID
|
-- Searches and return a group by its ID
|
||||||
|
-------------------------------------
|
||||||
-- @param groupID ID of the group
|
-- @param groupID ID of the group
|
||||||
-- @return A group table, or nil if no group with this ID was found
|
-- @return A group table, or nil if no group with this ID was found
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -246,7 +270,13 @@ do
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description
|
-------------------------------------
|
||||||
|
-- Searches and return a map marker by its text (case-insensitive)
|
||||||
|
-------------------------------------
|
||||||
|
-- @param text Text to look for (case insensitive)
|
||||||
|
-- @param coalition Coalition the marker must belong to, or nil to search all coalitions
|
||||||
|
-- @return A map marker table, or nil if no marker was found
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.getMarkerByText(text, coalition)
|
function DCSEx.world.getMarkerByText(text, coalition)
|
||||||
if not text then return nil end
|
if not text then return nil end
|
||||||
text = text:lower()
|
text = text:lower()
|
||||||
@ -256,7 +286,7 @@ do
|
|||||||
local markerText = m.text or ""
|
local markerText = m.text or ""
|
||||||
markerText = markerText:lower()
|
markerText = markerText:lower()
|
||||||
if markerText == text then
|
if markerText == text then
|
||||||
if coalition == nil or m.coalition == coalition then
|
if not coalition or m.coalition == coalition then
|
||||||
return m
|
return m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -266,7 +296,8 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns a new, unique, map marker ID
|
-- Returns a new unique map marker ID
|
||||||
|
-------------------------------------
|
||||||
-- @return A numeric ID
|
-- @return A numeric ID
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.world.getNextMarkerID()
|
function DCSEx.world.getNextMarkerID()
|
||||||
@ -274,7 +305,12 @@ do
|
|||||||
return nextMarkerId - 1
|
return nextMarkerId - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, file header
|
-------------------------------------
|
||||||
|
-- Returns a table of all player units currently in the air (not on ramp/ground/runway)
|
||||||
|
-------------------------------------
|
||||||
|
-- @param side Coalition the players must belong to, or nil to search all coalitions
|
||||||
|
-- @return A table of player objects
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.getPlayersInAir(side)
|
function DCSEx.world.getPlayersInAir(side)
|
||||||
local players = {}
|
local players = {}
|
||||||
if side then
|
if side then
|
||||||
@ -293,7 +329,38 @@ do
|
|||||||
return playersInAir
|
return playersInAir
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, file header
|
-------------------------------------
|
||||||
|
-- Returns a table of all player units currently NOT in the air (on ramp/ground/runway)
|
||||||
|
-------------------------------------
|
||||||
|
-- @param side Coalition the players must belong to, or nil to search all coalitions
|
||||||
|
-- @return A table of player objects
|
||||||
|
-------------------------------------
|
||||||
|
function DCSEx.world.getPlayersOnGround(side)
|
||||||
|
local players = {}
|
||||||
|
if side then
|
||||||
|
players = coalition.getPlayers(side)
|
||||||
|
else
|
||||||
|
players = DCSEx.world.getAllPlayers()
|
||||||
|
end
|
||||||
|
|
||||||
|
local playersOnGround = {}
|
||||||
|
for _,p in ipairs(players) do
|
||||||
|
if not p:inAir() then
|
||||||
|
table.insert(playersOnGround, p)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return playersOnGround
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Returns a valid spawn point for a ground unit (not stuck in trees, buildings...) or a naval unit
|
||||||
|
-------------------------------------
|
||||||
|
-- @param zone Trigger zone in which to look for a spawn point
|
||||||
|
-- @param surface Type of surface (land.SurfaceType enum) to look for, or any to return any point (good for air units)
|
||||||
|
-- @param safeRadius Saferadius in meters from any obstacle (default: 100)
|
||||||
|
-- @return A 2D point, or nil if none was found
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
function DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
||||||
safeRadius = safeRadius or 100
|
safeRadius = safeRadius or 100
|
||||||
|
|
||||||
@ -336,7 +403,14 @@ do
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description
|
-------------------------------------
|
||||||
|
-- Returns a table of all scenery objects in a given radius
|
||||||
|
-------------------------------------
|
||||||
|
-- @param center 2D point on which to center object search
|
||||||
|
-- @param radius Radius (in meters) around the center in which to search
|
||||||
|
-- @param minHealth Minimum health for a scenery object to be valid. Allow filtering of small objects like bollards (default: 0)
|
||||||
|
-- @return A table of scenery objects
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
function DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||||
minHealth = minHealth or 0
|
minHealth = minHealth or 0
|
||||||
local sceneries = {}
|
local sceneries = {}
|
||||||
@ -362,23 +436,12 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Searches and return a static object by its ID
|
-- Returns the maximum height difference in a given radius around a point
|
||||||
-- @param staticID ID of the static object
|
-------------------------------------
|
||||||
-- @return An unit, or nil if no static object with this ID was found
|
-- @param coord 2D point in which to search
|
||||||
|
-- @param searchRadius Radius in meters
|
||||||
|
-- @return A numeric value, in meters
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.world.getStaticObjectByID(staticID)
|
|
||||||
for coalitionID = 1, 2 do
|
|
||||||
for _, s in pairs(coalition.getStaticObjects(coalitionID)) do
|
|
||||||
if DCSEx.dcs.getObjectIDAsNumber(s) == staticID then
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO: description, update file header
|
|
||||||
function DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
function DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
||||||
local samples = {}
|
local samples = {}
|
||||||
searchRadius = searchRadius or 5
|
searchRadius = searchRadius or 5
|
||||||
@ -403,30 +466,37 @@ do
|
|||||||
return tMax - tMin
|
return tMax - tMin
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, update file header
|
-------------------------------------
|
||||||
|
-- Returns the 2D center of unit group
|
||||||
|
-------------------------------------
|
||||||
|
-- @param group A group of unit
|
||||||
|
-- @return The 2D point center of all units' positions or 0,0 if no units were found
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.getGroupCenter(group)
|
function DCSEx.world.getGroupCenter(group)
|
||||||
return DCSEx.world.getUnitsCenter(group:getUnits())
|
return DCSEx.world.getUnitsCenter(group:getUnits())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description, update file header
|
-------------------------------------
|
||||||
function DCSEx.world.getUnitsCenter(units)
|
-- Searches and return a coalition static object by its ID
|
||||||
if not units or #units == 0 then return { x = 0, y = 0 } end
|
-------------------------------------
|
||||||
|
-- @param unitID ID of the static object
|
||||||
local center = { x = 0, y = 0 }
|
-- @return An static object, or nil if no unit with this ID was found
|
||||||
for _,u in pairs(units) do
|
-------------------------------------
|
||||||
local uPt2 = DCSEx.math.vec3ToVec2(u:getPoint())
|
function DCSEx.world.getStaticObjectByID(staticID)
|
||||||
center.x = center.x + uPt2.x
|
for coalitionID = 1,2 do
|
||||||
center.y = center.y + uPt2.y
|
for _,s in pairs(coalition.getStaticObjects(coalitionID)) do
|
||||||
|
if DCSEx.dcs.getObjectIDAsNumber(s) == staticID then
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
center.x = center.x / #units
|
return nil
|
||||||
center.y = center.y / #units
|
|
||||||
|
|
||||||
return center
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Searches and return an unit by its ID
|
-- Searches and returns an unit by its ID
|
||||||
|
-------------------------------------
|
||||||
-- @param unitID ID of the unit
|
-- @param unitID ID of the unit
|
||||||
-- @return An unit, or nil if no unit with this ID was found
|
-- @return An unit, or nil if no unit with this ID was found
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -446,23 +516,34 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Searches and return a coalition static object by its ID
|
-- Returns the 2D center of a number of units
|
||||||
-- @param unitID ID of the static object
|
|
||||||
-- @return An static object, or nil if no unit with this ID was found
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.world.getStaticObjectByID(staticID)
|
-- @param units A table of units
|
||||||
for coalitionID = 1,2 do
|
-- @return The 2D point center of all units' positions or 0,0 if no units were found
|
||||||
for _,s in pairs(coalition.getStaticObjects(coalitionID)) do
|
-------------------------------------
|
||||||
if DCSEx.dcs.getObjectIDAsNumber(s) == staticID then
|
function DCSEx.world.getUnitsCenter(units)
|
||||||
return s
|
if not units or #units == 0 then return { x = 0, y = 0 } end
|
||||||
end
|
|
||||||
end
|
local center = { x = 0, y = 0 }
|
||||||
|
for _,u in pairs(units) do
|
||||||
|
local uPt2 = DCSEx.math.vec3ToVec2(u:getPoint())
|
||||||
|
center.x = center.x + uPt2.x
|
||||||
|
center.y = center.y + uPt2.y
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
center.x = center.x / #units
|
||||||
|
center.y = center.y / #units
|
||||||
|
|
||||||
|
return center
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description
|
-------------------------------------
|
||||||
|
-- Returns true if a group exists and any of its units are alive, false otherwise
|
||||||
|
-------------------------------------
|
||||||
|
-- @param g A group
|
||||||
|
-- @param unitsMustBeInAir Are units on the ground ignored? (default: false)
|
||||||
|
-- @return True if a group exists and any of its units are alive, false otherwise
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
function DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||||
if not g then return false end
|
if not g then return false end
|
||||||
if not g:isExist() then return false end
|
if not g:isExist() then return false end
|
||||||
@ -484,22 +565,16 @@ do
|
|||||||
return atLeastOneActiveUnit
|
return atLeastOneActiveUnit
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description & file header
|
-------------------------------------
|
||||||
|
-- Sets the health of an unit
|
||||||
|
-------------------------------------
|
||||||
|
-- @param unitID ID of the unit
|
||||||
|
-- @param life Life percentage
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.world.setUnitLifePercent(unitID, life)
|
function DCSEx.world.setUnitLifePercent(unitID, life)
|
||||||
net.dostring_in("mission", string.format("a_unit_set_life_percentage(%d, %f)", unitID, life))
|
net.dostring_in("mission", string.format("a_unit_set_life_percentage(%d, %f)", unitID, life))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: description & file header
|
|
||||||
function DCSEx.world.explodeUnit(unitID, amount)
|
|
||||||
net.dostring_in("mission", string.format("a_explosion_unit(%d, %f)", unitID, amount))
|
|
||||||
end
|
|
||||||
|
|
||||||
function DCSEx.world.destroyGroupByID(groupID)
|
|
||||||
if not groupID then return end
|
|
||||||
local g = DCSEx.world.getGroupByID(groupID)
|
|
||||||
if g then g:destroy() end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- function DCSEx.world.destroySceneryInZone(zone, destructionPercent)
|
-- function DCSEx.world.destroySceneryInZone(zone, destructionPercent)
|
||||||
-- destructionPercent = destructionPercent or 0
|
-- destructionPercent = destructionPercent or 0
|
||||||
-- net.dostring_in("mission", string.format("a_scenery_destruction_zone(%d, %f)", zone.zoneId, destructionPercent))
|
-- net.dostring_in("mission", string.format("a_scenery_destruction_zone(%d, %f)", zone.zoneId, destructionPercent))
|
||||||
@ -518,4 +593,41 @@ do
|
|||||||
-- function DCSEx.world.shellingZone(zone, tnt, shellsCount)
|
-- function DCSEx.world.shellingZone(zone, tnt, shellsCount)
|
||||||
-- net.dostring_in("mission", string.format("a_shelling_zone(%d, %f, %d)", zone.zoneId, tnt, shellsCount))
|
-- net.dostring_in("mission", string.format("a_shelling_zone(%d, %f, %d)", zone.zoneId, tnt, shellsCount))
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
|
-- function DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery, territorySide, expandSearch)
|
||||||
|
-- expandSearch = expandSearch or true
|
||||||
|
|
||||||
|
-- for _=0,16 do
|
||||||
|
-- for _=0,16 do
|
||||||
|
-- local spawnPoint = nil
|
||||||
|
|
||||||
|
-- spawnPoint = DCSEx.math.randomPointInCircle(
|
||||||
|
-- vec2,
|
||||||
|
-- DCSEx.converter.nmToMeters(maxRadius),
|
||||||
|
-- DCSEx.converter.nmToMeters(minRadius),
|
||||||
|
-- surfaceType)
|
||||||
|
|
||||||
|
-- if spawnPoint and radiusWithoutScenery then
|
||||||
|
-- if DCSEx.world.collidesWithScenery(spawnPoint, radiusWithoutScenery) then
|
||||||
|
-- spawnPoint = nil
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if spawnPoint and territorySide then
|
||||||
|
-- if scramble.territories.getOwner(spawnPoint) ~= territorySide then
|
||||||
|
-- spawnPoint = nil
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if spawnPoint then return spawnPoint end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if not expandSearch then return nil end
|
||||||
|
|
||||||
|
-- minRadius = minRadius * 0.9
|
||||||
|
-- maxRadius = maxRadius * 1.2
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- return nil
|
||||||
|
-- end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,24 +1,35 @@
|
|||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- ZONETOOLS - FUNCTIONS RELATED TO MAP TRIGGER ZONES
|
-- DCSEX.ZONES - FUNCTIONS RELATED TO MAP TRIGGER ZONES
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readonly)
|
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
|
||||||
|
-- DCSEx.zones.getAirbases(zone, coalID, allowShips)
|
||||||
-- DCSEx.zones.getAll()
|
-- DCSEx.zones.getAll()
|
||||||
-- DCSEx.zones.getByName(name)
|
-- DCSEx.zones.getByName(name)
|
||||||
-- DCSEx.zones.getCenter(zoneTable)
|
-- DCSEx.zones.getCenter(zoneTable)
|
||||||
-- DCSEx.zones.getProperty(zoneTable, propertyName)
|
-- DCSEx.zones.getProperty(zoneTable, propertyName, defaultValue)
|
||||||
-- DCSEx.zones.getPropertyBoolean(zoneTable, propertyName, defaultValue)
|
-- DCSEx.zones.getPropertyBoolean(zoneTable, propertyName, defaultValue)
|
||||||
-- DCSEx.zones.getPropertyFloat(zoneTable, propertyName, defaultValue, min, max)
|
-- DCSEx.zones.getPropertyFloat(zoneTable, propertyName, defaultValue, min, max)
|
||||||
-- DCSEx.zones.getPropertyInt(zoneTable, propertyName, defaultValue, min, max)
|
-- DCSEx.zones.getPropertyInt(zoneTable, propertyName, defaultValue, min, max)
|
||||||
-- DCSEx.zones.getPropertyParse(zoneTable, propertyName, stringTable, valueTable, defaultValue)
|
-- DCSEx.zones.getPropertyParse(zoneTable, propertyName, stringTable, valueTable, defaultValue)
|
||||||
-- DCSEx.zones.getPropertyTable(zoneTable, propertyName)
|
-- DCSEx.zones.getPropertyTable(zoneTable, propertyName)
|
||||||
-- DCSEx.zones.getRadius(zoneTable, useMaxForQuads)
|
-- DCSEx.zones.getRadius(zoneTable, useMaxForQuads)
|
||||||
|
-- DCSEx.zones.getRandomPointInside(zoneTable, surfaceType)
|
||||||
-- DCSEx.zones.getSurfaceArea(zoneTable)
|
-- DCSEx.zones.getSurfaceArea(zoneTable)
|
||||||
-- DCSEx.zones.isPointInside(zoneTable, point)
|
-- DCSEx.zones.isPointInside(zoneTable, point)
|
||||||
-- ====================================================================================
|
-- ====================================================================================
|
||||||
|
|
||||||
DCSEx.zones = { }
|
DCSEx.zones = { }
|
||||||
|
|
||||||
-- TODO: function description
|
-------------------------------------
|
||||||
|
-- Draws a zone on the F10, visible for all players
|
||||||
|
-------------------------------------
|
||||||
|
-- @param zoneTable The zone to draw
|
||||||
|
-- @param lineColor Line color as a RGBA table
|
||||||
|
-- @param fillColor Fill color as a RGBA table
|
||||||
|
-- @param lineType Type of line from the DCSEx.enums.lineType enum
|
||||||
|
-- @param drawName Should the name of the zone be drawn too (default: false)
|
||||||
|
-- @param drawName Should the zone marker be read only? (default: true)
|
||||||
|
-------------------------------------
|
||||||
function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
|
function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
|
||||||
drawName = drawName or false
|
drawName = drawName or false
|
||||||
readOnly = readOnly or true
|
readOnly = readOnly or true
|
||||||
@ -62,8 +73,46 @@ function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawNa
|
|||||||
return markerID
|
return markerID
|
||||||
end
|
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
|
-- Returns all trigger zones
|
||||||
|
-------------------------------------
|
||||||
-- @return Table of zones
|
-- @return Table of zones
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function DCSEx.zones.getAll()
|
function DCSEx.zones.getAll()
|
||||||
@ -79,6 +128,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Finds and return a trigger zone by a certain name
|
-- Finds and return a trigger zone by a certain name
|
||||||
|
-------------------------------------
|
||||||
-- @param name Case-insensitive name of the zone
|
-- @param name Case-insensitive name of the zone
|
||||||
-- @return Zone table or nil if no zone with this name was found
|
-- @return Zone table or nil if no zone with this name was found
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -100,6 +150,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the center of a zone
|
-- Returns the center of a zone
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
||||||
-- @return A vec2
|
-- @return A vec2
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -119,6 +170,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the value of the property of a trigger zone, as a string
|
-- Returns the value of the property of a trigger zone, as a string
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @return The value of the property or nil if it doesn't exist
|
-- @return The value of the property or nil if it doesn't exist
|
||||||
@ -141,6 +193,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the value of the property of a trigger zone, parsed against a case-insensitive table of strings
|
-- Returns the value of the property of a trigger zone, parsed against a case-insensitive table of strings
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @param defaultValue Default value to return if no match was found
|
-- @param defaultValue Default value to return if no match was found
|
||||||
@ -157,6 +210,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the value of the property of a trigger zone, as a float
|
-- Returns the value of the property of a trigger zone, as a float
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @param defaultValue Default value to return if no match was found
|
-- @param defaultValue Default value to return if no match was found
|
||||||
@ -174,6 +228,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the value of the property of a trigger zone, as an integer
|
-- Returns the value of the property of a trigger zone, as an integer
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @param defaultValue Default value to return if no match was found
|
-- @param defaultValue Default value to return if no match was found
|
||||||
@ -189,6 +244,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Gets the value of a property of a trigger zone and parse it according to two correspondance tables
|
-- Gets the value of a property of a trigger zone and parse it according to two correspondance tables
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @param stringTable A table of strings
|
-- @param stringTable A table of strings
|
||||||
@ -211,6 +267,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the value of the property of a trigger zone, as a table of comma-separated lowercase strings
|
-- Returns the value of the property of a trigger zone, as a table of comma-separated lowercase strings
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param propertyName Case-insensitive name of the property
|
-- @param propertyName Case-insensitive name of the property
|
||||||
-- @return An table
|
-- @return An table
|
||||||
@ -223,6 +280,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the radius of a zone, in meter
|
-- Returns the radius of a zone, in meter
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
|
||||||
-- @param useMaxForQuads If true, return largest distance between the center and a vertex. If false (default value), returns the mean distance. Only used if the zone is a quad.
|
-- @param useMaxForQuads If true, return largest distance between the center and a vertex. If false (default value), returns the mean distance. Only used if the zone is a quad.
|
||||||
-- @return An table
|
-- @return An table
|
||||||
@ -271,6 +329,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns the surface area of a zone
|
-- Returns the surface area of a zone
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
||||||
-- @return A number, in squared meters
|
-- @return A number, in squared meters
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -289,6 +348,7 @@ end
|
|||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Returns true if a point is inside a zone
|
-- Returns true if a point is inside a zone
|
||||||
|
-------------------------------------
|
||||||
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
|
||||||
-- @param point A point, as a vec3 or vec2
|
-- @param point A point, as a vec3 or vec2
|
||||||
-- @return True if the point is inside the zone, false otherwise
|
-- @return True if the point is inside the zone, false otherwise
|
||||||
|
|||||||
@ -561,7 +561,7 @@ Library.aircraft = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
["A-50"] = {
|
["A-50"] = {
|
||||||
["altitude"] = 4898,
|
["altitude"] = 11000,
|
||||||
["speed"] = 220,
|
["speed"] = 220,
|
||||||
["payload"] = {
|
["payload"] = {
|
||||||
["chaff"] = 192,
|
["chaff"] = 192,
|
||||||
@ -2460,7 +2460,7 @@ Library.aircraft = {
|
|||||||
["pylons"] = {}
|
["pylons"] = {}
|
||||||
},
|
},
|
||||||
["E-2C"] = {
|
["E-2C"] = {
|
||||||
["altitude"] = 4510,
|
["altitude"] = 9000,
|
||||||
["speed"] = 133.3,
|
["speed"] = 133.3,
|
||||||
["payload"] = {
|
["payload"] = {
|
||||||
["chaff"] = 120,
|
["chaff"] = 120,
|
||||||
@ -2479,7 +2479,7 @@ Library.aircraft = {
|
|||||||
["pylons"] = {}
|
["pylons"] = {}
|
||||||
},
|
},
|
||||||
["E-3A"] = {
|
["E-3A"] = {
|
||||||
["altitude"] = 4800,
|
["altitude"] = 11000,
|
||||||
["speed"] = 220,
|
["speed"] = 220,
|
||||||
["payload"] = {
|
["payload"] = {
|
||||||
["chaff"] = 120,
|
["chaff"] = 120,
|
||||||
@ -7567,7 +7567,7 @@ Library.aircraft = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
["KJ-2000"] = {
|
["KJ-2000"] = {
|
||||||
["altitude"] = 4898,
|
["altitude"] = 11000,
|
||||||
["speed"] = 220,
|
["speed"] = 220,
|
||||||
["payload"] = {
|
["payload"] = {
|
||||||
["fuel"] = 70000,
|
["fuel"] = 70000,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -201,16 +201,17 @@ Library.radioMessages = {
|
|||||||
"$1, off the deck, forming up now."
|
"$1, off the deck, forming up now."
|
||||||
},
|
},
|
||||||
|
|
||||||
atcRequireNearestAirbase = { -- TODO: voiceover
|
atcRequireNearestAirbase = {
|
||||||
"Roger. Vectoring you to the nearest airbase.\n$1",
|
"Roger. Vectoring you to the nearest airbase.\n\n$1",
|
||||||
"Copy. Coordinates to nearest field inbound.\n$1",
|
"Copy. Coordinates to nearest field inbound.\n\n$1",
|
||||||
"Roger. Guide you direct to the nearest recovery airfield.\n$1"
|
"Roger. Guide you direct to the nearest recovery airfield.\n\n$1"
|
||||||
},
|
},
|
||||||
atcWeatherUpdate = { -- TODO: voiceover
|
atcRequireNearestAirbaseNone = "No friendly airbase is available at the moment.",
|
||||||
"Roger. Weather info coming up now.\n$1",
|
atcWeatherUpdate = {
|
||||||
"Copy. Weather report inbound.\n$1",
|
"Roger. Weather info coming up now.\n\n$1",
|
||||||
"This is control, checking conditions now.\n$1",
|
"Copy. Weather report inbound.\n\n$1",
|
||||||
"Copy. Weather data on the way.\n$1"
|
"This is control, checking conditions now.\n\n$1",
|
||||||
|
"Copy. Weather data on the way.\n\n$1"
|
||||||
},
|
},
|
||||||
atcSafeLanding = { "Be advised: $1 is wheels down at $2 and clear of runway.", "All aircraft, $1 has landed at $2 and vacated active. Runway is open for next inbound.", "Traffic, $1 is on deck at $2 and heading to parking. Runway clear.", "All flights, $1 just rolled out at $2 and cleared the active.", "Heads up, $1 landed at $2 and moving to the ramp. Runway available for next approach." },
|
atcSafeLanding = { "Be advised: $1 is wheels down at $2 and clear of runway.", "All aircraft, $1 has landed at $2 and vacated active. Runway is open for next inbound.", "Traffic, $1 is on deck at $2 and heading to parking. Runway clear.", "All flights, $1 just rolled out at $2 and cleared the active.", "Heads up, $1 landed at $2 and moving to the ramp. Runway available for next approach." },
|
||||||
atcSafeLandingPlayer = { "$1, wheels on deck, welcome back. You may taxi to the parking area.", "$1, good copy on landing. Exit when able, proceed to the parking area.", "$1, touchdown confirmed. Continue to parking.", "$1, welcome home. Clear of runway and taxi to parking area.", "$1, nice landing. Taxi to parking when ready." },
|
atcSafeLandingPlayer = { "$1, wheels on deck, welcome back. You may taxi to the parking area.", "$1, good copy on landing. Exit when able, proceed to the parking area.", "$1, touchdown confirmed. Continue to parking.", "$1, welcome home. Clear of runway and taxi to parking area.", "$1, nice landing. Taxi to parking when ready." },
|
||||||
@ -306,13 +307,13 @@ Library.radioMessages = {
|
|||||||
"$1, target already marked with smoke."
|
"$1, target already marked with smoke."
|
||||||
},
|
},
|
||||||
|
|
||||||
playerATCRequireNearestAirbase = { -- TODO: voiceover
|
playerATCRequireNearestAirbase = {
|
||||||
"Control, request vectors to nearest suitable base for recovery.",
|
"Control, request vectors to nearest suitable base for recovery.",
|
||||||
"Control, requesting nearest friendly airfield for landing, over.",
|
"Control, requesting nearest friendly airfield for landing, over.",
|
||||||
"Control, negative on original destination, request alternate field nearest current position.",
|
"Control, negative on original destination, request alternate field nearest current position.",
|
||||||
"Control, requesting location and frequency for closest towered airfield."
|
"Control, requesting location and frequency for closest towered airfield."
|
||||||
},
|
},
|
||||||
playerATCWeatherUpdate = { -- TODO: voiceover
|
playerATCWeatherUpdate = {
|
||||||
"Control, request latest weather update, over.",
|
"Control, request latest weather update, over.",
|
||||||
"Control, need current weather and visibility.",
|
"Control, need current weather and visibility.",
|
||||||
"Control, what's the weather looking like out there?",
|
"Control, what's the weather looking like out there?",
|
||||||
@ -330,7 +331,6 @@ Library.radioMessages = {
|
|||||||
"Command, mission timeline check, are we on schedule?"
|
"Command, mission timeline check, are we on schedule?"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
playerCommandRequireObjectives = {
|
playerCommandRequireObjectives = {
|
||||||
"Command, request objective $1 coordinates, over.",
|
"Command, request objective $1 coordinates, over.",
|
||||||
"Command, send me grid for objective $1.",
|
"Command, send me grid for objective $1.",
|
||||||
|
|||||||
@ -9,35 +9,38 @@ TUM.VERSION_STRING = "0.1.250722"
|
|||||||
|
|
||||||
TUM.DEBUG_MODE = __DEBUG_MODE__
|
TUM.DEBUG_MODE = __DEBUG_MODE__
|
||||||
|
|
||||||
TUM.logLevel = {
|
|
||||||
INFO = 0,
|
|
||||||
WARNING = 1,
|
|
||||||
ERROR = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
-- Prints and logs a debug message
|
-- Prints and logs a debug message
|
||||||
-- @param message The message
|
-- @param message The message
|
||||||
-- @param logLevel Is it a warning, error or info messages (as defined in TUM.logLevel). Info messages are not printed out unless debug mode is enabled.
|
-- @param logLevel Is it a warning, error or info messages (as defined in TUM.logger.logLevel). Info messages are not printed out unless debug mode is enabled.
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
function TUM.log(message, logLevel)
|
function TUM.log(message, logLevel)
|
||||||
logLevel = logLevel or TUM.logLevel.INFO
|
logLevel = logLevel or TUM.logger.logLevel.INFO
|
||||||
|
TUM.logger.print(logLevel, message)
|
||||||
if logLevel == TUM.logLevel.ERROR then
|
|
||||||
trigger.action.outText("ERROR: "..message, 3600)
|
|
||||||
env.warning("TUM - ERROR: "..message, false)
|
|
||||||
elseif logLevel == TUM.logLevel.WARNING then
|
|
||||||
trigger.action.outText("WARNING: "..message, 10)
|
|
||||||
env.warning("TUM - WARNING: "..message, false)
|
|
||||||
else
|
|
||||||
if TUM.DEBUG_MODE then -- Info messages are only printed out if debug mode is enabled
|
|
||||||
trigger.action.outText(message, 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
env.info("TUM: "..message, false)
|
|
||||||
end
|
|
||||||
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]]--
|
--[[DCS EXTENSIONS]]--
|
||||||
|
|
||||||
--[[LIBRARY]]--
|
--[[LIBRARY]]--
|
||||||
@ -48,118 +51,128 @@ end
|
|||||||
-- Module startup --
|
-- Module startup --
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
do
|
function TUM.initialize()
|
||||||
local function startUpMission()
|
do
|
||||||
TUM.hasStarted = false
|
TUM.administrativeSettings.onStartUp() -- load the administrative settings
|
||||||
|
|
||||||
local coreSettings = {
|
local function startUpMission()
|
||||||
multiplayer = false
|
TUM.hasStarted = false
|
||||||
}
|
|
||||||
|
|
||||||
if not net or not net.dostring_in then
|
local coreSettings = {
|
||||||
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)
|
multiplayer = false
|
||||||
return nil
|
}
|
||||||
end
|
|
||||||
|
|
||||||
if #DCSEx.envMission.getPlayerGroups() == 0 then
|
if not net or not net.dostring_in then
|
||||||
TUM.log("No \"Player\" or \"Client\" aircraft slots have been found. Please fix this problem in the mission editor.", TUM.logLevel.ERROR)
|
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.logger.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
|
return nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
coreSettings.multiplayer = true
|
if #DCSEx.envMission.getPlayerGroups() == 0 then
|
||||||
|
TUM.log("No \"Client\" aircraft slots have been found. Please fix this problem in the mission editor.", TUM.logger.logLevel.ERROR)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if world:getPlayer() then
|
||||||
|
TUM.log("A \"Player\" aircraft slot has been found. The Universal Mission only uses \"Client\" slots, even for single-player missions. Please fix this problem in the mission editor.", TUM.logger.logLevel.ERROR)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
coreSettings.multiplayer = (#DCSEx.envMission.getPlayerGroups() > 1)
|
||||||
|
|
||||||
if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) == 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) == 0 then
|
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)
|
TUM.log("Neither BLUE nor RED coalitions have player slots. Please make sure one coalition has player slots in the mission editor.", TUM.logger.logLevel.ERROR)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) > 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) > 0 then
|
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)
|
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.logger.logLevel.ERROR)
|
||||||
return nil
|
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
|
end
|
||||||
|
|
||||||
if not TUM.territories.onStartUp() then return nil end
|
if not startUpMission() then
|
||||||
if not TUM.settings.onStartUp(coreSettings) then return nil end -- Must be called after TUM.territories.onStartUp()
|
trigger.action.outText("A critical error has happened, cannot start the mission.", 3600)
|
||||||
if not TUM.playerCareer.onStartUp() then return nil end
|
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
|
end
|
||||||
|
|
||||||
if not startUpMission() then
|
-------------------
|
||||||
trigger.action.outText("A critical error has happened, cannot start the mission.", 3600)
|
-- Event handler --
|
||||||
end
|
-------------------
|
||||||
end
|
do
|
||||||
|
local eventHandler = {}
|
||||||
|
|
||||||
-------------------
|
function eventHandler:onEvent(event)
|
||||||
-- Event handler --
|
if not event then return end -- No event
|
||||||
-------------------
|
|
||||||
do
|
|
||||||
local eventHandler = {}
|
|
||||||
|
|
||||||
function eventHandler:onEvent(event)
|
TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages will interrupt the "ambient" ones
|
||||||
if not event then return end -- No event
|
TUM.airForce.onEvent(event)
|
||||||
|
TUM.ambientWorld.onEvent(event)
|
||||||
TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages will interrupt the "ambient" ones
|
TUM.ambientWorld.onEvent(event)
|
||||||
TUM.ambientWorld.onEvent(event)
|
TUM.objectives.onEvent(event)
|
||||||
TUM.objectives.onEvent(event)
|
TUM.playerScore.onEvent(event)
|
||||||
TUM.playerScore.onEvent(event)
|
TUM.mission.onEvent(event)
|
||||||
TUM.mission.onEvent(event)
|
TUM.wingmen.onEvent(event)
|
||||||
TUM.wingmen.onEvent(event)
|
TUM.mizCleaner.onEvent(event) -- Must be last, can remove units which could cause bugs in other onEvent methods
|
||||||
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
|
|
||||||
end
|
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
|
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
|
||||||
|
|
||||||
|
TUM.supportAWACS.create()
|
||||||
|
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.logger.logLevel.INFO)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,32 @@
|
|||||||
TUM.atc = {}
|
TUM.atc = {}
|
||||||
|
|
||||||
do
|
do
|
||||||
|
local function getFlyTime(playerUnit, point3)
|
||||||
|
local point2 = DCSEx.math.vec3ToVec2(point3)
|
||||||
|
|
||||||
|
local velocity = playerUnit: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(point2, DCSEx.math.vec3ToVec2(playerUnit:getPoint()))
|
||||||
|
local timeInMinutes = math.max(1, math.floor(distance / (speed * 60)))
|
||||||
|
local eta = DCSEx.string.getTimeString(timer.getAbsTime() + timeInMinutes * 60)
|
||||||
|
if timeInMinutes > 600 then
|
||||||
|
return "More than ten hours of flight time at current airspeed\n"
|
||||||
|
elseif timeInMinutes > 120 then
|
||||||
|
return tostring(math.floor(timeInMinutes / 60)).." hours of flight time at current airspeed, ETA "..eta.."\n"
|
||||||
|
elseif timeInMinutes < 2 then
|
||||||
|
return "Less than 2 minutes of flight time at current airspeed, ETA "..eta.."\n"
|
||||||
|
else
|
||||||
|
return tostring(timeInMinutes).." minutes of flight time at current airspeed, ETA "..eta.."\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTemperatureCelsiusAndFarenheit(temperature, inKelvins)
|
||||||
|
inKelvins = inKelvins or false
|
||||||
|
if inKelvins then temperature = temperature - 273.15 end
|
||||||
|
|
||||||
|
return tostring(math.floor(temperature)).."°C/"..tostring(math.floor(DCSEx.converter.celsiusToFahrenheit(temperature))).."°F"
|
||||||
|
end
|
||||||
|
|
||||||
function TUM.atc.requestNavAssistanceToObjective(index, delayRadioAnswer)
|
function TUM.atc.requestNavAssistanceToObjective(index, delayRadioAnswer)
|
||||||
local obj = TUM.objectives.getObjective(index)
|
local obj = TUM.objectives.getObjective(index)
|
||||||
if not obj then return end
|
if not obj then return end
|
||||||
@ -12,20 +38,7 @@ do
|
|||||||
for _,p in ipairs(players) do
|
for _,p in ipairs(players) do
|
||||||
-- Give BRA to objective
|
-- Give BRA to objective
|
||||||
local navInfo = "- Fly "..DCSEx.dcs.getBRAA(obj.waypoint3, p:getPoint(), false).."\n"
|
local navInfo = "- Fly "..DCSEx.dcs.getBRAA(obj.waypoint3, p:getPoint(), false).."\n"
|
||||||
|
navInfo = navInfo.."- "..getFlyTime(p, obj.waypoint3) -- Give flight time and ETA
|
||||||
-- 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
|
-- Give objective coordinates
|
||||||
if obj.preciseCoordinates then
|
if obj.preciseCoordinates then
|
||||||
@ -35,20 +48,20 @@ do
|
|||||||
end
|
end
|
||||||
navInfo = navInfo..DCSEx.world.getCoordinatesAsString(obj.waypoint3, false)
|
navInfo = navInfo..DCSEx.world.getCoordinatesAsString(obj.waypoint3, false)
|
||||||
|
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..msgIDSuffix, { obj.name, navInfo }, "Command", delayRadioAnswer)
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..msgIDSuffix, { obj.name, navInfo }, "Command", delayRadioAnswer, nil, nil, 2.5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function TUM.atc.requireNearestAirbase(delayRadioAnswer)
|
function TUM.atc.requestNavAssistanceToAirbase(delayRadioAnswer)
|
||||||
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
|
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
|
||||||
for _,p in ipairs(players) do
|
for _,p in ipairs(players) do
|
||||||
local airbaseInfo = "- No airbase available near you at the moment." -- TODO: proper "no airbase" message
|
local airbaseInfo = nil -- Mark as nil, in case no valid airbase is found
|
||||||
|
|
||||||
local validAirbaseTypes = { Airbase.Category.AIRDROME }
|
local validAirbaseTypes = { Airbase.Category.AIRDROME }
|
||||||
if p:hasAttribute("Helicopters") then table.insert(validAirbaseTypes, Airbase.Category.HELIPAD) end
|
if p:hasAttribute("Helicopters") then table.insert(validAirbaseTypes, Airbase.Category.HELIPAD) end
|
||||||
local pDesc = p:getDesc()
|
local pDesc = p:getDesc()
|
||||||
if pDesc.LandRWCategories and #pDesc.LandRWCategories > 0 then
|
if pDesc.LandRWCategories and #pDesc.LandRWCategories > 0 then
|
||||||
-- TODO: check player unit description to filter compatible carrier types
|
-- TODO: check player unit description to filter compatible carrier types (Harrier/helos can land anywhere, naval fighters can land on carriers, etc)
|
||||||
table.insert(validAirbaseTypes, Airbase.Category.SHIP)
|
table.insert(validAirbaseTypes, Airbase.Category.SHIP)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -57,32 +70,81 @@ do
|
|||||||
allAirbases = DCSEx.dcs.getNearestObjects(DCSEx.math.vec3ToVec2(p:getPoint()), allAirbases)
|
allAirbases = DCSEx.dcs.getNearestObjects(DCSEx.math.vec3ToVec2(p:getPoint()), allAirbases)
|
||||||
|
|
||||||
for i=1,#allAirbases do
|
for i=1,#allAirbases do
|
||||||
local abDesc = airbaseInfo[i]:getDesc()
|
local abDesc = allAirbases[i]:getDesc()
|
||||||
|
|
||||||
if DCSEx.table.contains(validAirbaseTypes, abDesc.category) then
|
if DCSEx.table.contains(validAirbaseTypes, abDesc.category) then
|
||||||
airbaseInfo = abDesc.displayName
|
local abPoint = allAirbases[i]:getPoint()
|
||||||
break
|
|
||||||
|
if abDesc.category == Airbase.Category.AIRDROME then
|
||||||
|
airbaseInfo = abDesc.displayName:upper().." AIRBASE:\n"
|
||||||
|
else -- Helipad or ship
|
||||||
|
airbaseInfo = abDesc.displayName:upper()..":\n"
|
||||||
|
end
|
||||||
|
airbaseInfo = airbaseInfo.."- Fly "..DCSEx.dcs.getBRAA(abPoint, p:getPoint(), false).."\n"
|
||||||
|
airbaseInfo = airbaseInfo.."- "..getFlyTime(p, abPoint).."\n"
|
||||||
|
airbaseInfo = airbaseInfo..DCSEx.world.getCoordinatesAsString(abPoint, false)
|
||||||
|
|
||||||
|
local runways = allAirbases[i]:getRunways()
|
||||||
|
if #runways > 0 then
|
||||||
|
airbaseInfo = airbaseInfo.."\n\nRunways: "
|
||||||
|
for j=1,#runways do
|
||||||
|
-- Compute the runway course (in degrees, divided by 10)
|
||||||
|
local courseDeg = math.floor(DCSEx.converter.radiansToDegrees(runways[j].course * -1))
|
||||||
|
if courseDeg < 0 then courseDeg = courseDeg + 360 end
|
||||||
|
if courseDeg >= 360 then courseDeg = courseDeg - 360 end
|
||||||
|
courseDeg = math.floor(courseDeg / 10)
|
||||||
|
|
||||||
|
-- Compute the opposite runway coursecourseDegNeg
|
||||||
|
local courseDegNeg = courseDeg + 18
|
||||||
|
if courseDegNeg >= 36 then courseDegNeg = courseDegNeg - 36 end
|
||||||
|
|
||||||
|
-- Make sure the lowest runway heading is displayed first
|
||||||
|
if courseDeg > courseDegNeg then
|
||||||
|
local tmp = courseDegNeg
|
||||||
|
courseDegNeg = courseDeg
|
||||||
|
courseDeg = tmp
|
||||||
|
end
|
||||||
|
|
||||||
|
airbaseInfo = airbaseInfo..tostring(courseDeg).."/"..tostring(courseDegNeg).." ("..tostring(math.floor(runways[j].length)).." m)"
|
||||||
|
if j < #runways then airbaseInfo = airbaseInfo..", " end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- TODO: radio tower frequency?
|
||||||
|
|
||||||
|
break -- Stop after finding one
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcRequireNearestAirbase", { airbaseInfo }, "Control", delayRadioAnswer)
|
if airbaseInfo then
|
||||||
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcRequireNearestAirbase", { airbaseInfo }, "Control", delayRadioAnswer, nil, false, 2.5)
|
||||||
|
else
|
||||||
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcRequireNearestAirbaseNone", nil, "Control", delayRadioAnswer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function TUM.atc.requestWeatherUpdate(delayRadioAnswer)
|
function TUM.atc.requestWeatherUpdate(delayRadioAnswer)
|
||||||
local weatherInfo = "- It is currenly "..DCSEx.string.getTimeString()
|
local commonWeatherInfo = "- It is currenly "..DCSEx.string.getTimeString()
|
||||||
if Library.environment.isItNightTime() then
|
if Library.environment.isItNightTime() then
|
||||||
weatherInfo = weatherInfo.." (night, sunrise at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, false))..")\n"
|
commonWeatherInfo = commonWeatherInfo.." (night, sunrise at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, false))..")\n"
|
||||||
else
|
else
|
||||||
weatherInfo = weatherInfo.." (day, sunset at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, true))..")\n"
|
commonWeatherInfo = commonWeatherInfo.." (day, sunset at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, true))..")\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
weatherInfo = weatherInfo.."- Average windspeed is "..tostring(DCSEx.floor(Library.environment.getWindAverage())).."m/s\n"
|
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())
|
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
|
||||||
for _,p in ipairs(players) do
|
for _,p in ipairs(players) do
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcWeatherUpdate", { weatherInfo }, "Control", delayRadioAnswer)
|
local lTemperature, _ = atmosphere.getTemperatureAndPressure(p:getPoint())
|
||||||
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
87
Script/The Universal Mission/AdministrativeSettings.lua
Normal file
87
Script/The Universal Mission/AdministrativeSettings.lua
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
-- ====================================================================================
|
||||||
|
-- TUM.ADMINISTRATIVESETTINGS - HANDLE ADMINISTRATIVE SETTINGS
|
||||||
|
-- ====================================================================================
|
||||||
|
-- (enum) TUM.administrativeSettings
|
||||||
|
-- TUM.administrativeSettingsDefaultValues
|
||||||
|
-- TUM.administrativeSettingsValues
|
||||||
|
-- TUM.administrativeSettings.getValue(key)
|
||||||
|
-- TUM.administrativeSettings.onStartUp()
|
||||||
|
-- TUM.administrativeSettings.setValue(key, value)
|
||||||
|
-- ====================================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
--- 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.onStartUp()
|
||||||
|
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
|
||||||
|
|
||||||
|
--- 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.logger.logLevel.ERROR)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
TUM.administrativeSettingsValues[key] = value
|
||||||
|
end
|
||||||
@ -6,9 +6,13 @@
|
|||||||
TUM.airForce = {}
|
TUM.airForce = {}
|
||||||
|
|
||||||
do
|
do
|
||||||
|
local SUPPRESSION_INCREASE_ON_KILL = 0.35 -- Value added to enemyCAPSuppression each time an enemy aircraft is shot down
|
||||||
|
|
||||||
local desiredUnitCount = { 4, 4 } -- Desired max number of aircraft in the air at any single time
|
local desiredUnitCount = { 4, 4 } -- Desired max number of aircraft in the air at any single time
|
||||||
local fighterGroups = { {}, {} }
|
local fighterGroups = { {}, {} }
|
||||||
|
|
||||||
|
local enemyCAPSuppression = 1
|
||||||
|
local enemyCAPSuppressionTimer = 1
|
||||||
local playerCenter = nil
|
local playerCenter = nil
|
||||||
|
|
||||||
local function getSkillLevel(side)
|
local function getSkillLevel(side)
|
||||||
@ -65,7 +69,7 @@ do
|
|||||||
local function launchNewAircraftGroup(side, airbases)
|
local function launchNewAircraftGroup(side, airbases)
|
||||||
local groupSize = DCSEx.table.getRandom({ 1, 2, 2, 2, 2, 3, 3, 4 })
|
local groupSize = DCSEx.table.getRandom({ 1, 2, 2, 2, 2, 3, 3, 4 })
|
||||||
groupSize = math.min(groupSize, desiredUnitCount[side] - getAirborneUnitCount(side))
|
groupSize = math.min(groupSize, desiredUnitCount[side] - getAirborneUnitCount(side))
|
||||||
if groupSize <= 0 then return false end
|
if groupSize <= 0 then return false end -- No aircraft slots left
|
||||||
|
|
||||||
local faction = TUM.settings.getEnemyFaction()
|
local faction = TUM.settings.getEnemyFaction()
|
||||||
if side == TUM.settings.getPlayerCoalition() then faction = TUM.settings.getPlayerFaction() end
|
if side == TUM.settings.getPlayerCoalition() then faction = TUM.settings.getPlayerFaction() end
|
||||||
@ -73,6 +77,17 @@ do
|
|||||||
local units = Library.factions.getUnits(faction, DCSEx.enums.unitFamily.PLANE_FIGHTER, groupSize, true)
|
local units = Library.factions.getUnits(faction, DCSEx.enums.unitFamily.PLANE_FIGHTER, groupSize, true)
|
||||||
if not units or #units == 0 then return false end -- No aircraft found
|
if not units or #units == 0 then return false end -- No aircraft found
|
||||||
|
|
||||||
|
-- If enemy CAP suppression timer > 0, decrement it by 1 but don't spawn any aircraft
|
||||||
|
if side == TUM.settings.getEnemyCoalition() then
|
||||||
|
enemyCAPSuppressionTimer = enemyCAPSuppressionTimer - 1
|
||||||
|
if enemyCAPSuppressionTimer > 0 then
|
||||||
|
TUM.log("Enemy CAP is still suppressed (suppression="..tostring(enemyCAPSuppressionTimer).."), no enemy CAP spawned.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
enemyCAPSuppressionTimer = enemyCAPSuppression
|
||||||
|
end
|
||||||
|
|
||||||
local launchAirbase = airbases[DCSEx.math.clamp(math.random(1, math.ceil(math.sqrt(#airbases))), 1, #airbases)]
|
local launchAirbase = airbases[DCSEx.math.clamp(math.random(1, math.ceil(math.sqrt(#airbases))), 1, #airbases)]
|
||||||
local originPt = DCSEx.math.vec3ToVec2(launchAirbase:getPoint())
|
local originPt = DCSEx.math.vec3ToVec2(launchAirbase:getPoint())
|
||||||
|
|
||||||
@ -150,7 +165,6 @@ do
|
|||||||
randomizeDesiredAircraftCount(side)
|
randomizeDesiredAircraftCount(side)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return launchNewAircraftGroup(side, airbases)
|
|
||||||
return launchNewAircraftGroup(side, validAirbases)
|
return launchNewAircraftGroup(side, validAirbases)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -170,6 +184,23 @@ do
|
|||||||
return updateAirForce(side)
|
return updateAirForce(side)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
-- Called when an event is raised
|
||||||
|
-- @param event The DCS World event
|
||||||
|
-------------------------------------
|
||||||
|
function TUM.airForce.onEvent(event)
|
||||||
|
if not event.initiator then return end
|
||||||
|
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end
|
||||||
|
if event.id ~= world.event.S_EVENT_UNIT_LOST then return end
|
||||||
|
|
||||||
|
local groupID = DCSEx.dcs.getGroupIDAsNumber(event.initiator:getGroup())
|
||||||
|
|
||||||
|
if DCSEx.table.contains(fighterGroups[TUM.settings.getEnemyCoalition()], groupID) then
|
||||||
|
enemyCAPSuppression = enemyCAPSuppression + SUPPRESSION_INCREASE_ON_KILL
|
||||||
|
TUM.log("Enemy CAP suppression increased to "..tostring(enemyCAPSuppression))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function TUM.airForce.create()
|
function TUM.airForce.create()
|
||||||
TUM.airForce.removeAll()
|
TUM.airForce.removeAll()
|
||||||
TUM.log("Creating friendly and enemy air forces...")
|
TUM.log("Creating friendly and enemy air forces...")
|
||||||
@ -190,6 +221,10 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Reset enemy CAP suppression
|
||||||
|
enemyCAPSuppression = 1
|
||||||
|
enemyCAPSuppressionTimer = 1
|
||||||
|
|
||||||
fighterGroups = { {}, {} }
|
fighterGroups = { {}, {} }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -225,11 +225,9 @@ do
|
|||||||
if not event.initiator then return end -- No event initiator
|
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 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 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"
|
local baseName = event.place:getName():upper()
|
||||||
if event.place then
|
|
||||||
baseName = event.place:getName():upper()
|
|
||||||
end
|
|
||||||
|
|
||||||
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) or not event.initiator:getPlayerName() then
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) or not event.initiator:getPlayerName() then
|
||||||
doAmbientChatter("atcSafeLanding", {event.initiator:getCallsign(), baseName}, baseName.." ATC", 1)
|
doAmbientChatter("atcSafeLanding", {event.initiator:getCallsign(), baseName}, baseName.." ATC", 1)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Called when a unit is destroyed
|
-- Called when an unit is destroyed
|
||||||
local function onEventDead(event)
|
local function onEventDead(event)
|
||||||
if not event.initiator then return end -- Nothing was hit
|
if not event.initiator then return end -- Nothing was hit
|
||||||
|
|
||||||
@ -53,6 +53,17 @@ do
|
|||||||
)
|
)
|
||||||
end
|
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()
|
function TUM.ambientWorld.removeAll()
|
||||||
for _,id in ipairs(groupIDs) do
|
for _,id in ipairs(groupIDs) do
|
||||||
DCSEx.world.destroyGroupByID(id)
|
DCSEx.world.destroyGroupByID(id)
|
||||||
@ -70,6 +81,8 @@ do
|
|||||||
|
|
||||||
if event.id == world.event.S_EVENT_DEAD then
|
if event.id == world.event.S_EVENT_DEAD then
|
||||||
onEventDead(event)
|
onEventDead(event)
|
||||||
|
elseif event.id == world.event.S_EVENT_HIT then
|
||||||
|
onEventHit(event)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -68,7 +68,10 @@ do
|
|||||||
local runwayTouchEvent = { id = world.event.S_EVENT_RUNWAY_TOUCH, initiator = playerUnit }
|
local runwayTouchEvent = { id = world.event.S_EVENT_RUNWAY_TOUCH, initiator = playerUnit }
|
||||||
TUM.onEvent(runwayTouchEvent)
|
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)
|
timer.scheduleFunction(TUM.onEvent, landingEvent, timer.getTime() + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -94,7 +97,7 @@ do
|
|||||||
function TUM.debugMenu.createMenu()
|
function TUM.debugMenu.createMenu()
|
||||||
if not TUM.DEBUG_MODE then return end
|
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 - BOOM map markers", rootMenu, doMarkersBoom, nil)
|
||||||
missionCommands.addCommand("Detonate - AIRBOOM map markers", rootMenu, doMarkersAirBoom, nil)
|
missionCommands.addCommand("Detonate - AIRBOOM map markers", rootMenu, doMarkersAirBoom, nil)
|
||||||
missionCommands.addCommand("Wingman - kill", rootMenu, doKillWingman, nil)
|
missionCommands.addCommand("Wingman - kill", rootMenu, doKillWingman, nil)
|
||||||
|
|||||||
@ -109,7 +109,7 @@ do
|
|||||||
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
||||||
realCount = realCount + 1
|
realCount = realCount + 1
|
||||||
else
|
else
|
||||||
TUM.log("Failed to add point air defense group near objective "..TUM.objectives.getObjective(i).name..".", TUM.logLevel.WARNING)
|
TUM.log("Failed to add point air defense group near objective "..TUM.objectives.getObjective(i).name..".", TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -134,7 +134,7 @@ do
|
|||||||
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
||||||
realCount = realCount + 1
|
realCount = realCount + 1
|
||||||
else
|
else
|
||||||
TUM.log("Failed to add local air defense group.", TUM.logLevel.WARNING)
|
TUM.log("Failed to add local air defense group.", TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ do
|
|||||||
if addAirDefenseGroup(side, faction, DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS, point) then
|
if addAirDefenseGroup(side, faction, DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS, point) then
|
||||||
realCount = realCount + 1
|
realCount = realCount + 1
|
||||||
else
|
else
|
||||||
TUM.log("Failed to add local MANPADS group.", TUM.logLevel.WARNING)
|
TUM.log("Failed to add local MANPADS group.", TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -10,31 +10,6 @@ TUM.intermission = {}
|
|||||||
do
|
do
|
||||||
local missionZonesMarkers = {}
|
local missionZonesMarkers = {}
|
||||||
|
|
||||||
local function doCommandStartMission()
|
|
||||||
local players = DCSEx.world.getAllPlayers()
|
|
||||||
|
|
||||||
if #players == 0 then
|
|
||||||
trigger.action.outText("No player slots occupied. At least one client slot must be occupied by a player to start the mission.", 5)
|
|
||||||
trigger.action.outSound("UI-Error.ogg")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then
|
|
||||||
for _,p in ipairs(players) do
|
|
||||||
if p:inAir() then
|
|
||||||
trigger.action.outText("Cannot start a single player mission while the player is in the air. Please land before starting the mission.", 5)
|
|
||||||
trigger.action.outSound("UI-Error.ogg")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
trigger.action.outText("Generating mission and loading assets, this can take some time...", 5)
|
|
||||||
|
|
||||||
-- 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)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function setSetting(args)
|
local function setSetting(args)
|
||||||
if not args.id or not args.value then return end
|
if not args.id or not args.value then return end
|
||||||
|
|
||||||
@ -74,6 +49,41 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TUM.intermission.doCommandStartMission(allowEvenInAir)
|
||||||
|
allowEvenInAir = allowEvenInAir or false
|
||||||
|
local players = DCSEx.world.getAllPlayers()
|
||||||
|
|
||||||
|
if #players == 0 then
|
||||||
|
trigger.action.outText("No player slots occupied. At least one client slot must be occupied by a player to start the mission.", 5)
|
||||||
|
trigger.action.outSound("UI-Error.ogg")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not allowEvenInAir and not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then
|
||||||
|
for _,p in ipairs(players) do
|
||||||
|
if p:inAir() then
|
||||||
|
trigger.action.outText("Cannot start a single player mission while the player is in the air. Please land before starting the mission.", 5)
|
||||||
|
trigger.action.outSound("UI-Error.ogg")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
end
|
||||||
|
|
||||||
function TUM.intermission.removeMissionZonesMarkers()
|
function TUM.intermission.removeMissionZonesMarkers()
|
||||||
for _,id in ipairs(missionZonesMarkers) do
|
for _,id in ipairs(missionZonesMarkers) do
|
||||||
trigger.action.removeMark(id)
|
trigger.action.removeMark(id)
|
||||||
@ -86,7 +96,7 @@ do
|
|||||||
-- Creates the mission briefing menu
|
-- Creates the mission briefing menu
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.intermission.createMenu()
|
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."
|
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)
|
DCSEx.envMission.setBriefing(coalition.side.RED, briefingText)
|
||||||
@ -94,9 +104,9 @@ do
|
|||||||
|
|
||||||
TUM.intermission.createMissionZonesMarkers() -- Show the available mission zones on the F10 map
|
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_BLUE, settingsMenu)
|
||||||
createSubMenu(TUM.settings.id.COALITION_RED, settingsMenu)
|
createSubMenu(TUM.settings.id.COALITION_RED, settingsMenu)
|
||||||
createSubMenu(TUM.settings.id.TASKING, settingsMenu)
|
createSubMenu(TUM.settings.id.TASKING, settingsMenu)
|
||||||
@ -107,7 +117,7 @@ do
|
|||||||
createSubMenu(TUM.settings.id.WINGMEN, settingsMenu)
|
createSubMenu(TUM.settings.id.WINGMEN, settingsMenu)
|
||||||
createSubMenu(TUM.settings.id.AI_CAP, settingsMenu)
|
createSubMenu(TUM.settings.id.AI_CAP, settingsMenu)
|
||||||
TUM.playerCareer.createMenu()
|
TUM.playerCareer.createMenu()
|
||||||
missionCommands.addCommand("➤ Begin mission", nil, doCommandStartMission, nil)
|
missionCommands.addCommand("➤ Begin mission", rootMenu, TUM.intermission.doCommandStartMission, false)
|
||||||
TUM.debugMenu.createMenu() -- Append debug menu to other menus (if debug mode enabled)
|
TUM.debugMenu.createMenu() -- Append debug menu to other menus (if debug mode enabled)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
140
Script/The Universal Mission/Logger.lua
Normal file
140
Script/The Universal Mission/Logger.lua
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
-- ====================================================================================
|
||||||
|
-- TUM.LOGGER - LOGS WARNINGS, ERRORS AND DEBUG INFO
|
||||||
|
-- ====================================================================================
|
||||||
|
-- (enum) TUM.logger.
|
||||||
|
-- TUM.logger.debug(text, ...)
|
||||||
|
-- TUM.logger.error(text, ...)
|
||||||
|
-- TUM.logger.formatText(text, ...)
|
||||||
|
-- TUM.logger.info(text, ...)
|
||||||
|
-- TUM.logger.print(level, text)
|
||||||
|
-- TUM.logger.splitText(text)
|
||||||
|
-- TUM.logger.trace(text, ...)
|
||||||
|
-- TUM.logger.warn(text, ...)
|
||||||
|
-- ====================================================================================
|
||||||
|
|
||||||
|
TUM.logger = {}
|
||||||
|
|
||||||
|
TUM.logger.logLevel = {
|
||||||
|
TRACE = -2,
|
||||||
|
DEBUG = -1,
|
||||||
|
INFO = 0,
|
||||||
|
WARNING = 1,
|
||||||
|
ERROR = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function TUM.logger.debug(text, ...)
|
||||||
|
if TUM.DEBUG_MODE then
|
||||||
|
text = TUM.logger.formatText(text, arg)
|
||||||
|
TUM.logger.print(TUM.logger.logLevel.DEBUG, text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.error(text, ...)
|
||||||
|
text = TUM.logger.formatText(text, arg)
|
||||||
|
local mText = text
|
||||||
|
if debug and debug.traceback then
|
||||||
|
mText = mText .. "\n" .. debug.traceback()
|
||||||
|
end
|
||||||
|
TUM.logger.print(TUM.logger.logLevel.ERROR, mText)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.formatText(text, ...)
|
||||||
|
if not text then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
if type(text) ~= 'string' then
|
||||||
|
text = TUM.p(text)
|
||||||
|
else
|
||||||
|
local args = ...
|
||||||
|
if args and args.n and args.n > 0 then
|
||||||
|
local pArgs = {}
|
||||||
|
for i=1,args.n do
|
||||||
|
pArgs[i] = TUM.p(args[i])
|
||||||
|
end
|
||||||
|
text = text:format(unpack(pArgs))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local fName = nil
|
||||||
|
local cLine = nil
|
||||||
|
if debug and debug.getinfo then
|
||||||
|
local dInfo = debug.getinfo(3)
|
||||||
|
fName = dInfo.name
|
||||||
|
cLine = dInfo.currentline
|
||||||
|
-- local fsrc = dinfo.short_src
|
||||||
|
--local fLine = dInfo.linedefined
|
||||||
|
end
|
||||||
|
if fName and cLine then
|
||||||
|
return fName .. '|' .. cLine .. ': ' .. text
|
||||||
|
elseif cLine then
|
||||||
|
return cLine .. ': ' .. text
|
||||||
|
else
|
||||||
|
return ' ' .. text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.info(text, ...)
|
||||||
|
text = TUM.logger.formatText(text, arg)
|
||||||
|
TUM.logger.print(TUM.logger.logLevel.INFO, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.print(level, text)
|
||||||
|
local texts = TUM.logger.splitText(text)
|
||||||
|
local levelChar = 'E'
|
||||||
|
local logFunction = function(messageForLogfile, messageForUser)
|
||||||
|
trigger.action.outText("ERROR: "..messageForUser, 3600)
|
||||||
|
env.error(messageForLogfile)
|
||||||
|
end
|
||||||
|
if level == TUM.logger.logLevel.WARNING then
|
||||||
|
levelChar = 'W'
|
||||||
|
logFunction = function(messageForLogfile, messageForUser)
|
||||||
|
trigger.action.outText("WARNING: "..messageForUser, 10)
|
||||||
|
env.warning(messageForLogfile)
|
||||||
|
end
|
||||||
|
elseif level == TUM.logger.logLevel.INFO then
|
||||||
|
levelChar = 'I'
|
||||||
|
logFunction = function(messageForLogfile, messageForUser)
|
||||||
|
if TUM.DEBUG_MODE then -- Info messages are only printed out if debug mode is enabled
|
||||||
|
trigger.action.outText(messageForUser, 3)
|
||||||
|
end
|
||||||
|
env.info(messageForLogfile)
|
||||||
|
end
|
||||||
|
elseif level == TUM.logger.logLevel.DEBUG then
|
||||||
|
levelChar = 'D'
|
||||||
|
logFunction = env.info
|
||||||
|
elseif level == TUM.logger.logLevel.TRACE then
|
||||||
|
levelChar = 'T'
|
||||||
|
logFunction = env.info
|
||||||
|
end
|
||||||
|
for i = 1, #texts do
|
||||||
|
if i == 1 then
|
||||||
|
local theText = 'TUM|' .. levelChar .. '|' .. texts[i]
|
||||||
|
logFunction(theText, texts[i])
|
||||||
|
else
|
||||||
|
local theText = texts[i]
|
||||||
|
logFunction(theText, theText)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.splitText(text)
|
||||||
|
local tbl = {}
|
||||||
|
while text:len() > 4000 do
|
||||||
|
local sub = text:sub(1, 4000)
|
||||||
|
text = text:sub(4001)
|
||||||
|
table.insert(tbl, sub)
|
||||||
|
end
|
||||||
|
table.insert(tbl, text)
|
||||||
|
return tbl
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.trace(text, ...)
|
||||||
|
if TUM.DEBUG_MODE then
|
||||||
|
text = TUM.logger.formatText(text, arg)
|
||||||
|
TUM.logger.print(TUM.logger.logLevel.TRACE, text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TUM.logger.warn(text, ...)
|
||||||
|
text = TUM.logger.formatText(text, arg)
|
||||||
|
TUM.logger.print(TUM.logger.logLevel.WARNING, text)
|
||||||
|
end
|
||||||
@ -62,17 +62,18 @@ do
|
|||||||
closeMission(true)
|
closeMission(true)
|
||||||
TUM.intermission.removeMissionZonesMarkers()
|
TUM.intermission.removeMissionZonesMarkers()
|
||||||
|
|
||||||
|
TUM.objectivesMaker.clear()
|
||||||
for _=1,TUM.settings.getValue(TUM.settings.id.TARGET_COUNT) do
|
for _=1,TUM.settings.getValue(TUM.settings.id.TARGET_COUNT) do
|
||||||
TUM.objectives.add()
|
TUM.objectives.add()
|
||||||
end
|
end
|
||||||
|
|
||||||
if TUM.objectives.getCount() == 0 then
|
if TUM.objectives.getCount() == 0 then
|
||||||
TUM.log("Couldn't create any objective, mission creation failed.", TUM.logLevel.WARNING)
|
TUM.log("Couldn't create any objective, mission creation failed.", TUM.logger.logLevel.WARNING)
|
||||||
closeMission(true)
|
closeMission(true)
|
||||||
return
|
return
|
||||||
end
|
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.enemyAirDefense.create() -- Must be called once objectives have been created
|
||||||
TUM.airForce.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
|
TUM.missionMenu.create() -- Must be called once objectives have been created
|
||||||
@ -107,7 +108,7 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
function TUM.mission.getPlayerCallsign()
|
function TUM.mission.getPlayerCallsign()
|
||||||
local player = world.getPlayer()
|
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||||
if player then return player:getCallsign() end
|
if player then return player:getCallsign() end
|
||||||
return "Flight"
|
return "Flight"
|
||||||
end
|
end
|
||||||
@ -193,10 +194,20 @@ do
|
|||||||
-- @param event The DCS World event
|
-- @param event The DCS World event
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.mission.onEvent(event)
|
function TUM.mission.onEvent(event)
|
||||||
if missionStatus == TUM.mission.status.NONE then return end
|
|
||||||
if not event.initiator then return end
|
if not event.initiator then return end
|
||||||
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end
|
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end -- Initiator is not an unit
|
||||||
if not event.initiator:getPlayerName() then return end
|
if not event.initiator:getPlayerName() then return end -- Initiator is not a player
|
||||||
|
|
||||||
|
-- Start mission (it wasn't started yet) when all players have taken off
|
||||||
|
if event.id == world.event.S_EVENT_TAKEOFF then
|
||||||
|
if missionStatus == TUM.mission.status.NONE and #DCSEx.world.getPlayersOnGround(TUM.settings.getPlayerCoalition()) == 0 then
|
||||||
|
TUM.intermission.doCommandStartMission(true)
|
||||||
|
-- Force wingman spawning because the "on player takeoff spawn wingman" function won't be called as mission wasn't started yet when the "take off" even was raised
|
||||||
|
timer.scheduleFunction(TUM.wingmen.create, nil, timer.getTime() + 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missionStatus == TUM.mission.status.NONE then return end
|
||||||
|
|
||||||
-- All objectives complete and all players on the ground? Mission is complete
|
-- All objectives complete and all players on the ground? Mission is complete
|
||||||
if event.id == world.event.S_EVENT_RUNWAY_TOUCH or event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT or event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then
|
if event.id == world.event.S_EVENT_RUNWAY_TOUCH or event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT or event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then
|
||||||
@ -205,11 +216,14 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end
|
-- When the player dies in single-player, remind them that they can respawn
|
||||||
|
-- (because no one knows the "respawn" shortcut key in DCS and it's not possible to respawn by
|
||||||
-- When player dies in single-player, fail the mission
|
-- changing slots when there's only one)
|
||||||
if event.id == world.event.S_EVENT_CRASH or event.id == world.event.S_EVENT_EJECTION or event.id == world.event.S_EVENT_PILOT_DEAD then
|
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then
|
||||||
TUM.mission.endMission(TUM.mission.endCause.FAILED)
|
if event.id == world.event.S_EVENT_CRASH or event.id == world.event.S_EVENT_EJECTION or event.id == world.event.S_EVENT_PILOT_DEAD then
|
||||||
|
-- TUM.mission.endMission(TUM.mission.endCause.FAILED)
|
||||||
|
trigger.action.outText("Your aircraft has been downed.\nPress Right CTRL+Right Shift+Tab (default) to respawn.", 10)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ do
|
|||||||
|
|
||||||
local function doCommandNearestAirbase()
|
local function doCommandNearestAirbase()
|
||||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCRequireNearestAirbase", nil, TUM.mission.getPlayerCallsign(), false)
|
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCRequireNearestAirbase", nil, TUM.mission.getPlayerCallsign(), false)
|
||||||
TUM.atc.requestNavAssistanceToAirbase(false)
|
TUM.atc.requestNavAssistanceToAirbase(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function doCommandObjectiveLocation(index)
|
local function doCommandObjectiveLocation(index)
|
||||||
@ -30,17 +30,17 @@ do
|
|||||||
|
|
||||||
local function doCommandWeatherUpdate()
|
local function doCommandWeatherUpdate()
|
||||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCWeatherUpdate", nil, TUM.mission.getPlayerCallsign(), false)
|
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCWeatherUpdate", nil, TUM.mission.getPlayerCallsign(), false)
|
||||||
TUM.atc.requestWeatherUpdate(false)
|
TUM.atc.requestWeatherUpdate(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
function TUM.missionMenu.create()
|
function TUM.missionMenu.create()
|
||||||
missionCommands.removeItem() -- Clear the menu
|
local rootMenu = TUM.getOrCreateRootMenu(true) -- Clear the menu
|
||||||
missionCommands.addCommand("☱ Mission status", nil, doCommandMissionStatus, nil)
|
missionCommands.addCommand("☱ Mission status", rootMenu, doCommandMissionStatus, nil)
|
||||||
|
|
||||||
local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives")
|
local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives", rootMenu)
|
||||||
local navigationMenuRoot = missionCommands.addSubMenu("➽ Navigation")
|
|
||||||
-- missionCommands.addCommand("Nav to nearest airbase", navigationMenuRoot, doCommandNearestAirbase, nil)
|
|
||||||
|
|
||||||
|
local navigationMenuRoot = missionCommands.addSubMenu("➽ Navigation", rootMenu)
|
||||||
|
missionCommands.addCommand("Nav to nearest airbase", navigationMenuRoot, doCommandNearestAirbase, nil)
|
||||||
for i=1,TUM.objectives.getCount() do
|
for i=1,TUM.objectives.getCount() do
|
||||||
local obj = TUM.objectives.getObjective(i)
|
local obj = TUM.objectives.getObjective(i)
|
||||||
if obj then
|
if obj then
|
||||||
@ -51,16 +51,16 @@ do
|
|||||||
missionCommands.addCommand("Nav to objective "..objNameAndDescription, navigationMenuRoot, doCommandObjectiveLocation, i)
|
missionCommands.addCommand("Nav to objective "..objNameAndDescription, navigationMenuRoot, doCommandObjectiveLocation, i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- missionCommands.addCommand("Weather update", navigationMenuRoot, doCommandWeatherUpdate, nil)
|
missionCommands.addCommand("Weather update", navigationMenuRoot, doCommandWeatherUpdate, nil)
|
||||||
|
|
||||||
TUM.wingmenMenu.create()
|
TUM.wingmenMenu.create()
|
||||||
TUM.supportAWACS.createMenu()
|
TUM.supportAWACS.createMenu()
|
||||||
|
|
||||||
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then -- If not multiplayer, add "show mission score" command
|
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
|
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
|
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)
|
missionCommands.addCommand("✓ Confirm (all xp since last landing will be lost!)", abortRoot, doCommandAbortMission, nil)
|
||||||
else
|
else
|
||||||
|
|||||||
@ -15,7 +15,6 @@ do
|
|||||||
-- @param event A DCS World event, possibly a S_EVENT_LAND event
|
-- @param event A DCS World event, possibly a S_EVENT_LAND event
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
local function removeAIAircraftOnLandEvent(event)
|
local function removeAIAircraftOnLandEvent(event)
|
||||||
if event.id ~= world.event.S_EVENT_LAND then return end
|
|
||||||
if not event.initiator 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 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
|
if event.initiator:getPlayerName() then return end -- Don't remove player aircraft, that would cause horrendous bugs
|
||||||
@ -55,7 +54,7 @@ do
|
|||||||
local u = DCSEx.world.getUnitByID(id)
|
local u = DCSEx.world.getUnitByID(id)
|
||||||
if u then u:destroy() end
|
if u then u:destroy() end
|
||||||
end
|
end
|
||||||
TUM.log("Removed "..tostring(#aiWingMenToRemove).." AI wingmen from the mission.\nPlease do not add AI wingmen to the mission, The Universal Mission uses its own wingman system.", TUM.logLevel.WARNING)
|
TUM.log("Removed "..tostring(#aiWingMenToRemove).." AI wingmen from the mission.\nPlease do not add AI wingmen to the mission, The Universal Mission relies on its own wingman system.", TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -73,6 +72,8 @@ do
|
|||||||
-- @param event The DCS World event
|
-- @param event The DCS World event
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.mizCleaner.onEvent(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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -25,7 +25,7 @@ do
|
|||||||
local objective = TUM.objectivesMaker.create()
|
local objective = TUM.objectivesMaker.create()
|
||||||
|
|
||||||
if not objective then
|
if not objective then
|
||||||
TUM.log("Failed to spawn a group for objective #"..tostring(#objectives + 1)..".", TUM.logLevel.WARNING)
|
TUM.log("Failed to spawn a group for objective #"..tostring(#objectives + 1)..".", TUM.logger.logLevel.WARNING)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -168,13 +168,44 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function onObjectiveEvent(index, event)
|
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 index < 1 or index > #objectives then return end -- Out of bounds
|
||||||
if objectives[index].completed then return end -- Objective already completed
|
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
|
local completionEvent = Library.tasks[objectives[index].taskID].completionEvent
|
||||||
if not event.initiator then return end
|
|
||||||
|
|
||||||
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 Object.getCategory(event.initiator) == Object.Category.SCENERY then
|
||||||
if DCSEx.math.isSamePoint(event.initiator:getPoint(), objectives[index].point3) then
|
if DCSEx.math.isSamePoint(event.initiator:getPoint(), objectives[index].point3) then
|
||||||
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)
|
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
TUM.objectivesMaker = {}
|
TUM.objectivesMaker = {}
|
||||||
|
|
||||||
do
|
do
|
||||||
|
local usedParkingSpots = {}
|
||||||
|
|
||||||
local function pickRandomTask()
|
local function pickRandomTask()
|
||||||
local taskFamily = TUM.settings.getValue(TUM.settings.id.TASKING)
|
local taskFamily = TUM.settings.getValue(TUM.settings.id.TASKING)
|
||||||
|
|
||||||
@ -41,24 +43,30 @@ do
|
|||||||
return possiblePoints[1]
|
return possiblePoints[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TUM.objectivesMaker.clear()
|
||||||
|
usedParkingSpots = {}
|
||||||
|
end
|
||||||
|
|
||||||
function TUM.objectivesMaker.create()
|
function TUM.objectivesMaker.create()
|
||||||
local zone = DCSEx.zones.getByName(TUM.settings.getValue(TUM.settings.id.TARGET_LOCATION, true))
|
local zone = DCSEx.zones.getByName(TUM.settings.getValue(TUM.settings.id.TARGET_LOCATION, true))
|
||||||
|
|
||||||
local taskID = pickRandomTask()
|
local taskID = pickRandomTask()
|
||||||
if not taskID then
|
if not taskID then
|
||||||
TUM.log("Failed to find a valid task.", TUM.logLevel.WARNING)
|
TUM.log("Failed to find a valid task.", TUM.logger.logLevel.WARNING)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local objectiveDB = Library.tasks[taskID]
|
local objectiveDB = Library.tasks[taskID]
|
||||||
|
|
||||||
|
local parkingInfo = nil
|
||||||
local spawnPoint2 = nil
|
local spawnPoint2 = nil
|
||||||
local spawnPoint3 = nil
|
local spawnPoint3 = nil
|
||||||
|
local isAirbaseTarget = false
|
||||||
local isSceneryTarget = false
|
local isSceneryTarget = false
|
||||||
|
|
||||||
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
|
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
|
||||||
local validSceneries = DCSEx.world.getSceneriesInZone(zone, DCSEx.zones.getRadius(zone), 250)
|
local validSceneries = DCSEx.world.getSceneriesInZone(zone, DCSEx.zones.getRadius(zone), 250)
|
||||||
if not validSceneries or #validSceneries == 0 then
|
if not validSceneries or #validSceneries == 0 then
|
||||||
TUM.log("Failed to find a valid scenery object to use as target.", TUM.logLevel.WARNING)
|
TUM.log("Failed to find a valid scenery object to use as target.", TUM.logger.logLevel.WARNING)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -66,6 +74,40 @@ do
|
|||||||
spawnPoint3 = DCSEx.table.deepCopy(pickedScenery:getPoint())
|
spawnPoint3 = DCSEx.table.deepCopy(pickedScenery:getPoint())
|
||||||
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
|
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
|
||||||
isSceneryTarget = true
|
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
|
elseif objectiveDB.surfaceType == land.SurfaceType.WATER then
|
||||||
spawnPoint2 = pickWaterPoint(zone)
|
spawnPoint2 = pickWaterPoint(zone)
|
||||||
if not spawnPoint2 then
|
if not spawnPoint2 then
|
||||||
@ -76,7 +118,7 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not spawnPoint2 then
|
if not spawnPoint2 then
|
||||||
TUM.log("Failed to find a spawn point for objective.", TUM.logLevel.WARNING)
|
TUM.log("Failed to find a spawn point for objective.", TUM.logger.logLevel.WARNING)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -89,10 +131,12 @@ do
|
|||||||
local objective = {
|
local objective = {
|
||||||
completed = false,
|
completed = false,
|
||||||
completedUnitsID = {},
|
completedUnitsID = {},
|
||||||
|
isAirbaseTarget = isAirbaseTarget,
|
||||||
isSceneryTarget = isSceneryTarget,
|
isSceneryTarget = isSceneryTarget,
|
||||||
markerID = DCSEx.world.getNextMarkerID(),
|
markerID = DCSEx.world.getNextMarkerID(),
|
||||||
markerTextID = DCSEx.world.getNextMarkerID(),
|
markerTextID = DCSEx.world.getNextMarkerID(),
|
||||||
name = Library.objectiveNames.get():upper(),
|
name = Library.objectiveNames.get():upper(),
|
||||||
|
parkingInfo = parkingInfo,
|
||||||
point2 = DCSEx.table.deepCopy(spawnPoint2),
|
point2 = DCSEx.table.deepCopy(spawnPoint2),
|
||||||
point3 = DCSEx.table.deepCopy(spawnPoint3),
|
point3 = DCSEx.table.deepCopy(spawnPoint3),
|
||||||
preciseCoordinates = objectiveDB.waypointInaccuracy <= 0,
|
preciseCoordinates = objectiveDB.waypointInaccuracy <= 0,
|
||||||
@ -108,7 +152,7 @@ do
|
|||||||
objective.waypoint3 = DCSEx.math.vec2ToVec3(objective.waypoint2, "land")
|
objective.waypoint3 = DCSEx.math.vec2ToVec3(objective.waypoint2, "land")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
|
if not isAirbaseTarget and not isSceneryTarget then
|
||||||
-- Check group options
|
-- Check group options
|
||||||
local groupOptions = {}
|
local groupOptions = {}
|
||||||
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.MOVING) then
|
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.MOVING) then
|
||||||
@ -123,7 +167,26 @@ do
|
|||||||
end
|
end
|
||||||
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
|
local groupInfo = nil
|
||||||
if objectiveDB.targetFamilies[1] == DCSEx.enums.unitFamily.STATIC_STRUCTURE then
|
if objectiveDB.targetFamilies[1] == DCSEx.enums.unitFamily.STATIC_STRUCTURE then
|
||||||
@ -132,11 +195,11 @@ do
|
|||||||
groupInfo.unitsID = { DCSEx.unitGroupMaker.createStatic(TUM.settings.getEnemyCoalition(), objective.point2, units[1], "") }
|
groupInfo.unitsID = { DCSEx.unitGroupMaker.createStatic(TUM.settings.getEnemyCoalition(), objective.point2, units[1], "") }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
groupInfo = DCSEx.unitGroupMaker.create(TUM.settings.getEnemyCoalition(), DCSEx.dcs.getUnitTypeFromFamily(objectiveDB.targetFamilies[1]), objective.point2, units, groupOptions)
|
groupInfo = DCSEx.unitGroupMaker.create(groupCoalition, DCSEx.dcs.getUnitCategoryFromFamily(objectiveDB.targetFamilies[1]), objective.point2, units, groupOptions)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not groupInfo then
|
if not groupInfo then
|
||||||
TUM.log("Failed to spawn a group for objective.", TUM.logLevel.WARNING)
|
TUM.log("Failed to spawn a group for objective.", TUM.logger.logLevel.WARNING)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
objective.groupID = groupInfo.groupID
|
objective.groupID = groupInfo.groupID
|
||||||
|
|||||||
@ -146,10 +146,11 @@ do
|
|||||||
-- Appends the career menu to the F10 menu. Only works in single-player missions
|
-- Appends the career menu to the F10 menu. Only works in single-player missions
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.playerCareer.createMenu()
|
function TUM.playerCareer.createMenu()
|
||||||
|
local rootMenu = TUM.getOrCreateRootMenu()
|
||||||
if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled
|
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
|
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
|
end
|
||||||
|
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
@ -273,7 +274,7 @@ do
|
|||||||
msg = msg.."To enable the IO module, comment or remove the \"sanitizeModule('io')\" line in \n"
|
msg = msg.."To enable the IO module, comment or remove the \"sanitizeModule('io')\" line in \n"
|
||||||
msg = msg.."[DCSWorld installation directory]\\Scripts\\MissionScripting.lua and restart the game."
|
msg = msg.."[DCSWorld installation directory]\\Scripts\\MissionScripting.lua and restart the game."
|
||||||
|
|
||||||
TUM.log(msg, TUM.logLevel.WARNING)
|
TUM.log(msg, TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -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
|
if not objectDesc or not objectDesc.attributes then return 10 end -- No description, assume a default value of 10 points
|
||||||
|
|
||||||
local groundMultiplier = 1
|
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
|
-- Misc
|
||||||
if objectDesc.attributes["Missiles"] then return 10 end
|
if objectDesc.attributes["Missiles"] then return 10 end
|
||||||
@ -68,8 +73,8 @@ do
|
|||||||
if objectDesc.attributes["Planes"] then return math.floor(25 * groundMultiplier) end
|
if objectDesc.attributes["Planes"] then return math.floor(25 * groundMultiplier) end
|
||||||
|
|
||||||
-- Rotary wing
|
-- Rotary wing
|
||||||
if objectDesc.attributes["Attack helicopters"] then return math.floor(30 * groundMultiplier) end
|
if objectDesc.attributes["Attack helicopters"] then return math.floor(25 * groundMultiplier) end
|
||||||
if objectDesc.attributes["Helicopters"] then return math.floor(25 * groundMultiplier) end
|
if objectDesc.attributes["Helicopters"] then return math.floor(15 * groundMultiplier) end
|
||||||
|
|
||||||
-- Default air
|
-- Default air
|
||||||
if objectDesc.attributes["Air"] then return math.floor(20 * groundMultiplier) end
|
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 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
|
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
|
local scoreMultiplier = 1.0
|
||||||
|
|
||||||
|
-- Add XP multipliers for game settings
|
||||||
for _,v in pairs(TUM.settings.id) do
|
for _,v in pairs(TUM.settings.id) do
|
||||||
scoreMultiplier = scoreMultiplier + (TUM.playerScore.getScoreMultiplier(v) or 0.0)
|
scoreMultiplier = scoreMultiplier + (TUM.playerScore.getScoreMultiplier(v) or 0.0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Add XP multipliers for weather
|
||||||
|
scoreMultiplier = scoreMultiplier + TUM.weather.getWeatherXPModifier()
|
||||||
|
scoreMultiplier = scoreMultiplier + TUM.weather.getWindXPModifier()
|
||||||
|
|
||||||
return math.max(0.0, scoreMultiplier)
|
return math.max(0.0, scoreMultiplier)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -335,7 +347,7 @@ do
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if event.id == world.event.S_EVENT_LAND then
|
if event.id == world.event.S_EVENT_LAND and event.place then
|
||||||
onLandEvent(event)
|
onLandEvent(event)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@ -50,7 +50,7 @@ do
|
|||||||
end
|
end
|
||||||
message = DCSEx.string.firstToUpper(message)
|
message = DCSEx.string.firstToUpper(message)
|
||||||
|
|
||||||
local duration = DCSEx.string.getReadingTime(message)
|
local duration = DCSEx.string.getReadingTime(message) * args.displayTimeMultiplier
|
||||||
|
|
||||||
-- Print message
|
-- Print message
|
||||||
trigger.action.outTextForUnit(args.unitID, callsign:upper()..": "..message, duration, false)
|
trigger.action.outTextForUnit(args.unitID, callsign:upper()..": "..message, duration, false)
|
||||||
@ -73,12 +73,13 @@ do
|
|||||||
-- @param delayed Should the message be delayed (used for message answers)
|
-- @param delayed Should the message be delayed (used for message answers)
|
||||||
-- @param functionToRun Function to run when the message is played
|
-- @param functionToRun Function to run when the message is played
|
||||||
-- @param functionParameters Parameters for the function to run when the message is played
|
-- @param functionParameters Parameters for the function to run when the message is played
|
||||||
|
-- @param displayTimeMultiplier Multiplier for how long the message should be displayed (2.0=twice as long, 0.5=half as long). Default is 1.0
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.radio.playForAll(messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
function TUM.radio.playForAll(messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
local players = DCSEx.world.getAllPlayers()
|
local players = DCSEx.world.getAllPlayers()
|
||||||
|
|
||||||
for _, unit in pairs(players) do
|
for _, unit in pairs(players) do
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(unit), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(unit), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -91,12 +92,13 @@ do
|
|||||||
-- @param delayed Should the message be delayed (used for message answers)
|
-- @param delayed Should the message be delayed (used for message answers)
|
||||||
-- @param functionToRun Function to run when the message is played
|
-- @param functionToRun Function to run when the message is played
|
||||||
-- @param functionParameters Parameters for the function to run when the message is played
|
-- @param functionParameters Parameters for the function to run when the message is played
|
||||||
|
-- @param displayTimeMultiplier Multiplier for how long the message should be displayed (2.0=twice as long, 0.5=half as long). Default is 1.0
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.radio.playForCoalition(coalitionID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
function TUM.radio.playForCoalition(coalitionID, messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
local players = coalition.getPlayers(coalitionID)
|
local players = coalition.getPlayers(coalitionID)
|
||||||
|
|
||||||
for _,u in pairs(players) do
|
for _,u in pairs(players) do
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -109,13 +111,14 @@ do
|
|||||||
-- @param delayed Should the message be delayed (used for message answers)
|
-- @param delayed Should the message be delayed (used for message answers)
|
||||||
-- @param functionToRun Function to run when the message is played
|
-- @param functionToRun Function to run when the message is played
|
||||||
-- @param functionParameters Parameters for the function to run when the message is played
|
-- @param functionParameters Parameters for the function to run when the message is played
|
||||||
|
-- @param displayTimeMultiplier Multiplier for how long the message should be displayed (2.0=twice as long, 0.5=half as long). Default is 1.0
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.radio.playForGroup(groupID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
function TUM.radio.playForGroup(groupID, messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
local group = DCSEx.world.getGroupByID(groupID)
|
local group = DCSEx.world.getGroupByID(groupID)
|
||||||
if not group then return end -- group does not exist
|
if not group then return end -- group does not exist
|
||||||
|
|
||||||
for _,u in pairs(group:getUnits()) do
|
for _,u in pairs(group:getUnits()) do
|
||||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -129,8 +132,9 @@ do
|
|||||||
-- @param delayed Should the message be delayed (used for message answers)
|
-- @param delayed Should the message be delayed (used for message answers)
|
||||||
-- @param functionToRun Function to run when the message is played
|
-- @param functionToRun Function to run when the message is played
|
||||||
-- @param functionParameters Parameters for the function to run when the message is played
|
-- @param functionParameters Parameters for the function to run when the message is played
|
||||||
|
-- @param displayTimeMultiplier Multiplier for how long the message should be displayed (2.0=twice as long, 0.5=half as long). Default is 1.0
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
function TUM.radio.playForUnit(unitID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
function TUM.radio.playForUnit(unitID, messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||||
if not messageID then return end
|
if not messageID then return end
|
||||||
if not Library.radioMessages[messageID] then return end
|
if not Library.radioMessages[messageID] then return end
|
||||||
delayed = delayed or false
|
delayed = delayed or false
|
||||||
@ -141,6 +145,7 @@ do
|
|||||||
|
|
||||||
local radioArgs = {
|
local radioArgs = {
|
||||||
callsign = callsign,
|
callsign = callsign,
|
||||||
|
displayTimeMultiplier = math.max(0.1, displayTimeMultiplier or 1.0),
|
||||||
functionToRun = functionToRun,
|
functionToRun = functionToRun,
|
||||||
functionParameters = functionParameters,
|
functionParameters = functionParameters,
|
||||||
messageID = messageID,
|
messageID = messageID,
|
||||||
|
|||||||
@ -52,7 +52,8 @@ do
|
|||||||
[TUM.settings.id.PLAYER_COALITION] = { "Red", "Blue" }, -- Must match values in the coalition.side enum
|
[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_COUNT] = { "1", "2", "3", "4" },
|
||||||
[TUM.settings.id.TARGET_LOCATION] = { },
|
[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.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" }
|
[TUM.settings.id.WINGMEN] = { "None", "1", "2", "3" }
|
||||||
}
|
}
|
||||||
@ -193,6 +194,10 @@ do
|
|||||||
end
|
end
|
||||||
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
|
if showScoreMultiplier then
|
||||||
summary = summary.."\n\nTotal XP modifier: "..tostring(math.ceil(TUM.playerScore.getTotalScoreMultiplier() * 100)).."%"
|
summary = summary.."\n\nTotal XP modifier: "..tostring(math.ceil(TUM.playerScore.getTotalScoreMultiplier() * 100)).."%"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -43,6 +43,15 @@ do
|
|||||||
end
|
end
|
||||||
end
|
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
|
-- No aircraft on picture
|
||||||
if #detectedAircraft == 0 then
|
if #detectedAircraft == 0 then
|
||||||
@ -91,8 +100,9 @@ do
|
|||||||
|
|
||||||
function TUM.supportAWACS.createMenu()
|
function TUM.supportAWACS.createMenu()
|
||||||
if not awacsGroupID then return end -- No AWACS
|
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("Bogey dope", rootPath, doCommandBogeyDope, nil)
|
||||||
missionCommands.addCommand("Picture", rootPath, doCommandPicture, nil)
|
missionCommands.addCommand("Picture", rootPath, doCommandPicture, nil)
|
||||||
end
|
end
|
||||||
@ -131,7 +141,7 @@ do
|
|||||||
end
|
end
|
||||||
TUM.log("Spawned AWACS aircraft")
|
TUM.log("Spawned AWACS aircraft")
|
||||||
else
|
else
|
||||||
TUM.log("Failed to create AWACS aircraft", TUM.logLevel.WARNING)
|
TUM.log("Failed to create AWACS aircraft", TUM.logger.logLevel.WARNING)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
TUM.log("No AWACS aircraft available")
|
TUM.log("No AWACS aircraft available")
|
||||||
|
|||||||
@ -60,7 +60,7 @@ do
|
|||||||
return
|
return
|
||||||
end
|
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 })
|
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacSmokeOK", { jtacName[index], smokeColorName }, jtacName[index], true, spawnSmoke, { point3 = obj.point3, smokeColor = smokeColor })
|
||||||
else
|
else
|
||||||
for _,id in ipairs(obj.unitsID) do
|
for _,id in ipairs(obj.unitsID) do
|
||||||
|
|||||||
@ -125,7 +125,27 @@ do
|
|||||||
elseif DCSEx.string.startsWith(z.name:lower(), "water") then
|
elseif DCSEx.string.startsWith(z.name:lower(), "water") then
|
||||||
table.insert(waterZones, z)
|
table.insert(waterZones, z)
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -135,18 +155,18 @@ do
|
|||||||
local zoneName = "BLUFOR"
|
local zoneName = "BLUFOR"
|
||||||
if side == 1 then zoneName = "REDFOR" end
|
if side == 1 then zoneName = "REDFOR" end
|
||||||
|
|
||||||
TUM.log("Coalition "..name.." has no territory zones and/or controls no airfields. Please add zone with a name starting with "..zoneName.." in the mission editor and make sure at least one contains an airbase.", TUM.logLevel.ERROR)
|
TUM.log("Coalition "..name.." has no territory zones and/or controls no airfields. Please add zone with a name starting with "..zoneName.." in the mission editor and make sure at least one contains an airbase.", TUM.logger.logLevel.ERROR)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #missionZones == 0 then
|
if #missionZones == 0 then
|
||||||
TUM.log("No mission zones found. Create at least one mission zone in the mission editor.", TUM.logLevel.ERROR)
|
TUM.log("No mission zones found. Create at least one mission zone in the mission editor.", TUM.logger.logLevel.ERROR)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if #missionZones > 10 then
|
if #missionZones > 10 then
|
||||||
TUM.log("Too many mission zones, extra zones removed.", TUM.logLevel.WARNING)
|
TUM.log("Too many mission zones, extra zones removed.", TUM.logger.logLevel.WARNING)
|
||||||
while #missionZones > 10 do
|
while #missionZones > 10 do
|
||||||
table.remove(missionZones, 11)
|
table.remove(missionZones, 11)
|
||||||
end
|
end
|
||||||
@ -157,10 +177,10 @@ do
|
|||||||
-- zones[coalition.side.RED] = DCSEx.zones.getByName("REDFOR")
|
-- zones[coalition.side.RED] = DCSEx.zones.getByName("REDFOR")
|
||||||
|
|
||||||
-- if not zones[coalition.side.BLUE] then
|
-- if not zones[coalition.side.BLUE] then
|
||||||
-- TUM.log("BLUFOR zone not found.", TUM.logLevel.ERROR)
|
-- TUM.log("BLUFOR zone not found.", TUM.logger.logLevel.ERROR)
|
||||||
-- return false
|
-- return false
|
||||||
-- elseif not zones[coalition.side.RED] then
|
-- elseif not zones[coalition.side.RED] then
|
||||||
-- TUM.log("REDFOR zone not found.", TUM.logLevel.ERROR)
|
-- TUM.log("REDFOR zone not found.", TUM.logger.logLevel.ERROR)
|
||||||
-- return false
|
-- return false
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
|
|||||||
322
Script/The Universal Mission/Weather.lua
Normal file
322
Script/The Universal Mission/Weather.lua
Normal 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
|
||||||
@ -23,11 +23,11 @@ do
|
|||||||
elseif taskingID == DCSEx.enums.taskFamily.GROUND_ATTACK then
|
elseif taskingID == DCSEx.enums.taskFamily.GROUND_ATTACK then
|
||||||
return "attack"
|
return "attack"
|
||||||
-- elseif taskingID == DCSEx.enums.taskFamily.HELICOPTER then
|
-- 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
|
elseif taskingID == DCSEx.enums.taskFamily.INTERCEPTION then
|
||||||
return "cap"
|
return "cap"
|
||||||
-- elseif taskingID == DCSEx.enums.taskFamily.OCA then
|
|
||||||
elseif taskingID == DCSEx.enums.taskFamily.SEAD then
|
elseif taskingID == DCSEx.enums.taskFamily.SEAD then
|
||||||
|
-- elseif taskingID == DCSEx.enums.taskFamily.OCA then
|
||||||
return "sead"
|
return "sead"
|
||||||
-- elseif taskingID == DCSEx.enums.taskFamily.STRIKE then
|
-- elseif taskingID == DCSEx.enums.taskFamily.STRIKE then
|
||||||
-- return "strike"
|
-- return "strike"
|
||||||
@ -48,7 +48,7 @@ do
|
|||||||
-- Retrive player unit type
|
-- Retrive player unit type
|
||||||
local playerTypeName = player:getTypeName()
|
local playerTypeName = player:getTypeName()
|
||||||
if not Library.aircraft[playerTypeName] then
|
if not Library.aircraft[playerTypeName] then
|
||||||
TUM.log("Cannot spawn AI wingmen, aircraft \""..playerTypeName.."\" not found in the database.", TUM.logLevel.WARNING)
|
TUM.log("Cannot spawn AI wingmen, aircraft \""..playerTypeName.."\" not found in the database.", TUM.logger.logLevel.WARNING)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local playerCategory = Group.Category.AIRPLANE
|
local playerCategory = Group.Category.AIRPLANE
|
||||||
@ -88,7 +88,7 @@ do
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not groupInfo then
|
if not groupInfo then
|
||||||
TUM.log("Failed to spawn AI wingmen", TUM.logLevel.WARNING)
|
TUM.log("Failed to spawn AI wingmen", TUM.logger.logLevel.WARNING)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
wingmenGroupID = groupInfo.groupID
|
wingmenGroupID = groupInfo.groupID
|
||||||
@ -177,9 +177,11 @@ do
|
|||||||
if not event.initiator:getPlayerName() then return end
|
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
|
if TUM.mission.getStatus() == TUM.mission.status.NONE then return end -- Mission not in progress, no wingman needed
|
||||||
TUM.wingmen.create()
|
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
|
if not event.initiator:getPlayerName() then return end
|
||||||
TUM.wingmen.removeAll()
|
TUM.wingmen.removeAll()
|
||||||
|
elseif event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then -- Remove wingmen when player takes control of a new unit
|
||||||
|
TUM.wingmen.removeAll()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -75,11 +75,12 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
function TUM.wingmenMenu.create()
|
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.MULTIPLAYER) then return end -- No wingmen in multiplayer
|
||||||
if TUM.settings.getValue(TUM.settings.id.WINGMEN) <= 1 then return end -- No wingmen
|
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 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)
|
missionCommands.addCommand("Cover me!", rootPath, radioCommandCoverMe, nil)
|
||||||
|
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
@ -137,7 +138,9 @@ do
|
|||||||
-- "Change altitude" submenu
|
-- "Change altitude" submenu
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
local altitudePath = missionCommands.addSubMenu("Change altitude", rootPath)
|
local altitudePath = missionCommands.addSubMenu("Change altitude", rootPath)
|
||||||
local baseAltitude = DCSEx.converter.metersToFeet(Library.aircraft[world.getPlayer():getTypeName()].altitude)
|
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||||
|
local baseAltitude = DCSEx.converter.metersToFeet(10000)
|
||||||
|
if player then baseAltitude = DCSEx.converter.metersToFeet(Library.aircraft[player:getTypeName()].altitude) end
|
||||||
local altitudeFactions = { 0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5 }
|
local altitudeFactions = { 0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5 }
|
||||||
for _,f in ipairs(altitudeFactions) do
|
for _,f in ipairs(altitudeFactions) do
|
||||||
local altText = DCSEx.string.toStringThousandsSeparator(math.floor((baseAltitude * f) / 100) * 100).."ft"
|
local altText = DCSEx.string.toStringThousandsSeparator(math.floor((baseAltitude * f) / 100) * 100).."ft"
|
||||||
|
|||||||
@ -29,7 +29,7 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function getAltitude()
|
local function getAltitude()
|
||||||
local player = world.getPlayer()
|
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||||
if not player then return 600 end -- Don't care about altitude if player's dead anyway
|
if not player then return 600 end -- Don't care about altitude if player's dead anyway
|
||||||
|
|
||||||
local altitude = Library.aircraft[player:getTypeName()].altitude * cruiseAltitudeFraction
|
local altitude = Library.aircraft[player:getTypeName()].altitude * cruiseAltitudeFraction
|
||||||
@ -133,10 +133,16 @@ do
|
|||||||
local function getRejoinTaskTable(formationDistance)
|
local function getRejoinTaskTable(formationDistance)
|
||||||
formationDistance = formationDistance or 800
|
formationDistance = formationDistance or 800
|
||||||
|
|
||||||
|
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||||
|
local groupID = 1
|
||||||
|
if player then
|
||||||
|
groupID = DCSEx.dcs.getObjectIDAsNumber(player:getGroup()) or 1
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id = "Follow",
|
id = "Follow",
|
||||||
params = {
|
params = {
|
||||||
groupId = DCSEx.dcs.getObjectIDAsNumber(world.getPlayer():getGroup()),
|
groupId = groupID,
|
||||||
lastWptIndexFlag = false,
|
lastWptIndexFlag = false,
|
||||||
lastWptIndex = -1,
|
lastWptIndex = -1,
|
||||||
pos = { x = -formationDistance, y = 0, z = -formationDistance }
|
pos = { x = -formationDistance, y = 0, z = -formationDistance }
|
||||||
|
|||||||
198
The Universal Mission - User's Manual.md
Normal file
198
The Universal Mission - User's Manual.md
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
<div style="text-align:center">
|
||||||
|
<img style="width:100%" src="docs/logo.png" alt="The Universal Mission for DCS World" />
|
||||||
|
<p class="heavy" style="font-size:175%;">
|
||||||
|
User's manual
|
||||||
|
</p>
|
||||||
|
<p class="heavy">
|
||||||
|
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">
|
||||||
|
<a href='https://github.com/akaAgar/the-universal-mission-for-dcs-world'>github.com/akaAgar/the-universal-mission-for-dcs-world</a><br />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<h2>Table of contents</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#page_welcome">Welcome to <em>The Universal Mission</em></a></li>
|
||||||
|
<li><a href="#page_howtoplay">How to use/play The Universal Mission?</a></li>
|
||||||
|
<li><a href="#page_menumission">Using the mission menu</a></li>
|
||||||
|
<li><a href="#page_advancedstuff">Advanced stuff you may want to try</a></li>
|
||||||
|
<li><a href="#page_multiplayer">A few notes regarding multiplayer</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<div id="page_welcome"></div>
|
||||||
|
<h2>Welcome to <em>The Universal Mission</em></h2>
|
||||||
|
|
||||||
|
_The Universal Mission for DCS World_ is an attempt to create a fully dynamic single-player/PvE mission giving access to the whole content of DCS World in a structure similar to the one found in old "simulators", like the early Microprose games (think F-117 or the Strike Eagle serie).
|
||||||
|
|
||||||
|
These game had both fun and clear objectives, endless replayability and a career system that made sure that something was at stake: crash and die, and you'll lose all these hard-earned medals.
|
||||||
|
|
||||||
|
As the original creator of [_Briefing Room for DCS World_](https://github.com/DCS-BR-Tools/briefing-room-for-dcs) (now maintained by the talented John Harvey), I've always wanted to create an easy-to-use, enticing and fun mission generator for DCS, capable of creating CPU-light missions without requiring an external program.
|
||||||
|
|
||||||
|
I think _The Universal Mission_ is, finally, the proper way to approach this problem. The current version is still an early beta but most core features are already working.
|
||||||
|
|
||||||
|
I hope you'll like it.
|
||||||
|
|
||||||
|
<h3>Features</h3>
|
||||||
|
|
||||||
|
- Can generate any kind of mission: ground attack, interception, strike, airbase attack, CAS, CAP, and more
|
||||||
|
- Completely dynamic, no two missions are ever the same
|
||||||
|
- Entirely self-contained inside a .miz file, no need for any external program
|
||||||
|
- More than 325 voiced radio messages for immersive and realistic coms
|
||||||
|
- Supports both single-player and small-scale PvE on closed servers
|
||||||
|
- Persistent single player career mode, with awards and promotions. Dying won't reset your progress, but you have to come back to base alive for your kills and completed objectives to be saved to your profile, so watch out for SAMs on your way home
|
||||||
|
- All new AI wingman system, smarter and more immersive than DCS's original wingmen
|
||||||
|
- Uses advanced DCS World scripting functionalities (like the brand new Disposition singleton and net.dostring_in hacks) to achieve effects seldom seen in other scripts, such as graphic overlays and random but realistic placement of units in cities and forests without the use of handmade spawn points
|
||||||
|
- Various little details to make the DCS World more alive, like crew running away from destroyed vehicles
|
||||||
|
|
||||||
|
<h3>Limitations of current beta version</h3>
|
||||||
|
|
||||||
|
- The current version supports only modern (post-Cold War) units and Caucasus, Kola, Marianas, Persian Gulf and Syria theaters
|
||||||
|
- Germany support will come soon, others will follow later
|
||||||
|
- Not all mission types are supported yet
|
||||||
|
- Career progress may be lost because of future updates, don't get too attached to it
|
||||||
|
|
||||||
|
<h3>Known bugs</h3>
|
||||||
|
- AWACS datalink info is now displayed on SA pages
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<div id="page_howtoplay"></div>
|
||||||
|
<h2>How to use/play The Universal Mission?</h2>
|
||||||
|
|
||||||
|
<h3>First setup</h3>
|
||||||
|
|
||||||
|
- Download the latest release from this GitHub page.
|
||||||
|
- Copy the provided autoexec.cfg file to your **[Saved Games]\DCS\Config directory**
|
||||||
|
- 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**
|
||||||
|
|
||||||
|
<h3>Customizing the mission to your taste</h3>
|
||||||
|
|
||||||
|
- _**(Optional but you'll probably want to do it)**_ Open the .miz file in the DCS World mission editor and change the player unit to pick your desired aircraft. The default player unit is a Su-25T (as it is the only free DCS airplane equipped with weapons) and you probably won't want to stick with it.
|
||||||
|
|
||||||
|
Please refer to the "Advanced stuff you may want to try" section to learn all the ways you can customize The Universal Mission.
|
||||||
|
|
||||||
|
<h3>Starting the mission</h3>
|
||||||
|
|
||||||
|
- Launch the mission from the mission editor or the "Mission" selection in the main DCS World menu
|
||||||
|
- You are now on the ramp or runway. Open the communication menu (see "Using the mission menu" below) and navigate to the F10/Other menu. From there, you can view and change mission settings. They include:
|
||||||
|
- Who belong to the blue and red coalitions
|
||||||
|
- The type of mission
|
||||||
|
- The number and location of targets (you can use the F10 map to see where the available target zones are located).
|
||||||
|
- The amount of enemy air force and air defense. The higher these settings, the more XP you'll recieve upon completion of a single-player mission
|
||||||
|
- When you're ready, pick the "Begin mission" option, wait a few seconds (precaching all the game assets can take some time, especially if you have a slow CPU), you're ready to go!
|
||||||
|
- Use the F10 mission and check the F10 map for additional information about the mission (see "Using the mission menu" below). Don't forget to come back to base alive, all awarded XP and completed objectives will only be saved to your pilot profile once you've landed
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<div id="page_menumission"></div>
|
||||||
|
<h2>Using the mission menu</h2>
|
||||||
|
|
||||||
|
Most features of The Universal Mission require the use of the "F10. Other" menu. To access it, press the "Communication menu" key (check the key bindings), navigate to the root menu by pressing F11 ("Previous menu") if need, then press "F10" to access the "Other" menu.
|
||||||
|
The exact content of the menu will depend on the current phase of the mission.
|
||||||
|
|
||||||
|
<h3>On startup/when no mission is active</h3>
|
||||||
|
|
||||||
|
- **Display mission settings**: Displays the current mission settings, that will be applied if you choose to start the mission now.
|
||||||
|
- **Change mission settings**: Allows you to change the mission settings to your taste.
|
||||||
|
- **Blue coalition**: Who is the blue coalition? Determines the type of units that will be spawned. Available factions (e.g. NATO) depend on the missions's time period and theater.
|
||||||
|
- **Red coalition**: Who is the red coalition? Determines the type of units that will be spawned. Available factions (e.g. USSR) depend on the missions's time period and theater.
|
||||||
|
- **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.
|
||||||
|
- Missions taking place in enemy territory award 30% more XP to account for increased SAM threat and proximity of enemy airbases.
|
||||||
|
- Make sure to pick a region not too far away from your starting location if you don't like long ingresses.
|
||||||
|
- Picking a region very close to your starting location (for instance, the one where your airbase is located in) can also be a bad idea, as you might takeoff in range of an enemy SAM.
|
||||||
|
- Be aware that targets of antiship strikes will always be spawned in open seas, which can be quite far if you picked a landlocked target zone.
|
||||||
|
- **Target count**: How many objectives will be spawned. More objectives means potentially more xp in a single sortie, so better medals, but also more work and more risk. Be aware that you can RTB to rearm/refuel at any time between objectives, but you won't accumulate as many single-sortie XP as if you complete objectives without going back to base, because XP is awarded to your profile and reset each time you land.
|
||||||
|
- **Enemy air defense**: Amount, quality and skill of enemy surface-to-air units (AAA, MANPADS and SAM). A higher setting awards more XP.
|
||||||
|
- **Enemy air force**: Amount, quality and skill of enemy combat air patrols. A higher setting awards more XP.
|
||||||
|
- **Wingmen count**: How many wingmen will fly by your side (from zero to three). A small XP penalty is added for each additional wingman. Wingman won't get replaced if they get shot during a mission, but they will (with full payload) each time you land and takeoff again. Only shown in single-player missions.
|
||||||
|
- **Friendly AI CAP**: Should AI fighter aicraft be spawned regularly to patrol the AO and shoot down potential threats? Disabling this option will award you more XP (only if "Enemy air force" is not set to "None") but also means you and your wingmen will be alone against the whole enemy air force.
|
||||||
|
- **View pilot career stats**: Displays a list of your achievements, as well as your medal case. Only available when playing single-player missions and if the Lua IO module has been unsanitized (see "First setup" above)
|
||||||
|
- **Begin mission**: Starts a mission with the current settings.
|
||||||
|
|
||||||
|
<h3>Other parameters</h3>
|
||||||
|
|
||||||
|
- _(Not yet implemented in this version)_ By changing the year in mission time parameters, the time period will be changed accordingly and the proper factions and AI units will be spawned during the mission. Time periods are:
|
||||||
|
- 1945 and before: World War 2
|
||||||
|
- 1946-1959: Korea War
|
||||||
|
- 1960-1974: Vietnam War
|
||||||
|
- 1975-1989: Late Cold War
|
||||||
|
- 1990-now: Modern
|
||||||
|
- _(Not yet implemented in this version)_ Changing the weather to make it more cloudy or windy, or setting the mission to nighttime, will make the mission more difficult but also award more points.
|
||||||
|
|
||||||
|
<h3>During the mission</h3>
|
||||||
|
|
||||||
|
- **Mission status**: Displays a summary of the mission's status (list of objectives and progress on each objective).
|
||||||
|
- **Objectives**: Displays a list of special commands related to each of the mission's objectives. Be aware that some objectives may have no special commands associated with them.
|
||||||
|
- **Smoke marker on target**: Asks for a friendly JTAC to pop a smoke marker on the target. Makes finding the target easier, but will cost you a small XP penalty. Only available for missions where a JTAC is available (it's pretty hard to throw a smoke grenade at an airplane or a ship in the middle of the sea).
|
||||||
|
- **Navigation**: Displays a list of commands related to navigational assistance.
|
||||||
|
- **Navigation to nearest airbase**: Displays the coordinates of the nearest friendly airbase, its BRA ("fly X for Y") relative to the player's position and an estimated flight time and ETA.
|
||||||
|
- **Navigation to objective [OBJECTIVE NAME]**: Displays the coordinates of the objective, its BRA ("fly X for Y") relative to the player's position and an estimated flight time and ETA. Some objectives types (e.g. strike missions) are provided with exact coordinates, but most will only have approximate coordiantes, so you'll have to search for targets yourself once in the objective area.
|
||||||
|
- **Weather update**: Displays information about the weather (wind speed, temperature...) at the player location.
|
||||||
|
- **Flight**: Displays a list of commands for your wingmen. Only shown in single-player missions and if wingmen are available for this mission.
|
||||||
|
- **Cover me!**: Tasks your wingmen to immediately engage any nearby air threats.
|
||||||
|
- **Engage**: Tasks your wingmen to engage a certain type of targets. Targets must be detected by your wingmen (see "Report contacts" below), or they won't be able to engage them.
|
||||||
|
- **Report contacts**: Asks your wingmen for a list of all detected contacts. According to range and sensors capabilities, their reports can go from perfect ID (e.g. "Su-27") to very generic descriptions (e.g. "fighter" or even "aircraft")
|
||||||
|
- **Hold position**: Tasks your wingmen to orbit at their current position. All other tasking will be aborted.
|
||||||
|
- **Change altitude**: Asks your wingmen to change their altitude. This altitude will be employed when attacking on orbiting but not when rejoining/forming up with you (in that case, they'll match your altitude).
|
||||||
|
- **Status report**: Asks your wingmen for a complete report (damage sustained, fuel status, available payload).
|
||||||
|
- **Rejoin**: Asks your wingmen to rejoin and follow you. All other tasking will be aborted. This is the default tasking when wingmen take off and when they complete another task.
|
||||||
|
- **AWACS**: Displays a list of commands for the AWACS. Only shown if an AWACS aircraft is available for this mission.
|
||||||
|
- **Bogey dope**: Asks for the nearest enemy air threat
|
||||||
|
- **Picture**: Asks for a summary of all detected enemy aircraft
|
||||||
|
- **Display mission score**: Displays the number of XP gained and objectives completed since your last takeoff. They will be added to your flight log (and any promotions/medals be awarded) the next time you land. If you crash, eject or abort the mission, all currently "stowed" XP and objectives will be lost. Only available when playing single-player missions and if the Lua IO module has been unsanitized (see "First setup" above)
|
||||||
|
- **Abort mission**: Aborts the current mission and forfeit all XP/objectives gained since last landing. The game will ask for confirmation so you don't select this option by mistake.
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<div id="page_advancedstuff"></div>
|
||||||
|
<h2>Advanced stuff you may want to try</h2>
|
||||||
|
|
||||||
|
The Universal Mission is designed to be easily editable to suit your preferences. Here are a few things you could do after opening the .miz file in DCS World's mission editor.
|
||||||
|
|
||||||
|
<h3>Player aircraft</h3>
|
||||||
|
|
||||||
|
- Change the player aircraft starting condition (runway, parking or parking hot). Air starts are not recommended as all players must be on the ground to begin a new mission
|
||||||
|
- Move it to another airbase, change its coalition (make sure blue players are spawned on an airbase located in a BLUFOR zone are red players are spawned on an airbase located in a REDFOR zone)
|
||||||
|
- You may also add an aircraft carrier or a FARP for the player to take off from
|
||||||
|
- Change its default loadout if you plan to play a specific kind of mission and don't want to lose time asking the ground crew to rearm your aircraft (e.g. if you know you want to play SEAD missions, you may as well stock up on AGM-88s)
|
||||||
|
- Add other aircraft to create a multiplayer mission to play with your friends. Keep in mind that the persistent career/player stats system will be disabled in multiplayer missions and that all player aircraft must belong to the same coalition (TUM does not support PvP)
|
||||||
|
|
||||||
|
<h3>Zones</h3>
|
||||||
|
|
||||||
|
- All zones whose names starts with BLUFOR or REDFOR decide the territory (and airbases) controlled by the blue and red coalitions
|
||||||
|
- Be aware that any change to the airbases coalitions will be superseded by the BLUFOR and REFOR zones
|
||||||
|
- All zones whose names starts with WATER are seas, used to spawn ships
|
||||||
|
- Zones with a name not starting with BLUFOR, REDFOR or WATER are target zones. These are zones where objectives can be spawned, who can be selected in the "objective location" setting of the intermission F10 menu
|
||||||
|
- Change, add or remove zones to create new possible target areas. A maximum of 10 target areas can be created, so they fit the F10 menu
|
||||||
|
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
<!------------------------- NEW PAGE ------------------------->
|
||||||
|
|
||||||
|
<div id="page_multiplayer"></div>
|
||||||
|
<h2>A few notes regarding multiplayer</h2>
|
||||||
|
|
||||||
|
While The Universal Mission supports multiplayer and is perfectely suitable (and fun!) for playing with friends on a private server, it is **absolutely not suited for public servers** as missions settings can be edited by anyone at any time Using the mission menu.
|
||||||
|
Please also note that PvP is not supported at the moment and that the mission will not launch if both coalitions have player slots.
|
||||||
159
Theaters/Germany.json
Normal file
159
Theaters/Germany.json
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
{
|
||||||
|
"displayName": "Germany Cold War",
|
||||||
|
|
||||||
|
"dcsID": "GermanyCW",
|
||||||
|
|
||||||
|
"mapCenter": [-185764.56195212, -700000],
|
||||||
|
"mapZoom": 825958.7020649,
|
||||||
|
|
||||||
|
"dateTime": {
|
||||||
|
"day": 1,
|
||||||
|
"month": 6,
|
||||||
|
"year": 2010,
|
||||||
|
"hour": 9,
|
||||||
|
"minute": 0
|
||||||
|
},
|
||||||
|
|
||||||
|
"temperature": 20,
|
||||||
|
|
||||||
|
"airbasesIDs": [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
||||||
|
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
|
||||||
|
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
|
||||||
|
95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
|
||||||
|
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
|
||||||
|
132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
|
||||||
|
150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167,
|
||||||
|
168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185,
|
||||||
|
186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
|
||||||
|
204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221,
|
||||||
|
222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
|
||||||
|
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
|
||||||
|
],
|
||||||
|
|
||||||
|
"bullseye": {
|
||||||
|
"blue": [-175892.0672249, -516165.19174041],
|
||||||
|
"red": [-263207.70144319, -729262.53687316]
|
||||||
|
},
|
||||||
|
|
||||||
|
"player": {
|
||||||
|
"airdromeID": 107,
|
||||||
|
"coordinates": [-213558.34375, -675577]
|
||||||
|
},
|
||||||
|
|
||||||
|
"territories": {
|
||||||
|
"blue": [
|
||||||
|
[
|
||||||
|
[-486980.39467552, -1075457.2271386],
|
||||||
|
[-118130.83715339, -637699.11504425],
|
||||||
|
[-34827.002345132, -609852.50737463],
|
||||||
|
[-55593.963997049, -1038879.0560472]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-236596.91384956, -663657.81710914],
|
||||||
|
[-141493.6690118, -590737.46312684],
|
||||||
|
[-105387.47432153, -632507.37463127],
|
||||||
|
[-409812.25308259, -1001828.9085546]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-570737.46312684, -715575.22123894],
|
||||||
|
[-343735.55691741, -749557.52212389],
|
||||||
|
[-227374.63126844, -748141.59292035],
|
||||||
|
[-493805.30973451, -1099056.0471976]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-448844.43584661, -767964.60176991],
|
||||||
|
[-314331.16151033, -711799.4100295],
|
||||||
|
[-266109.53472566, -672678.25972862],
|
||||||
|
[-248049.94676106, -785324.93965782]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"red": [
|
||||||
|
[
|
||||||
|
[-103952.80235988, -627315.63421829],
|
||||||
|
[-269616.51917404, -402418.87905605],
|
||||||
|
[23716.814159291, -412566.37168142],
|
||||||
|
[-27020.648967553, -604660.76696165]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-251209.43952802, -680176.99115044],
|
||||||
|
[-395870.20648968, -508849.55752212],
|
||||||
|
[-237758.1120944, -401946.90265487],
|
||||||
|
[-133687.31563422, -582241.8879056]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-409835.3584123, -745309.73451327],
|
||||||
|
[-428908.55457227, -607020.64896755],
|
||||||
|
[-295103.24483776, -493746.31268437],
|
||||||
|
[-260691.65833628, -668389.10758702]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"water": [
|
||||||
|
[
|
||||||
|
[-28849.557522124, -1063185.840708],
|
||||||
|
[-1474.9262536873, -772684.36578171],
|
||||||
|
[86312.684365782, -786371.68141593],
|
||||||
|
[31563.421828909, -1088436.5781711]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-18938.053097345, -545899.70501475],
|
||||||
|
[41002.949852507, -482182.89085546],
|
||||||
|
[24719.764011799, -537404.12979351],
|
||||||
|
[-17286.135693215, -612920.3539823]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[5840.7079646018, -553923.30383481],
|
||||||
|
[39351.032448378, -397463.12684366],
|
||||||
|
[68377.581120944, -395811.20943953],
|
||||||
|
[39587.020648968, -537404.12979351]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
"targetZones": {
|
||||||
|
"Berlin": [
|
||||||
|
[-229395.01555967, -531485.76918785],
|
||||||
|
[-259421.10835878, -447931.26582763],
|
||||||
|
[-200456.21860007, -435636.45902688],
|
||||||
|
[-188385.2142356, -489175.57499588]
|
||||||
|
],
|
||||||
|
"Fassberg": [
|
||||||
|
[-247191.58418879, -813059.75504425],
|
||||||
|
[-221545.92330383, -637203.79469027],
|
||||||
|
[-123542.8620649, -678649.01451327],
|
||||||
|
[-219485.11126844, -867327.80530973]
|
||||||
|
],
|
||||||
|
"Hamburg": [
|
||||||
|
[-126211.54521994, -771032.44837758],
|
||||||
|
[-80429.834305488, -609852.50737463],
|
||||||
|
[-3969.657314338, -644070.79646018],
|
||||||
|
[-104972.60716685, -806902.65486726]
|
||||||
|
],
|
||||||
|
"Leipzig": [
|
||||||
|
[-348235.75989842, -658154.69330028],
|
||||||
|
[-364451.04063155, -542876.3175586],
|
||||||
|
[-284030.66324113, -539864.77631412],
|
||||||
|
[-264392.49441197, -624589.44754851]
|
||||||
|
],
|
||||||
|
"Neubrandenburg": [
|
||||||
|
[-85867.341455633, -586901.83936957],
|
||||||
|
[-151457.6531, -426123.7738649],
|
||||||
|
[-33365.345967059, -405301.45270796],
|
||||||
|
[-198.36298135891, -516552.14003219]
|
||||||
|
],
|
||||||
|
"Ramstein": [
|
||||||
|
[-541345.29186423, -1007397.2292776],
|
||||||
|
[-536064.48212853, -833130.50799962],
|
||||||
|
[-371982.17962653, -887258.80779051],
|
||||||
|
[-404232.83908382, -1012112.2379702]
|
||||||
|
],
|
||||||
|
"Wiitstock": [
|
||||||
|
[-202970.05962823, -596596.27265076],
|
||||||
|
[-178043.23620461, -508945.97506878],
|
||||||
|
[-122770.71470005, -507184.84080515],
|
||||||
|
[-96489.172612102, -584810.22027111]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
docs/logo.png
Normal file
BIN
docs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
35
docs/style.css
Normal file
35
docs/style.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
font-family: Garamond, Georgia, "Times New Roman", Times, serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
background-color: dimgray;
|
||||||
|
color: white;
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
color: dimgray;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heavy {
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user