mirror of
https://github.com/akaAgar/the-universal-mission-for-dcs-world.git
synced 2026-09-03 09:20:57 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ec4598369 | |||
| e87222b715 |
@@ -11,8 +11,15 @@
|
||||
if not net then net = {} end
|
||||
|
||||
net.allow_unsafe_api = { -- this defines the secure zones where net.dostring_in() can be called from
|
||||
"userhooks",
|
||||
"scripting",
|
||||
"gui",
|
||||
}
|
||||
|
||||
net.allow_dostring_in = { -- and this defines the zones that should be addressed from net.dostring_in()
|
||||
"mission"
|
||||
"mission",
|
||||
"scripting",
|
||||
"gui",
|
||||
"export",
|
||||
"config",
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
-- TODO
|
||||
|
||||
-- [DCSEx.enums.unitFamily.GROUND_APC] = { "CHAP_FV107" },
|
||||
|
||||
|
||||
-- CHAP_IRISTSLM_CP, CHAP_IRISTSLM_LN, CHAP_IRISTSLM_STR
|
||||
@@ -24,14 +24,14 @@ do
|
||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*SA-2", "*SA-10" },
|
||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = { "*SA-10" },
|
||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = { "*SA-3", "*SA-6", "*SA-11" },
|
||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "2S6 Tunguska", "Osa 9A33 ln", "Tor 9A331", "CHAP_PantsirS1", "CHAP_TorM2" },
|
||||
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = { "2S6 Tunguska", "Osa 9A33 ln", "Tor 9A331" },
|
||||
[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", "CHAP_BMPT" },
|
||||
[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_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_ARTILLERY] = { "Grad-URAL", "SAU 2-C9", "SAU Akatsia", "SAU Gvozdika", "SAU Msta", "Smerch", "SpGH_Dana", "Uragan_BM-27" },
|
||||
[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_SS_MISSILE] = { "Scud_B", "CHAP_9K720_HE" },
|
||||
[DCSEx.enums.unitFamily.GROUND_SS_MISSILE] = { "Scud_B" },
|
||||
[DCSEx.enums.unitFamily.GROUND_UNARMED] = { "Ural-375", "Ural-4320 APA-5D", "Ural-4320T" },
|
||||
|
||||
[DCSEx.enums.unitFamily.HELICOPTER_ATTACK] = { "Ka-50", "Mi-24V", "Mi-28N" },
|
||||
@@ -52,7 +52,7 @@ do
|
||||
[DCSEx.enums.unitFamily.SHIP_CRUISER] = { "MOSCOW", "PIOTR" },
|
||||
[DCSEx.enums.unitFamily.SHIP_FRIGATE] = { "NEUSTRASH", "REZKY" },
|
||||
[DCSEx.enums.unitFamily.SHIP_LIGHT] = { "speedboat" },
|
||||
[DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "ALBATROS", "BDK-775", "MOLNIYA", "CHAP_Project22160_TorM2KM" },
|
||||
[DCSEx.enums.unitFamily.SHIP_MISSILE_BOAT] = { "ALBATROS", "BDK-775", "MOLNIYA" },
|
||||
[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" },
|
||||
|
||||
@@ -21,12 +21,12 @@ do
|
||||
[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_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_ARTILLERY] = { "M-109", "MLRS" },
|
||||
[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.GROUND_UNARMED] = { "Land_Rover_101_FC", "Land_Rover_109_S3", "M 818" },
|
||||
|
||||
[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" },
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
-- 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
|
||||
-- }
|
||||
@@ -1,22 +0,0 @@
|
||||
Library.tasks.ocaBomberStrike = {
|
||||
taskFamily = DCSEx.enums.taskFamily.OCA,
|
||||
description =
|
||||
{
|
||||
briefing = {
|
||||
""
|
||||
},
|
||||
short = "Destroy enemy bomber on the ramp",
|
||||
},
|
||||
conditions = {
|
||||
difficultyMinimum = 0,
|
||||
eras = {},
|
||||
},
|
||||
completionEvent = DCSEx.enums.taskEvent.DESTROY,
|
||||
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 },
|
||||
waypointInaccuracy = 0.0
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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.DESTROY,
|
||||
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
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -95,7 +95,7 @@ mission =
|
||||
},
|
||||
},
|
||||
["pictureFileNameN"] = {},
|
||||
["descriptionNeutralsTask"] = "",
|
||||
["descriptionNeutralsTask"] = "DictKey_descriptionNeutralsTask_4",
|
||||
["pictureFileNameServer"] = {},
|
||||
["weather"] =
|
||||
{
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
["alt"] = 13,
|
||||
["alt_type"] = "BARO",
|
||||
["livery_id"] = "default",
|
||||
["skill"] = "Client",
|
||||
["skill"] = "Player",
|
||||
["speed"] = 138.88888888889,
|
||||
["AddPropAircraft"] =
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
["alt"] = 22,
|
||||
["alt_type"] = "BARO",
|
||||
["livery_id"] = "default",
|
||||
["skill"] = "Client",
|
||||
["skill"] = "Player",
|
||||
["speed"] = 138.88888888889,
|
||||
["type"] = "Su-25T",
|
||||
["unitId"] = 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# The Universal Mission for DCS World
|
||||
|
||||
**Current version: open beta 0.3.250914** (see the "version history" section at the end of this file for a list of the latest changes)
|
||||
**Current version: open beta 0.2.250729** (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.**
|
||||
|
||||
@@ -11,7 +11,7 @@ The Universal Mission for DCS World is a fully dynamic single-player/PvE mission
|
||||
- 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
|
||||
- More than 300 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
|
||||
@@ -26,13 +26,14 @@ The Universal Mission for DCS World is a fully dynamic single-player/PvE mission
|
||||
|
||||
**Please read the "planned development" section below for more information.**
|
||||
|
||||
- The current version supports only modern (post-Cold War) units and Caucasus, Germany, Kola, Marianas, Persian Gulf and Syria theaters
|
||||
- The current version supports only modern (post-Cold War) units and Caucasus, Kola, Marianas, Persian Gulf and Syria theaters
|
||||
- Not all mission types are supported yet
|
||||
- Career progress may be lost because of future updates, don't get too attached to it
|
||||
|
||||
<h4>Known bugs in latest release</h4>
|
||||
|
||||
- AWACS datalink info not displayed properly on SA pages
|
||||
- AWACS datalink info is now displayed on SA pages
|
||||
- Some player callsigns may cause an error on mission start, stick to the "basic" DCS callsigns (Enfield, Springfield, Uzi, Colt, Dodge, Ford, Chevy and Pontiac) to avoid this problem
|
||||
|
||||
## How to use/play The Universal Mission?
|
||||
|
||||
@@ -71,27 +72,31 @@ Please also note that PvP is not supported at the moment and that the mission wi
|
||||
|
||||
## Planned development
|
||||
|
||||
<h3>Planned for next version (can be subject to change)</h3>
|
||||
<h3>Planned for next version (could be subject to change)</h3>
|
||||
|
||||
- Additional content
|
||||
- [ ] More objectives types
|
||||
- [x] Helicopter hunt
|
||||
- [ ] Offensive counter-air
|
||||
- [ ] At least partial Germany map support
|
||||
- AI improvements
|
||||
- [ ] AI wingmen should engage tracking radars first when told to engage SAM sites, in order to disable the site ASAP
|
||||
- Balance improvements
|
||||
- [ ] Lowered enemy CAP respawn rate
|
||||
- [ ] Tweaked XP requirements for medals/promotions
|
||||
- Bug fixes
|
||||
- [ ] AWACS datalinked contacts not showing on SA pages
|
||||
- [X] AWACS can now detect enemy helicopters
|
||||
- [x] Some player callsigns causing a script error at startup
|
||||
- Extras
|
||||
- [ ] GitHub page
|
||||
- Improvements
|
||||
- [ ] Better weather reports (cloud cover, rain, etc)
|
||||
- [x] First draft of the PDF manual
|
||||
- New features
|
||||
- [ ] Administrative settings menu
|
||||
- [ ] Friendly air defenses
|
||||
- [ ] Additional commands in the "navigation" menu
|
||||
- [ ] Vector to nearest airfield
|
||||
- [ ] Weather report
|
||||
- [ ] Use of "Client" slot instead of "Player" slot even in single-player missions, allowing the player to respawn on death/ejection instead of having to start the whole mission again
|
||||
- [ ] Single-player mission autostart on player take off
|
||||
- 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
|
||||
- [x] Increased AWACS aircraft spawn altitude
|
||||
- [ ] Target coordinates radio message displayed for a longer time so players have the time to write them down or enter them in their flight computer
|
||||
|
||||
### High priority
|
||||
|
||||
@@ -99,9 +104,12 @@ Please also note that PvP is not supported at the moment and that the mission wi
|
||||
- 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)
|
||||
- Friendly air defenses
|
||||
- GitHub page
|
||||
- Improved score multiplier taking into account various aspects of mission difficulty (weather, nighttime ops...)
|
||||
- Laser designation of targets by JTAC
|
||||
- New objectives: helicopter (drop/pickup units...), CAP, CAS, OCA (airbase attack)
|
||||
- PDF manual
|
||||
- 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)
|
||||
|
||||
@@ -157,29 +165,6 @@ The core script is quite simple and small, I probably won't need too much help w
|
||||
|
||||
## Version history
|
||||
|
||||
- **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)
|
||||
- **MAJOR CHANGE:** Added all new wingman system
|
||||
- Far for perfect but a lot better than DCS's default wingmen
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
-- ====================================================================================
|
||||
-- DCSEX.DCS - FUNCTIONS HANDLING DCS WORLD'S GAME RULES AND TABLES
|
||||
-- ====================================================================================
|
||||
-- DCSEx.dcs.doNothing()
|
||||
-- DCSEx.dcs.getBRAA(point, refPoint, showAltitude, metricSystem, casualFormat)
|
||||
-- DCSEx.dcs.getCJTFForCoalition(coalitionID)
|
||||
-- DCSEx.dcs.getCoalitionAsString(coalitionID)
|
||||
@@ -24,12 +23,6 @@
|
||||
|
||||
DCSEx.dcs = { }
|
||||
|
||||
-------------------------------------
|
||||
-- Does nothing. Used to create commands that do nothing in the F10 menu
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.doNothing()
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets a BRAA (bearing, range, altitude, aspect) string about a point
|
||||
-- Format is "[bearing to unit] for [distance] at [altitude]"
|
||||
|
||||
@@ -39,28 +39,26 @@ DCSEx.enums.taskEvent = {
|
||||
-------------------------------------
|
||||
DCSEx.enums.taskFamily = {
|
||||
ANTISHIP = 1,
|
||||
-- CAP = XXX,
|
||||
-- CAS = XXX,
|
||||
GROUND_ATTACK = 2,
|
||||
-- HELICOPTER = XXX,
|
||||
HELO_HUNT = 3,
|
||||
INTERCEPTION = 4,
|
||||
OCA = 5,
|
||||
SEAD = 6,
|
||||
STRIKE = 7,
|
||||
-- CAP = 2, -- TODO
|
||||
-- CAS = 3, -- TODO
|
||||
GROUND_ATTACK = 2, -- 4
|
||||
-- HELICOPTER = XXX, -- 5
|
||||
-- HELO_HUNT = XXX, -- 6
|
||||
INTERCEPTION = 3, -- 7
|
||||
-- OCA = XXX, -- 8
|
||||
SEAD = 4, --9
|
||||
STRIKE = 5, -- 10
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Special events for tasks
|
||||
-------------------------------------
|
||||
DCSEx.enums.taskFlag = {
|
||||
AIRBASE_TARGET = 1,
|
||||
ALLOW_JTAC = 2,
|
||||
DESTROY_TRACK_RADARS_ONLY = 3,
|
||||
MOVING = 4,
|
||||
ON_ROADS = 5,
|
||||
PARKED_AIRCRAFT_TARGET = 6,
|
||||
SCENERY_TARGET = 7,
|
||||
ALLOW_JTAC = 1,
|
||||
DESTROY_TRACK_RADARS_ONLY = 2,
|
||||
MOVING = 3,
|
||||
ON_ROADS = 4,
|
||||
SCENERY_TARGET = 5
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
@@ -71,7 +69,8 @@ DCSEx.enums.timePeriod = {
|
||||
KOREA_WAR = 2,
|
||||
VIETNAM_WAR = 3,
|
||||
COLD_WAR = 4,
|
||||
MODERN = 5
|
||||
GULF_WAR = 5,
|
||||
MODERN = 6
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
-- DCSEx.math.getBearing(point, refPoint, returnAsNESWstring)
|
||||
-- DCSEx.math.getDistance2D(vec2a, vec2b)
|
||||
-- DCSEx.math.getDistance3D(vec3a, vec3b)
|
||||
-- DCSEx.math.getLength3D(vec3)
|
||||
-- DCSEx.math.getRelativeHeading(point, refObject, format)
|
||||
-- DCSEx.math.getVec2FromAngle(angle)
|
||||
-- DCSEx.math.isPointInsideCircle(center, radius, vec2)
|
||||
@@ -120,16 +119,6 @@ function DCSEx.math.getDistance3D(vec3a, vec3b)
|
||||
return math.sqrt((vec3a.x - vec3b.x) ^ 2 + (vec3a.y - vec3b.y) ^ 2 + (vec3a.z - vec3b.z) ^ 2)
|
||||
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
|
||||
-------------------------------------
|
||||
|
||||
@@ -143,7 +143,7 @@ do
|
||||
|
||||
local currentCallsigns = {}
|
||||
for _,i in pairs(CALLSIGN_TYPE) do
|
||||
currentCallsigns[i] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
||||
currentCallsigns[i] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
||||
end
|
||||
|
||||
local function getCallsignTypeByUnitType(unitType)
|
||||
@@ -229,7 +229,7 @@ do
|
||||
for _,g in ipairs(missionGroups) do
|
||||
if g.units and g.units[1] then
|
||||
local unit = g.units[1]
|
||||
if unit.callsign and type(unit.callsign) == "table" and unit.callsign.name then
|
||||
if unit.callsign and unit.callsign[1] and unit.callsign.name then
|
||||
local callsignName = unit.callsign.name:sub(1, #unit.callsign.name - 2)
|
||||
incrementCallsign(callsignName, unit.callsign[2])
|
||||
end
|
||||
|
||||
@@ -374,14 +374,6 @@ do
|
||||
setAircraftTaskOrbit(groupTable, options)
|
||||
end
|
||||
|
||||
-- For parked aircraft
|
||||
if options.airbaseID and options.parkingID then
|
||||
groupTable.route.points[1].action = "From Parking Area"
|
||||
groupTable.route.points[1].airdromeId = options.airbaseID
|
||||
groupTable.route.points[1].type = "TakeOffParking"
|
||||
groupTable.uncontrolled = true
|
||||
end
|
||||
|
||||
if options.callsign then
|
||||
groupCallsign = options.callsign
|
||||
else
|
||||
@@ -457,12 +449,6 @@ do
|
||||
if aircraftDB.properties then
|
||||
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
|
||||
end
|
||||
|
||||
-- For parked aircraft
|
||||
if options.airbaseID and options.parkingID then
|
||||
unitTable.parking = tostring(options.parkingID)
|
||||
end
|
||||
|
||||
-- Setup datalink
|
||||
local datalinkString = tostring(dataLinkID)
|
||||
if #datalinkString == 3 then
|
||||
|
||||
+103
-215
@@ -2,27 +2,19 @@
|
||||
-- DCSEX.WORLD - FUNCTIONS RELATED TO THE GAME WORLD
|
||||
-- ====================================================================================
|
||||
-- DCSEx.world.collidesWithScenery(vec2, radius)
|
||||
-- DCSEx.world.destroyGroupByID(groupID)
|
||||
-- DCSEx.world.explodeUnit(unitID, amount)
|
||||
-- DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery)
|
||||
-- DCSEx.world.getAllPlayers()
|
||||
-- DCSEx.world.getAllSceneryBuildings(minHealth)
|
||||
-- DCSEx.world.getAllUnits(coalitionID, unitCategory)
|
||||
-- DCSEx.world.getAllUnits(unitCategory)
|
||||
-- DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||
-- DCSEx.world.getCoordinatesAsString(point, hideElevation)
|
||||
-- DCSEx.world.getCoordinatesAsString(point)
|
||||
-- DCSEx.world.getCurrentMarkerID()
|
||||
-- DCSEx.world.getFirstPlayer(side)
|
||||
-- DCSEx.world.getGroupByID(groupID)
|
||||
-- DCSEx.world.getGroupCenter(group)
|
||||
-- DCSEx.world.getMarkerByText(text, coalition)
|
||||
-- DCSEx.world.getNextMarkerID()
|
||||
-- DCSEx.world.getPlayersInAir(side)
|
||||
-- DCSEx.world.getPlayersOnGround(side)
|
||||
-- DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||
-- DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
||||
-- DCSEx.world.getStaticObjectByID(staticID)
|
||||
-- DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
||||
-- DCSEx.world.getUnitByID(unitID)
|
||||
-- DCSEx.world.getUnitsCenter(units)
|
||||
-- DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||
-- DCSEx.world.setUnitLifePercent(unitID, life)
|
||||
-- ====================================================================================
|
||||
@@ -33,16 +25,9 @@ do
|
||||
-- TODO: get max marker already in use from envMission
|
||||
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)
|
||||
radius = radius or 8
|
||||
local foundOne = false
|
||||
radius = radius or 8
|
||||
|
||||
local volS = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
@@ -62,30 +47,46 @@ do
|
||||
return foundOne
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-- function DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery, territorySide, expandSearch)
|
||||
-- expandSearch = expandSearch or true
|
||||
|
||||
-------------------------------------
|
||||
-- Spawns an explosion where an unit is located
|
||||
-------------------------------------
|
||||
-- @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
|
||||
-- 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
|
||||
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a table of all player-controlled units currently in the game
|
||||
-------------------------------------
|
||||
-- @return A table of unit objects
|
||||
-------------------------------------
|
||||
function DCSEx.world.getAllPlayers()
|
||||
@@ -103,8 +104,7 @@ do
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a table of all map scenery buildings.
|
||||
-- This function is rather CPU-heavy, better run it once on mission start and store the results in a table.
|
||||
-------------------------------------
|
||||
-- This function is rather CPU-consuming, better run it once on mission start and store the result in table.
|
||||
-- @param minHealth Minimum health a building must have to be included in the table
|
||||
-- @return A table of scenery objects
|
||||
-------------------------------------
|
||||
@@ -134,7 +134,6 @@ do
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all units belonging to a given category
|
||||
-------------------------------------
|
||||
-- @param coalitionID Coalition ID (coalition.side.XXX) or nil to search all coalitions
|
||||
-- @param unitCategory An unit category (Group.Category.XXX)
|
||||
-- @return A table of unit tables
|
||||
@@ -157,9 +156,7 @@ do
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the closest point to roads as a vec2.
|
||||
-- An alternative to ED's zany land.getClosestPointOnRoads which returns two integers (!!???)
|
||||
-------------------------------------
|
||||
-- Returns the closest point to roads as a vec2
|
||||
-- @param vec2 Coordinates to look for
|
||||
-- @return A vec2 with the closest point on roads
|
||||
-------------------------------------
|
||||
@@ -171,7 +168,6 @@ do
|
||||
-------------------------------------
|
||||
-- Returns the LL/MGRS coordinates of a point, as a string
|
||||
-- Based on code by Bushmanni - https://forums.eagle.ru/showthread.php?t=99480
|
||||
-------------------------------------
|
||||
-- @param point The point, as a vec2 or vec3
|
||||
-- @param hideElevation (optional) Show elevation NOT be displayed? Default: false
|
||||
-- @return A string
|
||||
@@ -226,7 +222,6 @@ do
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the last map marker ID generated by DCSEx.world.getNextMarkerID(), if any
|
||||
-------------------------------------
|
||||
-- @return A numeric ID, or nil
|
||||
-------------------------------------
|
||||
function DCSEx.world.getCurrentMarkerID()
|
||||
@@ -234,27 +229,8 @@ do
|
||||
return nextMarkerId - 1
|
||||
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
|
||||
-------------------------------------
|
||||
-- @param groupID ID of the group
|
||||
-- @return A group table, or nil if no group with this ID was found
|
||||
-------------------------------------
|
||||
@@ -270,13 +246,7 @@ do
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description
|
||||
function DCSEx.world.getMarkerByText(text, coalition)
|
||||
if not text then return nil end
|
||||
text = text:lower()
|
||||
@@ -286,7 +256,7 @@ do
|
||||
local markerText = m.text or ""
|
||||
markerText = markerText:lower()
|
||||
if markerText == text then
|
||||
if not coalition or m.coalition == coalition then
|
||||
if coalition == nil or m.coalition == coalition then
|
||||
return m
|
||||
end
|
||||
end
|
||||
@@ -296,8 +266,7 @@ do
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a new unique map marker ID
|
||||
-------------------------------------
|
||||
-- Returns a new, unique, map marker ID
|
||||
-- @return A numeric ID
|
||||
-------------------------------------
|
||||
function DCSEx.world.getNextMarkerID()
|
||||
@@ -305,12 +274,7 @@ do
|
||||
return nextMarkerId - 1
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description, file header
|
||||
function DCSEx.world.getPlayersInAir(side)
|
||||
local players = {}
|
||||
if side then
|
||||
@@ -329,38 +293,7 @@ do
|
||||
return playersInAir
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description, file header
|
||||
function DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
||||
safeRadius = safeRadius or 100
|
||||
|
||||
@@ -403,14 +336,7 @@ do
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description
|
||||
function DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||
minHealth = minHealth or 0
|
||||
local sceneries = {}
|
||||
@@ -436,12 +362,23 @@ do
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the maximum height difference in a given radius around a point
|
||||
-------------------------------------
|
||||
-- @param coord 2D point in which to search
|
||||
-- @param searchRadius Radius in meters
|
||||
-- @return A numeric value, in meters
|
||||
-- Searches and return a static object by its ID
|
||||
-- @param staticID ID of the static object
|
||||
-- @return An unit, or nil if no static object with this ID was found
|
||||
-------------------------------------
|
||||
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)
|
||||
local samples = {}
|
||||
searchRadius = searchRadius or 5
|
||||
@@ -466,37 +403,30 @@ do
|
||||
return tMax - tMin
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description, update file header
|
||||
function DCSEx.world.getGroupCenter(group)
|
||||
return DCSEx.world.getUnitsCenter(group:getUnits())
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Searches and return a coalition static object by its ID
|
||||
-------------------------------------
|
||||
-- @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)
|
||||
for coalitionID = 1,2 do
|
||||
for _,s in pairs(coalition.getStaticObjects(coalitionID)) do
|
||||
if DCSEx.dcs.getObjectIDAsNumber(s) == staticID then
|
||||
return s
|
||||
end
|
||||
end
|
||||
-- TODO: description, update file header
|
||||
function DCSEx.world.getUnitsCenter(units)
|
||||
if not units or #units == 0 then return { x = 0, y = 0 } 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
|
||||
|
||||
return nil
|
||||
center.x = center.x / #units
|
||||
center.y = center.y / #units
|
||||
|
||||
return center
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Searches and returns an unit by its ID
|
||||
-------------------------------------
|
||||
-- Searches and return an unit by its ID
|
||||
-- @param unitID ID of the unit
|
||||
-- @return An unit, or nil if no unit with this ID was found
|
||||
-------------------------------------
|
||||
@@ -516,34 +446,23 @@ do
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the 2D center of a number of units
|
||||
-- Searches and return a coalition static object by its ID
|
||||
-- @param unitID ID of the static object
|
||||
-- @return An static object, or nil if no unit with this ID was found
|
||||
-------------------------------------
|
||||
-- @param units A table of units
|
||||
-- @return The 2D point center of all units' positions or 0,0 if no units were found
|
||||
-------------------------------------
|
||||
function DCSEx.world.getUnitsCenter(units)
|
||||
if not units or #units == 0 then return { x = 0, y = 0 } 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
|
||||
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
|
||||
|
||||
center.x = center.x / #units
|
||||
center.y = center.y / #units
|
||||
|
||||
return center
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- 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
|
||||
-------------------------------------
|
||||
-- TODO: description
|
||||
function DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||
if not g then return false end
|
||||
if not g:isExist() then return false end
|
||||
@@ -565,16 +484,22 @@ do
|
||||
return atLeastOneActiveUnit
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Sets the health of an unit
|
||||
-------------------------------------
|
||||
-- @param unitID ID of the unit
|
||||
-- @param life Life percentage
|
||||
-------------------------------------
|
||||
-- TODO: description & file header
|
||||
function DCSEx.world.setUnitLifePercent(unitID, life)
|
||||
net.dostring_in("mission", string.format("a_unit_set_life_percentage(%d, %f)", unitID, life))
|
||||
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)
|
||||
-- destructionPercent = destructionPercent or 0
|
||||
-- net.dostring_in("mission", string.format("a_scenery_destruction_zone(%d, %f)", zone.zoneId, destructionPercent))
|
||||
@@ -593,41 +518,4 @@ do
|
||||
-- function DCSEx.world.shellingZone(zone, tnt, shellsCount)
|
||||
-- net.dostring_in("mission", string.format("a_shelling_zone(%d, %f, %d)", zone.zoneId, tnt, shellsCount))
|
||||
-- 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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-- DCSEX.ZONES - FUNCTIONS RELATED TO MAP TRIGGER ZONES
|
||||
-- ====================================================================================
|
||||
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
|
||||
-- DCSEx.zones.getAirbases(zone, coalition)
|
||||
-- DCSEx.zones.getAll()
|
||||
-- DCSEx.zones.getByName(name)
|
||||
-- DCSEx.zones.getCenter(zoneTable)
|
||||
@@ -73,30 +72,6 @@ function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawNa
|
||||
return markerID
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all airbases in the zone
|
||||
-------------------------------------
|
||||
-- @param zoneTable Table of the zone in which to search
|
||||
-- @param coalition Coalition (from the coalition.side enum) the airbase must belong to. Default is nil, which means "all coalitions"
|
||||
-- @return Table of airbases
|
||||
-------------------------------------
|
||||
function DCSEx.zones.getAirbases(zoneTable, coalition)
|
||||
coalition = coalition or nil
|
||||
|
||||
local coalitions = { coalition.side.RED, coalition.side.BLUE }
|
||||
if coalition then coalitions = { coalition } end
|
||||
|
||||
local validAirbases = {}
|
||||
for _,side in ipairs(coalitions) do
|
||||
for _,ab in ipairs(coalition.getAirbases(side)) do
|
||||
if DCSEx.zones.isPointInside(zoneTable, ab:getPoint()) then
|
||||
table.insert(validAirbases, ab)
|
||||
end
|
||||
end
|
||||
end
|
||||
return validAirbases
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all trigger zones
|
||||
-------------------------------------
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -201,17 +201,16 @@ Library.radioMessages = {
|
||||
"$1, off the deck, forming up now."
|
||||
},
|
||||
|
||||
atcRequireNearestAirbase = {
|
||||
"Roger. Vectoring you to the nearest airbase.\n\n$1",
|
||||
"Copy. Coordinates to nearest field inbound.\n\n$1",
|
||||
"Roger. Guide you direct to the nearest recovery airfield.\n\n$1"
|
||||
atcRequireNearestAirbase = { -- TODO: voiceover
|
||||
"Roger. Vectoring you to the nearest airbase.\n$1",
|
||||
"Copy. Coordinates to nearest field inbound.\n$1",
|
||||
"Roger. Guide you direct to the nearest recovery airfield.\n$1"
|
||||
},
|
||||
atcRequireNearestAirbaseNone = "No friendly airbase is available at the moment.",
|
||||
atcWeatherUpdate = {
|
||||
"Roger. Weather info coming up now.\n\n$1",
|
||||
"Copy. Weather report inbound.\n\n$1",
|
||||
"This is control, checking conditions now.\n\n$1",
|
||||
"Copy. Weather data on the way.\n\n$1"
|
||||
atcWeatherUpdate = { -- TODO: voiceover
|
||||
"Roger. Weather info coming up now.\n$1",
|
||||
"Copy. Weather report inbound.\n$1",
|
||||
"This is control, checking conditions now.\n$1",
|
||||
"Copy. Weather data on the way.\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." },
|
||||
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." },
|
||||
@@ -307,13 +306,13 @@ Library.radioMessages = {
|
||||
"$1, target already marked with smoke."
|
||||
},
|
||||
|
||||
playerATCRequireNearestAirbase = {
|
||||
playerATCRequireNearestAirbase = { -- TODO: voiceover
|
||||
"Control, request vectors to nearest suitable base for recovery.",
|
||||
"Control, requesting nearest friendly airfield for landing, over.",
|
||||
"Control, negative on original destination, request alternate field nearest current position.",
|
||||
"Control, requesting location and frequency for closest towered airfield."
|
||||
},
|
||||
playerATCWeatherUpdate = {
|
||||
playerATCWeatherUpdate = { -- TODO: voiceover
|
||||
"Control, request latest weather update, over.",
|
||||
"Control, need current weather and visibility.",
|
||||
"Control, what's the weather looking like out there?",
|
||||
@@ -331,6 +330,7 @@ Library.radioMessages = {
|
||||
"Command, mission timeline check, are we on schedule?"
|
||||
},
|
||||
|
||||
|
||||
playerCommandRequireObjectives = {
|
||||
"Command, request objective $1 coordinates, over.",
|
||||
"Command, send me grid for objective $1.",
|
||||
|
||||
+114
-125
@@ -9,37 +9,34 @@ TUM.VERSION_STRING = "0.1.250722"
|
||||
|
||||
TUM.DEBUG_MODE = __DEBUG_MODE__
|
||||
|
||||
TUM.logLevel = {
|
||||
INFO = 0,
|
||||
WARNING = 1,
|
||||
ERROR = 2
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Prints and logs a debug message
|
||||
-- @param message The message
|
||||
-- @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.
|
||||
-- @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.
|
||||
-------------------------------------
|
||||
|
||||
function TUM.log(message, logLevel)
|
||||
logLevel = logLevel or TUM.logger.logLevel.INFO
|
||||
TUM.logger.print(logLevel, message)
|
||||
end
|
||||
logLevel = logLevel or TUM.logLevel.INFO
|
||||
|
||||
--------------------------------------
|
||||
--- 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)
|
||||
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
|
||||
end
|
||||
return TUM.rootMenu
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
env.info("TUM: "..message, false)
|
||||
end
|
||||
end
|
||||
|
||||
--[[DCS EXTENSIONS]]--
|
||||
|
||||
@@ -51,126 +48,118 @@ end
|
||||
-- Module startup --
|
||||
--------------------
|
||||
|
||||
function TUM.initialize()
|
||||
do
|
||||
TUM.administrativeSettings.onStartUp() -- load the administrative settings
|
||||
do
|
||||
local function startUpMission()
|
||||
TUM.hasStarted = false
|
||||
|
||||
local function startUpMission()
|
||||
TUM.hasStarted = false
|
||||
local coreSettings = {
|
||||
multiplayer = false
|
||||
}
|
||||
|
||||
local coreSettings = {
|
||||
multiplayer = false
|
||||
}
|
||||
if not net or not net.dostring_in then
|
||||
TUM.log("Mission failed to execute. Please copy the provided \"autoexec.cfg\" file to the [Saved Games]\\DCS\\Config directory.\nThe file can be downloaded from github.com/akaAgar/the-universal-mission-for-dcs-world", TUM.logLevel.ERROR)
|
||||
return nil
|
||||
end
|
||||
|
||||
if not net or not net.dostring_in then
|
||||
TUM.log("Mission failed to execute. Please copy the provided \"autoexec.cfg\" file to the [Saved Games]\\DCS\\Config directory.\nThe file can be downloaded from github.com/akaAgar/the-universal-mission-for-dcs-world", TUM.logger.logLevel.ERROR)
|
||||
if #DCSEx.envMission.getPlayerGroups() == 0 then
|
||||
TUM.log("No \"Player\" or \"Client\" aircraft slots have been found. Please fix this problem in the mission editor.", TUM.logLevel.ERROR)
|
||||
return nil
|
||||
end
|
||||
|
||||
if world:getPlayer() then
|
||||
coreSettings.multiplayer = false
|
||||
|
||||
if #DCSEx.envMission.getPlayerGroups() > 1 then
|
||||
TUM.log("Multiple players slots have been found in addition to the single-player \"Player\" aircraft. Please fix this problem in the mission editor.", TUM.logLevel.ERROR)
|
||||
return nil
|
||||
end
|
||||
|
||||
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)
|
||||
else
|
||||
coreSettings.multiplayer = true
|
||||
|
||||
if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) == 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) == 0 then
|
||||
TUM.log("Neither BLUE nor RED coalitions have player slots. Please make sure one coalition has player slots in the mission editor.", TUM.logger.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.logLevel.ERROR)
|
||||
return nil
|
||||
end
|
||||
|
||||
if #DCSEx.envMission.getPlayerGroups(coalition.side.BLUE) > 0 and #DCSEx.envMission.getPlayerGroups(coalition.side.RED) > 0 then
|
||||
TUM.log("Both coalitions have player slots. The Universal Mission is a purely singleplayer/PvE experience and does not support PvP. Please make sure only one coalition has player slots in the mission editor.", TUM.logger.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.logLevel.ERROR)
|
||||
return nil
|
||||
end
|
||||
|
||||
if not TUM.territories.onStartUp() then return nil end
|
||||
if not TUM.settings.onStartUp(coreSettings) then return nil end -- Must be called after TUM.territories.onStartUp()
|
||||
if not TUM.playerCareer.onStartUp() then return nil end
|
||||
if not TUM.intermission.onStartUp() then return nil end
|
||||
if not TUM.airForce.onStartUp() then return nil end
|
||||
if not TUM.mizCleaner.onStartUp() then return nil end -- Must be called after TUM.settings.onStartUp()
|
||||
|
||||
TUM.hasStarted = true
|
||||
|
||||
return coreSettings
|
||||
end
|
||||
|
||||
if not startUpMission() then
|
||||
trigger.action.outText("A critical error has happened, cannot start the mission.", 3600)
|
||||
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
|
||||
|
||||
-------------------
|
||||
-- Event handler --
|
||||
-------------------
|
||||
do
|
||||
local eventHandler = {}
|
||||
|
||||
function eventHandler:onEvent(event)
|
||||
if not event then return end -- No event
|
||||
|
||||
TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages will interrupt the "ambient" ones
|
||||
TUM.airForce.onEvent(event)
|
||||
TUM.ambientWorld.onEvent(event)
|
||||
TUM.ambientWorld.onEvent(event)
|
||||
TUM.objectives.onEvent(event)
|
||||
TUM.playerScore.onEvent(event)
|
||||
TUM.mission.onEvent(event)
|
||||
TUM.wingmen.onEvent(event)
|
||||
TUM.mizCleaner.onEvent(event) -- Must be last, can remove units which could cause bugs in other onEvent methods
|
||||
end
|
||||
|
||||
function TUM.onEvent(event)
|
||||
eventHandler:onEvent(event)
|
||||
end
|
||||
|
||||
if TUM.hasStarted then
|
||||
world.addEventHandler(eventHandler)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------
|
||||
-- Game clock, called every 10-20 seconds --
|
||||
--------------------------------------------
|
||||
do
|
||||
local clockTick = -1
|
||||
|
||||
function TUM.onClockTick(arg, time)
|
||||
local nextTickTime = time + math.random(10, 20)
|
||||
clockTick = clockTick + 1
|
||||
|
||||
TUM.wingmenTasking.onClockTick() -- No need to check the function return, it's just here to check if wingmen target is still alive
|
||||
|
||||
if clockTick % 4 == 0 then
|
||||
if TUM.playerScore.onClockTick() then return nextTickTime end
|
||||
if TUM.mission.onClockTick() then return nextTickTime end
|
||||
elseif clockTick % 4 == 1 then
|
||||
if TUM.airForce.onClockTick(TUM.settings.getPlayerCoalition()) then return nextTickTime end
|
||||
elseif clockTick % 4 == 2 then
|
||||
if TUM.supportAWACS.onClockTick() then return nextTickTime end
|
||||
else
|
||||
if TUM.airForce.onClockTick(TUM.settings.getEnemyCoalition()) then return nextTickTime end
|
||||
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
|
||||
if not startUpMission() then
|
||||
trigger.action.outText("A critical error has happened, cannot start the mission.", 3600)
|
||||
end
|
||||
end
|
||||
|
||||
if TUM.administrativeSettings.getValue(TUM.administrativeSettings.INITIALIZE_AUTOMATICALLY) then
|
||||
TUM.initialize()
|
||||
else
|
||||
TUM.log("TUM has been loaded, but not initialized. Call TUM.initialize() to start the mission.", TUM.logger.logLevel.INFO)
|
||||
-------------------
|
||||
-- Event handler --
|
||||
-------------------
|
||||
do
|
||||
local eventHandler = {}
|
||||
|
||||
function eventHandler:onEvent(event)
|
||||
if not event then return end -- No event
|
||||
|
||||
TUM.ambientRadio.onEvent(event) -- Must be first so other (more important) radio messages will interrupt the "ambient" ones
|
||||
TUM.ambientWorld.onEvent(event)
|
||||
TUM.objectives.onEvent(event)
|
||||
TUM.playerScore.onEvent(event)
|
||||
TUM.mission.onEvent(event)
|
||||
TUM.wingmen.onEvent(event)
|
||||
TUM.mizCleaner.onEvent(event) -- Must be last, can remove units which could cause bugs in other onEvent methods
|
||||
end
|
||||
|
||||
function TUM.onEvent(event)
|
||||
eventHandler:onEvent(event)
|
||||
end
|
||||
|
||||
if TUM.hasStarted then
|
||||
world.addEventHandler(eventHandler)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------
|
||||
-- Game clock, called every 10-20 seconds --
|
||||
--------------------------------------------
|
||||
do
|
||||
local clockTick = -1
|
||||
|
||||
function TUM.onClockTick(arg, time)
|
||||
local nextTickTime = time + math.random(10, 20)
|
||||
clockTick = clockTick + 1
|
||||
|
||||
TUM.wingmenTasking.onClockTick() -- No need to check the function return, it's just here to check if wingmen target is still alive
|
||||
|
||||
if clockTick % 4 == 0 then
|
||||
if TUM.playerScore.onClockTick() then return nextTickTime end
|
||||
if TUM.mission.onClockTick() then return nextTickTime end
|
||||
elseif clockTick % 4 == 1 then
|
||||
if TUM.airForce.onClockTick(TUM.settings.getPlayerCoalition()) then return nextTickTime end
|
||||
elseif clockTick % 4 == 2 then
|
||||
if TUM.supportAWACS.onClockTick() then return nextTickTime end
|
||||
else
|
||||
if TUM.airForce.onClockTick(TUM.settings.getEnemyCoalition()) then return nextTickTime end
|
||||
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
|
||||
|
||||
@@ -1,32 +1,6 @@
|
||||
TUM.atc = {}
|
||||
|
||||
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)
|
||||
local obj = TUM.objectives.getObjective(index)
|
||||
if not obj then return end
|
||||
@@ -38,7 +12,20 @@ do
|
||||
for _,p in ipairs(players) do
|
||||
-- Give BRA to objective
|
||||
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
|
||||
if obj.preciseCoordinates then
|
||||
@@ -48,20 +35,20 @@ do
|
||||
end
|
||||
navInfo = navInfo..DCSEx.world.getCoordinatesAsString(obj.waypoint3, false)
|
||||
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..msgIDSuffix, { obj.name, navInfo }, "Command", delayRadioAnswer, nil, nil, 2.5)
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "commandObjectiveCoordinates"..msgIDSuffix, { obj.name, navInfo }, "Command", delayRadioAnswer)
|
||||
end
|
||||
end
|
||||
|
||||
function TUM.atc.requestNavAssistanceToAirbase(delayRadioAnswer)
|
||||
function TUM.atc.requireNearestAirbase(delayRadioAnswer)
|
||||
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
|
||||
for _,p in ipairs(players) do
|
||||
local airbaseInfo = nil -- Mark as nil, in case no valid airbase is found
|
||||
local airbaseInfo = "- No airbase available near you at the moment." -- TODO: proper "no airbase" message
|
||||
|
||||
local validAirbaseTypes = { Airbase.Category.AIRDROME }
|
||||
if p:hasAttribute("Helicopters") then table.insert(validAirbaseTypes, Airbase.Category.HELIPAD) end
|
||||
local pDesc = p:getDesc()
|
||||
if pDesc.LandRWCategories and #pDesc.LandRWCategories > 0 then
|
||||
-- TODO: check player unit description to filter compatible carrier types (Harrier/helos can land anywhere, naval fighters can land on carriers, etc)
|
||||
-- TODO: check player unit description to filter compatible carrier types
|
||||
table.insert(validAirbaseTypes, Airbase.Category.SHIP)
|
||||
end
|
||||
|
||||
@@ -70,78 +57,32 @@ do
|
||||
allAirbases = DCSEx.dcs.getNearestObjects(DCSEx.math.vec3ToVec2(p:getPoint()), allAirbases)
|
||||
|
||||
for i=1,#allAirbases do
|
||||
local abDesc = allAirbases[i]:getDesc()
|
||||
local abDesc = airbaseInfo[i]:getDesc()
|
||||
|
||||
if DCSEx.table.contains(validAirbaseTypes, abDesc.category) then
|
||||
local abPoint = allAirbases[i]:getPoint()
|
||||
|
||||
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
|
||||
airbaseInfo = abDesc.displayName
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcRequireNearestAirbase", { airbaseInfo }, "Control", delayRadioAnswer)
|
||||
end
|
||||
end
|
||||
|
||||
function TUM.atc.requestWeatherUpdate(delayRadioAnswer)
|
||||
local commonWeatherInfo = "- It is currenly "..DCSEx.string.getTimeString()
|
||||
local weatherInfo = "- It is currenly "..DCSEx.string.getTimeString()
|
||||
if Library.environment.isItNightTime() then
|
||||
commonWeatherInfo = commonWeatherInfo.." (night, sunrise at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, false))..")\n"
|
||||
weatherInfo = weatherInfo.." (night, sunrise at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, false))..")\n"
|
||||
else
|
||||
commonWeatherInfo = commonWeatherInfo.." (day, sunset at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, true))..")\n"
|
||||
weatherInfo = weatherInfo.." (day, sunset at "..DCSEx.string.getTimeString(Library.environment.getDayTime(nil, true))..")\n"
|
||||
end
|
||||
|
||||
weatherInfo = weatherInfo.."- Average windspeed is "..tostring(DCSEx.floor(Library.environment.getWindAverage())).."m/s\n"
|
||||
|
||||
local players = coalition.getPlayers(TUM.settings.getPlayerCoalition())
|
||||
for _,p in ipairs(players) do
|
||||
local lTemperature, _ = atmosphere.getTemperatureAndPressure(p:getPoint())
|
||||
|
||||
local localWeatherInfo = "- Average windspeed is "..tostring(math.floor(Library.environment.getWindAverage())).."m/s\n"
|
||||
localWeatherInfo = localWeatherInfo.."- Windspeed at your location is "..DCSEx.math.getLength3D(atmosphere.getWind(p:getPoint())).."m/s\n"
|
||||
localWeatherInfo = localWeatherInfo.."- Average ground-level temperature is "..getTemperatureCelsiusAndFarenheit(env.mission.weather.season.temperature).."\n"
|
||||
localWeatherInfo = localWeatherInfo.."- Temperature at your location is "..getTemperatureCelsiusAndFarenheit(lTemperature, true)
|
||||
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcWeatherUpdate", { commonWeatherInfo..localWeatherInfo }, "Control", delayRadioAnswer)
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(p), "atcWeatherUpdate", { weatherInfo }, "Control", delayRadioAnswer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
-- ====================================================================================
|
||||
-- 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,13 +6,9 @@
|
||||
TUM.airForce = {}
|
||||
|
||||
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 fighterGroups = { {}, {} }
|
||||
|
||||
local enemyCAPSuppression = 1
|
||||
local enemyCAPSuppressionTimer = 1
|
||||
local playerCenter = nil
|
||||
|
||||
local function getSkillLevel(side)
|
||||
@@ -69,7 +65,7 @@ do
|
||||
local function launchNewAircraftGroup(side, airbases)
|
||||
local groupSize = DCSEx.table.getRandom({ 1, 2, 2, 2, 2, 3, 3, 4 })
|
||||
groupSize = math.min(groupSize, desiredUnitCount[side] - getAirborneUnitCount(side))
|
||||
if groupSize <= 0 then return false end -- No aircraft slots left
|
||||
if groupSize <= 0 then return false end
|
||||
|
||||
local faction = TUM.settings.getEnemyFaction()
|
||||
if side == TUM.settings.getPlayerCoalition() then faction = TUM.settings.getPlayerFaction() end
|
||||
@@ -77,17 +73,6 @@ do
|
||||
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 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 originPt = DCSEx.math.vec3ToVec2(launchAirbase:getPoint())
|
||||
|
||||
@@ -165,6 +150,7 @@ do
|
||||
randomizeDesiredAircraftCount(side)
|
||||
end
|
||||
|
||||
-- return launchNewAircraftGroup(side, airbases)
|
||||
return launchNewAircraftGroup(side, validAirbases)
|
||||
end
|
||||
end
|
||||
@@ -184,23 +170,6 @@ do
|
||||
return updateAirForce(side)
|
||||
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()
|
||||
TUM.airForce.removeAll()
|
||||
TUM.log("Creating friendly and enemy air forces...")
|
||||
@@ -221,10 +190,6 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
-- Reset enemy CAP suppression
|
||||
enemyCAPSuppression = 1
|
||||
enemyCAPSuppressionTimer = 1
|
||||
|
||||
fighterGroups = { {}, {} }
|
||||
end
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ do
|
||||
function TUM.debugMenu.createMenu()
|
||||
if not TUM.DEBUG_MODE then return end
|
||||
|
||||
local rootMenu = missionCommands.addSubMenu("[DEBUG]", TUM.getOrCreateRootMenu())
|
||||
local rootMenu = missionCommands.addSubMenu("[DEBUG]")
|
||||
missionCommands.addCommand("Detonate - BOOM map markers", rootMenu, doMarkersBoom, nil)
|
||||
missionCommands.addCommand("Detonate - AIRBOOM map markers", rootMenu, doMarkersAirBoom, nil)
|
||||
missionCommands.addCommand("Wingman - kill", rootMenu, doKillWingman, nil)
|
||||
|
||||
@@ -109,7 +109,7 @@ do
|
||||
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
||||
realCount = realCount + 1
|
||||
else
|
||||
TUM.log("Failed to add point air defense group near objective "..TUM.objectives.getObjective(i).name..".", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to add point air defense group near objective "..TUM.objectives.getObjective(i).name..".", TUM.logLevel.WARNING)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -134,7 +134,7 @@ do
|
||||
if addAirDefenseGroup(side, faction, unitFamily, point) then
|
||||
realCount = realCount + 1
|
||||
else
|
||||
TUM.log("Failed to add local air defense group.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to add local air defense group.", TUM.logLevel.WARNING)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,7 +154,7 @@ do
|
||||
if addAirDefenseGroup(side, faction, DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS, point) then
|
||||
realCount = realCount + 1
|
||||
else
|
||||
TUM.log("Failed to add local MANPADS group.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to add local MANPADS group.", TUM.logLevel.WARNING)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,31 @@ TUM.intermission = {}
|
||||
do
|
||||
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)
|
||||
if not args.id or not args.value then return end
|
||||
|
||||
@@ -49,32 +74,6 @@ do
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
function TUM.intermission.removeMissionZonesMarkers()
|
||||
for _,id in ipairs(missionZonesMarkers) do
|
||||
trigger.action.removeMark(id)
|
||||
@@ -87,7 +86,7 @@ do
|
||||
-- Creates the mission briefing menu
|
||||
-------------------------------------
|
||||
function TUM.intermission.createMenu()
|
||||
local rootMenu = TUM.getOrCreateRootMenu(true) -- Clear the menu
|
||||
missionCommands.removeItem() -- Clear the menu
|
||||
|
||||
local briefingText = "Welcome to The Universal Mission for DCS World, a highly customizable mission available for single-player and PvE.\n\nOpen the communication menu and select the ''F10. Other'' option to access mission settings."
|
||||
DCSEx.envMission.setBriefing(coalition.side.RED, briefingText)
|
||||
@@ -95,9 +94,9 @@ do
|
||||
|
||||
TUM.intermission.createMissionZonesMarkers() -- Show the available mission zones on the F10 map
|
||||
|
||||
missionCommands.addCommand("ℹ Display mission settings", rootMenu, TUM.settings.printSettingsSummary, false)
|
||||
missionCommands.addCommand("ℹ Display mission settings", nil, TUM.settings.printSettingsSummary, false)
|
||||
|
||||
local settingsMenu = missionCommands.addSubMenu("✎ Change mission settings", rootMenu)
|
||||
local settingsMenu = missionCommands.addSubMenu("✎ Change mission settings")
|
||||
createSubMenu(TUM.settings.id.COALITION_BLUE, settingsMenu)
|
||||
createSubMenu(TUM.settings.id.COALITION_RED, settingsMenu)
|
||||
createSubMenu(TUM.settings.id.TASKING, settingsMenu)
|
||||
@@ -108,7 +107,7 @@ do
|
||||
createSubMenu(TUM.settings.id.WINGMEN, settingsMenu)
|
||||
createSubMenu(TUM.settings.id.AI_CAP, settingsMenu)
|
||||
TUM.playerCareer.createMenu()
|
||||
missionCommands.addCommand("➤ Begin mission", rootMenu, TUM.intermission.doCommandStartMission, false)
|
||||
missionCommands.addCommand("➤ Begin mission", nil, doCommandStartMission, nil)
|
||||
TUM.debugMenu.createMenu() -- Append debug menu to other menus (if debug mode enabled)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
-- ====================================================================================
|
||||
-- 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
|
||||
@@ -67,7 +67,7 @@ do
|
||||
end
|
||||
|
||||
if TUM.objectives.getCount() == 0 then
|
||||
TUM.log("Couldn't create any objective, mission creation failed.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Couldn't create any objective, mission creation failed.", TUM.logLevel.WARNING)
|
||||
closeMission(true)
|
||||
return
|
||||
end
|
||||
@@ -107,7 +107,7 @@ do
|
||||
end
|
||||
|
||||
function TUM.mission.getPlayerCallsign()
|
||||
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||
local player = world.getPlayer()
|
||||
if player then return player:getCallsign() end
|
||||
return "Flight"
|
||||
end
|
||||
@@ -193,20 +193,10 @@ do
|
||||
-- @param event The DCS World event
|
||||
-------------------------------------
|
||||
function TUM.mission.onEvent(event)
|
||||
if not event.initiator 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 -- 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
|
||||
if not event.initiator then return end
|
||||
if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return end
|
||||
if not event.initiator:getPlayerName() then return end
|
||||
|
||||
-- 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
|
||||
@@ -215,14 +205,11 @@ do
|
||||
end
|
||||
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
|
||||
-- changing slots when there's only one)
|
||||
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then
|
||||
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
|
||||
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end
|
||||
|
||||
-- When player dies in single-player, fail the mission
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ do
|
||||
|
||||
local function doCommandNearestAirbase()
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCRequireNearestAirbase", nil, TUM.mission.getPlayerCallsign(), false)
|
||||
TUM.atc.requestNavAssistanceToAirbase(true)
|
||||
TUM.atc.requestNavAssistanceToAirbase(false)
|
||||
end
|
||||
|
||||
local function doCommandObjectiveLocation(index)
|
||||
@@ -30,17 +30,17 @@ do
|
||||
|
||||
local function doCommandWeatherUpdate()
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerATCWeatherUpdate", nil, TUM.mission.getPlayerCallsign(), false)
|
||||
TUM.atc.requestWeatherUpdate(true)
|
||||
TUM.atc.requestWeatherUpdate(false)
|
||||
end
|
||||
|
||||
function TUM.missionMenu.create()
|
||||
local rootMenu = TUM.getOrCreateRootMenu(true) -- Clear the menu
|
||||
missionCommands.addCommand("☱ Mission status", rootMenu, doCommandMissionStatus, nil)
|
||||
missionCommands.removeItem() -- Clear the menu
|
||||
missionCommands.addCommand("☱ Mission status", nil, doCommandMissionStatus, nil)
|
||||
|
||||
local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives", rootMenu)
|
||||
local objectivesMenuRoot = missionCommands.addSubMenu("❖ Objectives")
|
||||
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
|
||||
local obj = TUM.objectives.getObjective(i)
|
||||
if obj then
|
||||
@@ -51,16 +51,16 @@ do
|
||||
missionCommands.addCommand("Nav to objective "..objNameAndDescription, navigationMenuRoot, doCommandObjectiveLocation, i)
|
||||
end
|
||||
end
|
||||
missionCommands.addCommand("Weather update", navigationMenuRoot, doCommandWeatherUpdate, nil)
|
||||
-- missionCommands.addCommand("Weather update", navigationMenuRoot, doCommandWeatherUpdate, nil)
|
||||
|
||||
TUM.wingmenMenu.create()
|
||||
TUM.supportAWACS.createMenu()
|
||||
|
||||
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then -- If not multiplayer, add "show mission score" command
|
||||
missionCommands.addCommand("★ Display mission score", rootMenu, TUM.playerScore.showScore, nil)
|
||||
missionCommands.addCommand("★ Display mission score", nil, TUM.playerScore.showScore, nil)
|
||||
end
|
||||
|
||||
local abortRoot = missionCommands.addSubMenu("⬣ Abort mission", rootMenu)
|
||||
local abortRoot = missionCommands.addSubMenu("⬣ Abort mission")
|
||||
if not TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) and DCSEx.io.canReadAndWrite() then
|
||||
missionCommands.addCommand("✓ Confirm (all xp since last landing will be lost!)", abortRoot, doCommandAbortMission, nil)
|
||||
else
|
||||
|
||||
@@ -55,7 +55,7 @@ do
|
||||
local u = DCSEx.world.getUnitByID(id)
|
||||
if u then u:destroy() end
|
||||
end
|
||||
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)
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ do
|
||||
local objective = TUM.objectivesMaker.create()
|
||||
|
||||
if not objective then
|
||||
TUM.log("Failed to spawn a group for objective #"..tostring(#objectives + 1)..".", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to spawn a group for objective #"..tostring(#objectives + 1)..".", TUM.logLevel.WARNING)
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -174,13 +174,7 @@ do
|
||||
if event.id ~= world.event.S_EVENT_DEAD and event.id ~= world.event.S_EVENT_UNIT_LOST then return end
|
||||
if not event.initiator then return end
|
||||
|
||||
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 objectives[index].isSceneryTarget then
|
||||
if Object.getCategory(event.initiator) == Object.Category.SCENERY then
|
||||
if DCSEx.math.isSamePoint(event.initiator:getPoint(), objectives[index].point3) then
|
||||
timer.scheduleFunction(markObjectiveAsComplete, index, timer.getTime() + 3)
|
||||
|
||||
@@ -46,21 +46,19 @@ do
|
||||
|
||||
local taskID = pickRandomTask()
|
||||
if not taskID then
|
||||
TUM.log("Failed to find a valid task.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to find a valid task.", TUM.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
local objectiveDB = Library.tasks[taskID]
|
||||
|
||||
local parkingInfo = nil
|
||||
local spawnPoint2 = nil
|
||||
local spawnPoint3 = nil
|
||||
local isAirbaseTarget = false
|
||||
local isSceneryTarget = false
|
||||
|
||||
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
|
||||
local validSceneries = DCSEx.world.getSceneriesInZone(zone, DCSEx.zones.getRadius(zone), 250)
|
||||
if not validSceneries or #validSceneries == 0 then
|
||||
TUM.log("Failed to find a valid scenery object to use as target.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to find a valid scenery object to use as target.", TUM.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -68,36 +66,6 @@ do
|
||||
spawnPoint3 = DCSEx.table.deepCopy(pickedScenery:getPoint())
|
||||
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
|
||||
isSceneryTarget = true
|
||||
elseif DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.AIRBASE_TARGET) then
|
||||
local validAirbases = DCSEx.zones.getAirbases(zone, TUM.settings.getEnemyCoalition())
|
||||
if #validAirbases == 0 then
|
||||
TUM.log("Failed to find a valid airbase to use as target.", TUM.logger.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
local pickedAirbase = DCSEx.table.getRandom(validAirbases)
|
||||
spawnPoint3 = DCSEx.table.deepCopy(pickedAirbase:getPoint())
|
||||
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
|
||||
isAirbaseTarget = true
|
||||
elseif DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.PARKED_AIRCRAFT_TARGET) then
|
||||
local validAirbases = DCSEx.zones.getAirbases(zone, TUM.settings.getEnemyCoalition())
|
||||
if #validAirbases == 0 then
|
||||
TUM.log("Failed to find a valid airbase to use as target.", TUM.logger.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
local pickedAirbase = DCSEx.table.getRandom(validAirbases)
|
||||
local parkings = pickedAirbase:getParking()
|
||||
local validParkings = {}
|
||||
for _,p in pairs(parkings) do
|
||||
if p.Term_Type == 104 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)
|
||||
parkingInfo = { airbaseID = pickedAirbase:getID(), parkingID = pickedParking.Term_Index }
|
||||
spawnPoint3 = pickedParking.vTerminalPos
|
||||
spawnPoint2 = DCSEx.math.vec3ToVec2(spawnPoint3)
|
||||
elseif objectiveDB.surfaceType == land.SurfaceType.WATER then
|
||||
spawnPoint2 = pickWaterPoint(zone)
|
||||
if not spawnPoint2 then
|
||||
@@ -108,7 +76,7 @@ do
|
||||
end
|
||||
|
||||
if not spawnPoint2 then
|
||||
TUM.log("Failed to find a spawn point for objective.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to find a spawn point for objective.", TUM.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -121,12 +89,10 @@ do
|
||||
local objective = {
|
||||
completed = false,
|
||||
completedUnitsID = {},
|
||||
isAirbaseTarget = isAirbaseTarget,
|
||||
isSceneryTarget = isSceneryTarget,
|
||||
markerID = DCSEx.world.getNextMarkerID(),
|
||||
markerTextID = DCSEx.world.getNextMarkerID(),
|
||||
name = Library.objectiveNames.get():upper(),
|
||||
parkingInfo = parkingInfo,
|
||||
point2 = DCSEx.table.deepCopy(spawnPoint2),
|
||||
point3 = DCSEx.table.deepCopy(spawnPoint3),
|
||||
preciseCoordinates = objectiveDB.waypointInaccuracy <= 0,
|
||||
@@ -142,7 +108,7 @@ do
|
||||
objective.waypoint3 = DCSEx.math.vec2ToVec3(objective.waypoint2, "land")
|
||||
end
|
||||
|
||||
if not isAirbaseTarget and not isSceneryTarget then
|
||||
if not DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.SCENERY_TARGET) then
|
||||
-- Check group options
|
||||
local groupOptions = {}
|
||||
if DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.MOVING) then
|
||||
@@ -157,12 +123,6 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
-- Parked aircraft only
|
||||
if parkingInfo then
|
||||
groupOptions.airbaseID = parkingInfo.airbaseID
|
||||
groupOptions.parkingID = parkingInfo.parkingID
|
||||
end
|
||||
|
||||
local units = Library.factions.getUnits(TUM.settings.getEnemyFaction(), objectiveDB.targetFamilies, math.random(objectiveDB.targetCount[1], objectiveDB.targetCount[2]))
|
||||
|
||||
local groupInfo = nil
|
||||
@@ -176,7 +136,7 @@ do
|
||||
end
|
||||
|
||||
if not groupInfo then
|
||||
TUM.log("Failed to spawn a group for objective.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to spawn a group for objective.", TUM.logLevel.WARNING)
|
||||
return nil
|
||||
end
|
||||
objective.groupID = groupInfo.groupID
|
||||
|
||||
@@ -146,11 +146,10 @@ do
|
||||
-- Appends the career menu to the F10 menu. Only works in single-player missions
|
||||
-------------------------------------
|
||||
function TUM.playerCareer.createMenu()
|
||||
local rootMenu = TUM.getOrCreateRootMenu()
|
||||
if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled
|
||||
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No career in multiplayer
|
||||
|
||||
missionCommands.addCommand("✪ View pilot career stats", rootMenu, TUM.playerCareer.displayMedalBox, true)
|
||||
missionCommands.addCommand("✪ View pilot career stats", nil, TUM.playerCareer.displayMedalBox, true)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
@@ -274,7 +273,7 @@ do
|
||||
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."
|
||||
|
||||
TUM.log(msg, TUM.logger.logLevel.WARNING)
|
||||
TUM.log(msg, TUM.logLevel.WARNING)
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
@@ -68,8 +68,8 @@ do
|
||||
if objectDesc.attributes["Planes"] then return math.floor(25 * groundMultiplier) end
|
||||
|
||||
-- Rotary wing
|
||||
if objectDesc.attributes["Attack helicopters"] then return math.floor(25 * groundMultiplier) end
|
||||
if objectDesc.attributes["Helicopters"] then return math.floor(15 * groundMultiplier) end
|
||||
if objectDesc.attributes["Attack helicopters"] then return math.floor(30 * groundMultiplier) end
|
||||
if objectDesc.attributes["Helicopters"] then return math.floor(25 * groundMultiplier) end
|
||||
|
||||
-- Default air
|
||||
if objectDesc.attributes["Air"] then return math.floor(20 * groundMultiplier) end
|
||||
|
||||
@@ -50,7 +50,7 @@ do
|
||||
end
|
||||
message = DCSEx.string.firstToUpper(message)
|
||||
|
||||
local duration = DCSEx.string.getReadingTime(message) * args.displayTimeMultiplier
|
||||
local duration = DCSEx.string.getReadingTime(message)
|
||||
|
||||
-- Print message
|
||||
trigger.action.outTextForUnit(args.unitID, callsign:upper()..": "..message, duration, false)
|
||||
@@ -73,13 +73,12 @@ do
|
||||
-- @param delayed Should the message be delayed (used for message answers)
|
||||
-- @param functionToRun 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, displayTimeMultiplier)
|
||||
function TUM.radio.playForAll(messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
local players = DCSEx.world.getAllPlayers()
|
||||
|
||||
for _, unit in pairs(players) do
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(unit), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(unit), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,13 +91,12 @@ do
|
||||
-- @param delayed Should the message be delayed (used for message answers)
|
||||
-- @param functionToRun 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, displayTimeMultiplier)
|
||||
function TUM.radio.playForCoalition(coalitionID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
local players = coalition.getPlayers(coalitionID)
|
||||
|
||||
for _,u in pairs(players) do
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,14 +109,13 @@ do
|
||||
-- @param delayed Should the message be delayed (used for message answers)
|
||||
-- @param functionToRun 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, displayTimeMultiplier)
|
||||
function TUM.radio.playForGroup(groupID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
local group = DCSEx.world.getGroupByID(groupID)
|
||||
if not group then return end -- group does not exist
|
||||
|
||||
for _,u in pairs(group:getUnits()) do
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters, displayTimeMultiplier)
|
||||
TUM.radio.playForUnit(DCSEx.dcs.getObjectIDAsNumber(u), messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,9 +129,8 @@ do
|
||||
-- @param delayed Should the message be delayed (used for message answers)
|
||||
-- @param functionToRun 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, displayTimeMultiplier)
|
||||
function TUM.radio.playForUnit(unitID, messageID, replacements, callsign, delayed, functionToRun, functionParameters)
|
||||
if not messageID then return end
|
||||
if not Library.radioMessages[messageID] then return end
|
||||
delayed = delayed or false
|
||||
@@ -145,7 +141,6 @@ do
|
||||
|
||||
local radioArgs = {
|
||||
callsign = callsign,
|
||||
displayTimeMultiplier = math.max(0.1, displayTimeMultiplier or 1.0),
|
||||
functionToRun = functionToRun,
|
||||
functionParameters = functionParameters,
|
||||
messageID = messageID,
|
||||
|
||||
@@ -52,13 +52,11 @@ do
|
||||
[TUM.settings.id.PLAYER_COALITION] = { "Red", "Blue" }, -- Must match values in the coalition.side enum
|
||||
[TUM.settings.id.TARGET_COUNT] = { "1", "2", "3", "4" },
|
||||
[TUM.settings.id.TARGET_LOCATION] = { },
|
||||
[TUM.settings.id.TASKING] = { "Antiship strike", "Ground attack", "Helicopter hunt", "Interception", "OCA", "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.TASKING] = { "Antiship strike", "Ground attack", "Interception", "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", "Gulf war", "Modern" }, -- Must match values in the DCSEx.enums.timePeriod enum
|
||||
[TUM.settings.id.WINGMEN] = { "None", "1", "2", "3" }
|
||||
}
|
||||
|
||||
local targetLocation
|
||||
|
||||
local function getFaction(side)
|
||||
if side == coalition.side.BLUE then
|
||||
return TUM.settings.getValue(TUM.settings.id.COALITION_BLUE, true)
|
||||
@@ -83,18 +81,20 @@ do
|
||||
[TUM.settings.id.WINGMEN] = 2
|
||||
}
|
||||
|
||||
-- TODO: set default time period according to mission year
|
||||
-- TODO: enable WW2
|
||||
-- if env.mission.date.Year <= 1945 then
|
||||
-- settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.WORLD_WAR_2
|
||||
-- elseif env.mission.date.Year < 1960 then
|
||||
-- settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.KOREA_WAR
|
||||
-- elseif env.mission.date.Year < 1975 then
|
||||
-- settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.VIETNAM_WAR
|
||||
-- elseif env.mission.date.Year < 1990 then
|
||||
-- settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.COLD_WAR
|
||||
-- else
|
||||
-- settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.MODERN
|
||||
-- end
|
||||
if env.mission.date.Year < 1960 then
|
||||
settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.KOREA_WAR
|
||||
elseif env.mission.date.Year < 1975 then
|
||||
settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.VIETNAM_WAR
|
||||
elseif env.mission.date.Year < 1990 then
|
||||
settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.COLD_WAR
|
||||
elseif env.mission.date.Year < 2000 then
|
||||
settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.GULF_WAR
|
||||
else
|
||||
settings[TUM.settings.id.TIME_PERIOD] = DCSEx.enums.timePeriod.MODERN
|
||||
end
|
||||
|
||||
for i,id in pairs(SETTING_VALUES[TUM.settings.id.COALITION_BLUE]) do
|
||||
if id == Library.factions.defaults[coalition.side.BLUE] then settings[TUM.settings.id.COALITION_BLUE] = i end
|
||||
|
||||
@@ -43,15 +43,6 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
detectedGroups = coalition.getGroups(TUM.settings.getEnemyCoalition(), Group.Category.HELICOPTER)
|
||||
for _,g in pairs(detectedGroups) do
|
||||
local units = g:getUnits()
|
||||
for _,u in pairs(units) do
|
||||
if u:inAir() then
|
||||
table.insert(detectedAircraft, u)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- No aircraft on picture
|
||||
if #detectedAircraft == 0 then
|
||||
@@ -100,9 +91,8 @@ do
|
||||
|
||||
function TUM.supportAWACS.createMenu()
|
||||
if not awacsGroupID then return end -- No AWACS
|
||||
local rootMenu = TUM.getOrCreateRootMenu()
|
||||
|
||||
local rootPath = missionCommands.addSubMenu("⌾ Awacs", rootMenu)
|
||||
local rootPath = missionCommands.addSubMenu("⌾ Awacs")
|
||||
missionCommands.addCommand("Bogey dope", rootPath, doCommandBogeyDope, nil)
|
||||
missionCommands.addCommand("Picture", rootPath, doCommandPicture, nil)
|
||||
end
|
||||
@@ -141,7 +131,7 @@ do
|
||||
end
|
||||
TUM.log("Spawned AWACS aircraft")
|
||||
else
|
||||
TUM.log("Failed to create AWACS aircraft", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to create AWACS aircraft", TUM.logLevel.WARNING)
|
||||
end
|
||||
else
|
||||
TUM.log("No AWACS aircraft available")
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
-- ====================================================================================
|
||||
-- TUM.SUPPORTJTAC - HANDLES FRIENDLY JTAC SMOKE MARKERS AND LASING
|
||||
-- ====================================================================================
|
||||
-- (local) destroyAllLasers()
|
||||
-- (local) doCommandLaser(index)
|
||||
-- (local) doCommandLaserStop(index)
|
||||
-- (local) doCommandSmoke(index)
|
||||
-- (local) spawnSmoke(args)
|
||||
-- (local) updateLaserPositions()
|
||||
-- TUM.supportJTAC.setupJTACOnObjective(index, menuRoot)
|
||||
-- ====================================================================================
|
||||
|
||||
TUM.supportJTAC = {}
|
||||
@@ -35,57 +28,14 @@ do
|
||||
"Whiplash",
|
||||
}
|
||||
|
||||
local LASER_CODE = 1688
|
||||
local LASER_DESIGNATION_PENALTY = -15
|
||||
local SMOKE_DURATION = 300 -- in seconds
|
||||
local SMOKE_MARKER_PENALTY = -25
|
||||
|
||||
local jtacName = {}
|
||||
local lastSmoke = {}
|
||||
local laserSpots = {}
|
||||
local laserTargets = {}
|
||||
|
||||
local function destroyAllLasers()
|
||||
if #laserSpots == 0 and #laserTargets == 0 then return end
|
||||
|
||||
for _,l in pairs(laserSpots) do
|
||||
if l then l:destroy() end
|
||||
end
|
||||
|
||||
laserSpots = {}
|
||||
laserTargets = {}
|
||||
end
|
||||
|
||||
local function doCommandLaser(index)
|
||||
local obj = TUM.objectives.getObjective(index)
|
||||
if not obj then return end
|
||||
|
||||
if laserSpots[index] then
|
||||
laserSpots[index]:destroy()
|
||||
laserSpots[index] = nil
|
||||
end
|
||||
|
||||
local sourceObject = nil
|
||||
local targetPoint = {x = 0, y = 0, z = 0}
|
||||
|
||||
laserSpots[index] = Spot.createLaser(sourceObject, {x = 0, y = 1, z = 0}, targetPoint, LASER_CODE)
|
||||
end
|
||||
|
||||
local function doCommandLaserStop(index)
|
||||
local obj = TUM.objectives.getObjective(index)
|
||||
if not obj then return end
|
||||
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "playerJTACLaserStop", { jtacName[index], obj.name }, TUM.mission.getPlayerCallsign(), false)
|
||||
|
||||
-- Not lasing this objective at the moment
|
||||
if not laserSpots[index] then
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacLaserStopNoLaser", { jtacName[index], obj.name }, jtacName[index], true)
|
||||
return
|
||||
end
|
||||
|
||||
laserSpots[index]:destroy()
|
||||
laserSpots[index] = nil
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacLaserStop", { jtacName[index], obj.name }, jtacName[index], true)
|
||||
local function spawnSmoke(args)
|
||||
trigger.action.smoke(args.point3, args.smokeColor)
|
||||
end
|
||||
|
||||
local function doCommandSmoke(index)
|
||||
@@ -110,7 +60,7 @@ do
|
||||
return
|
||||
end
|
||||
|
||||
if obj.isAirbaseTarget or obj.isSceneryTarget then
|
||||
if obj.isSceneryTarget then
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacSmokeOK", { jtacName[index], smokeColorName }, jtacName[index], true, spawnSmoke, { point3 = obj.point3, smokeColor = smokeColor })
|
||||
else
|
||||
for _,id in ipairs(obj.unitsID) do
|
||||
@@ -129,24 +79,6 @@ do
|
||||
TUM.radio.playForCoalition(TUM.settings.getPlayerCoalition(), "jtacSmokeNoTarget", { jtacName[index] }, jtacName[index], true)
|
||||
end
|
||||
|
||||
local function spawnSmoke(args)
|
||||
trigger.action.smoke(args.point3, args.smokeColor)
|
||||
end
|
||||
|
||||
local function updateLaserPositions()
|
||||
-- Not in a mission? Destroy all lasers
|
||||
if TUM.mission.getStatus() == TUM.mission.status.NONE then
|
||||
destroyAllLasers()
|
||||
return timer.getTime() + 0.5
|
||||
end
|
||||
|
||||
for i=1,TUM.objectives.getCount() do
|
||||
-- TODO
|
||||
end
|
||||
|
||||
return timer.getTime() + 0.5
|
||||
end
|
||||
|
||||
function TUM.supportJTAC.setupJTACOnObjective(index, menuRoot)
|
||||
local obj = TUM.objectives.getObjective(index)
|
||||
if not obj then return end
|
||||
@@ -157,10 +89,5 @@ do
|
||||
if not DCSEx.table.contains(objectiveDB.flags, DCSEx.enums.taskFlag.ALLOW_JTAC) then return end -- No JTAC for this objective
|
||||
|
||||
missionCommands.addCommand("Smoke marker on target ("..tostring(SMOKE_MARKER_PENALTY).."xp)", menuRoot, doCommandSmoke, index)
|
||||
missionCommands.addCommand("Designate target with laser (code "..tostring(LASER_CODE)..", "..tostring(LASER_DESIGNATION_PENALTY).."xp)", menuRoot, doCommandLaser, index)
|
||||
missionCommands.addCommand("Stop designating target with laser", menuRoot, doCommandLaserStop, index)
|
||||
end
|
||||
|
||||
-- Schedule the laser updating function to be called every 0.5 seconds
|
||||
timer.scheduleFunction(updateLaserPositions, {}, timer.getTime() + 0.5)
|
||||
end
|
||||
|
||||
@@ -125,27 +125,7 @@ do
|
||||
elseif DCSEx.string.startsWith(z.name:lower(), "water") then
|
||||
table.insert(waterZones, z)
|
||||
else
|
||||
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
|
||||
table.insert(missionZones, z)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -155,18 +135,18 @@ do
|
||||
local zoneName = "BLUFOR"
|
||||
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.logger.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.logLevel.ERROR)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if #missionZones == 0 then
|
||||
TUM.log("No mission zones found. Create at least one mission zone in the mission editor.", TUM.logger.logLevel.ERROR)
|
||||
TUM.log("No mission zones found. Create at least one mission zone in the mission editor.", TUM.logLevel.ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
if #missionZones > 10 then
|
||||
TUM.log("Too many mission zones, extra zones removed.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Too many mission zones, extra zones removed.", TUM.logLevel.WARNING)
|
||||
while #missionZones > 10 do
|
||||
table.remove(missionZones, 11)
|
||||
end
|
||||
@@ -177,10 +157,10 @@ do
|
||||
-- zones[coalition.side.RED] = DCSEx.zones.getByName("REDFOR")
|
||||
|
||||
-- if not zones[coalition.side.BLUE] then
|
||||
-- TUM.log("BLUFOR zone not found.", TUM.logger.logLevel.ERROR)
|
||||
-- TUM.log("BLUFOR zone not found.", TUM.logLevel.ERROR)
|
||||
-- return false
|
||||
-- elseif not zones[coalition.side.RED] then
|
||||
-- TUM.log("REDFOR zone not found.", TUM.logger.logLevel.ERROR)
|
||||
-- TUM.log("REDFOR zone not found.", TUM.logLevel.ERROR)
|
||||
-- return false
|
||||
-- end
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ do
|
||||
elseif taskingID == DCSEx.enums.taskFamily.GROUND_ATTACK then
|
||||
return "attack"
|
||||
-- elseif taskingID == DCSEx.enums.taskFamily.HELICOPTER then
|
||||
elseif taskingID == DCSEx.enums.taskFamily.HELO_HUNT then
|
||||
-- elseif taskingID == DCSEx.enums.taskFamily.HELO_HUN then
|
||||
elseif taskingID == DCSEx.enums.taskFamily.INTERCEPTION then
|
||||
return "cap"
|
||||
elseif taskingID == DCSEx.enums.taskFamily.SEAD then
|
||||
-- elseif taskingID == DCSEx.enums.taskFamily.OCA then
|
||||
elseif taskingID == DCSEx.enums.taskFamily.SEAD then
|
||||
return "sead"
|
||||
-- elseif taskingID == DCSEx.enums.taskFamily.STRIKE then
|
||||
-- return "strike"
|
||||
@@ -48,7 +48,7 @@ do
|
||||
-- Retrive player unit type
|
||||
local playerTypeName = player:getTypeName()
|
||||
if not Library.aircraft[playerTypeName] then
|
||||
TUM.log("Cannot spawn AI wingmen, aircraft \""..playerTypeName.."\" not found in the database.", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Cannot spawn AI wingmen, aircraft \""..playerTypeName.."\" not found in the database.", TUM.logLevel.WARNING)
|
||||
return
|
||||
end
|
||||
local playerCategory = Group.Category.AIRPLANE
|
||||
@@ -88,7 +88,7 @@ do
|
||||
)
|
||||
|
||||
if not groupInfo then
|
||||
TUM.log("Failed to spawn AI wingmen", TUM.logger.logLevel.WARNING)
|
||||
TUM.log("Failed to spawn AI wingmen", TUM.logLevel.WARNING)
|
||||
return
|
||||
end
|
||||
wingmenGroupID = groupInfo.groupID
|
||||
@@ -180,8 +180,6 @@ do
|
||||
elseif event.id == world.event.S_EVENT_LAND then -- Remove wingmen on player landing
|
||||
if not event.initiator:getPlayerName() then return end
|
||||
TUM.wingmen.removeAll()
|
||||
elseif event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then -- Remove wingmen when player takes control of a new unit
|
||||
TUM.wingmen.removeAll()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -75,12 +75,11 @@ do
|
||||
end
|
||||
|
||||
function TUM.wingmenMenu.create()
|
||||
local rootMenu = TUM.getOrCreateRootMenu()
|
||||
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No wingmen in multiplayer
|
||||
if TUM.settings.getValue(TUM.settings.id.WINGMEN) <= 1 then return end -- No wingmen
|
||||
local isWW2 = (TUM.settings.getValue(TUM.settings) == DCSEx.enums.timePeriod.WORLD_WAR_2) -- Some options are different when time period is WW2
|
||||
|
||||
local rootPath = missionCommands.addSubMenu("✈ Flight", rootMenu)
|
||||
local rootPath = missionCommands.addSubMenu("✈ Flight")
|
||||
missionCommands.addCommand("Cover me!", rootPath, radioCommandCoverMe, nil)
|
||||
|
||||
------------------------------------------------------
|
||||
@@ -138,9 +137,7 @@ do
|
||||
-- "Change altitude" submenu
|
||||
------------------------------------------------------
|
||||
local altitudePath = missionCommands.addSubMenu("Change altitude", rootPath)
|
||||
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 baseAltitude = DCSEx.converter.metersToFeet(Library.aircraft[world.getPlayer():getTypeName()].altitude)
|
||||
local altitudeFactions = { 0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5 }
|
||||
for _,f in ipairs(altitudeFactions) do
|
||||
local altText = DCSEx.string.toStringThousandsSeparator(math.floor((baseAltitude * f) / 100) * 100).."ft"
|
||||
|
||||
@@ -29,7 +29,7 @@ do
|
||||
end
|
||||
|
||||
local function getAltitude()
|
||||
local player = DCSEx.world.getFirstPlayer(TUM.settings.getPlayerCoalition())
|
||||
local player = world.getPlayer()
|
||||
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
|
||||
@@ -133,16 +133,10 @@ do
|
||||
local function getRejoinTaskTable(formationDistance)
|
||||
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 {
|
||||
id = "Follow",
|
||||
params = {
|
||||
groupId = groupID,
|
||||
groupId = DCSEx.dcs.getObjectIDAsNumber(world.getPlayer():getGroup()),
|
||||
lastWptIndexFlag = false,
|
||||
lastWptIndex = -1,
|
||||
pos = { x = -formationDistance, y = 0, z = -formationDistance }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
User's manual
|
||||
</p>
|
||||
<p class="heavy">
|
||||
The Universal Mission v0.3.250914<br />
|
||||
The Universal Mission v0.2.250729<br />
|
||||
Created and maintained by Ambroise Garel (<a href='mailto:akaagarmail@gmail.com'>akaagarmail@gmail.com</a>)
|
||||
</p>
|
||||
<p class="heavy">
|
||||
@@ -46,7 +46,7 @@ I hope you'll like it.
|
||||
- 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
|
||||
- More than 300 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
|
||||
@@ -145,9 +145,7 @@ The exact content of the menu will depend on the current phase of the mission.
|
||||
- **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.
|
||||
@@ -176,7 +174,7 @@ The Universal Mission is designed to be easily editable to suit your preferences
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
<h3>Zones</h3>
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
{
|
||||
"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]
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user