diff --git a/Generator/MissionGenerator.py b/Generator/MissionGenerator.py
index 89ab13e..51be48c 100644
--- a/Generator/MissionGenerator.py
+++ b/Generator/MissionGenerator.py
@@ -29,8 +29,8 @@ import qtmodern.windows
# UPDATE BUILD VERSION
maj_version = 1
-minor_version = 2
-patch_version = 0
+minor_version = 3
+patch_version = 1
modules_version = 2
modules_url = 'https://dcs-helicopters.com/user-files/modules/'
@@ -421,6 +421,12 @@ class Window(QMainWindow, Ui_MainWindow):
if qobj:
qobj.setValue(config['spinboxes'][box])
+ for box in QObject.findChildren(self, QSpinBox):
+ if 'disable_spinboxes' in config and config['disable_spinboxes'] is not None and box.objectName() in config['disable_spinboxes']:
+ box.setEnabled(False)
+ else:
+ box.setEnabled(True)
+
for button in QObject.findChildren(self, QRadioButton):
if 'radiobuttons' in config and button.objectName() in config['radiobuttons']:
button.setChecked(True)
@@ -570,7 +576,6 @@ class Window(QMainWindow, Ui_MainWindow):
"game_display": self.game_status_checkBox.isChecked(),
"defending": self.defense_checkBox.isChecked(),
"slots": self.slot_template_comboBox.currentText(),
- "zone_protect_sams": self.zone_sams_checkBox.isChecked(),
"zone_farps": self.farp_buttonGroup.checkedButton().objectName(),
"e_transport_helos": self.e_transport_helos_spinBox.value(),
"transport_drop_qty": self.troop_drop_spinBox.value(),
@@ -588,6 +593,9 @@ class Window(QMainWindow, Ui_MainWindow):
"logistics_farp_file": self.scenario.getConfigValue("logistics_farp_file", default=None),
"zone_protect_file": self.scenario.getConfigValue("zone_protect_file", default=None),
"script": self.scenario.getConfigValue("script", default=None),
+ "advanced_defenses": self.advanced_defenses_checkBox.isChecked(),
+ "red_cap": self.scenario.getConfigValue("red_cap", default=True),
+ "blue_cap": self.scenario.getConfigValue("blue_cap", default=True),
}
logger.info("Generating mission with options:")
diff --git a/Generator/MissionGeneratorUI.py b/Generator/MissionGeneratorUI.py
index 2a2dacf..19250cf 100644
--- a/Generator/MissionGeneratorUI.py
+++ b/Generator/MissionGeneratorUI.py
@@ -34,20 +34,20 @@ class Ui_MainWindow(object):
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.logistics_crates_checkBox = QtWidgets.QCheckBox(self.centralwidget)
- self.logistics_crates_checkBox.setGeometry(QtCore.QRect(980, 211, 251, 28))
+ self.logistics_crates_checkBox.setGeometry(QtCore.QRect(980, 231, 251, 28))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
self.logistics_crates_checkBox.setFont(font)
self.logistics_crates_checkBox.setChecked(True)
self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox")
- self.zone_sams_checkBox = QtWidgets.QCheckBox(self.centralwidget)
- self.zone_sams_checkBox.setGeometry(QtCore.QRect(980, 320, 241, 28))
+ self.advanced_defenses_checkBox = QtWidgets.QCheckBox(self.centralwidget)
+ self.advanced_defenses_checkBox.setGeometry(QtCore.QRect(510, 350, 241, 28))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
- self.zone_sams_checkBox.setFont(font)
- self.zone_sams_checkBox.setObjectName("zone_sams_checkBox")
+ self.advanced_defenses_checkBox.setFont(font)
+ self.advanced_defenses_checkBox.setObjectName("advanced_defenses_checkBox")
self.red_forces_label = QtWidgets.QLabel(self.centralwidget)
self.red_forces_label.setGeometry(QtCore.QRect(470, 80, 171, 27))
font = QtGui.QFont()
@@ -79,7 +79,7 @@ class Ui_MainWindow(object):
self.description_textBrowser.setObjectName("description_textBrowser")
self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.defense_checkBox.setEnabled(True)
- self.defense_checkBox.setGeometry(QtCore.QRect(470, 130, 156, 28))
+ self.defense_checkBox.setGeometry(QtCore.QRect(980, 140, 156, 28))
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(False)
@@ -109,7 +109,7 @@ class Ui_MainWindow(object):
self.redforces_comboBox.setFont(font)
self.redforces_comboBox.setObjectName("redforces_comboBox")
self.scenario_label_8 = QtWidgets.QLabel(self.centralwidget)
- self.scenario_label_8.setGeometry(QtCore.QRect(570, 220, 271, 24))
+ self.scenario_label_8.setGeometry(QtCore.QRect(570, 180, 271, 24))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
@@ -165,14 +165,14 @@ class Ui_MainWindow(object):
self.version_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.version_label.setObjectName("version_label")
self.scenario_label_10 = QtWidgets.QLabel(self.centralwidget)
- self.scenario_label_10.setGeometry(QtCore.QRect(570, 260, 271, 24))
+ self.scenario_label_10.setGeometry(QtCore.QRect(570, 220, 271, 24))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
self.scenario_label_10.setFont(font)
self.scenario_label_10.setObjectName("scenario_label_10")
self.e_transport_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget)
- self.e_transport_helos_spinBox.setGeometry(QtCore.QRect(510, 260, 51, 31))
+ self.e_transport_helos_spinBox.setGeometry(QtCore.QRect(510, 220, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -187,7 +187,7 @@ class Ui_MainWindow(object):
self.e_transport_helos_spinBox.setProperty("value", 1)
self.e_transport_helos_spinBox.setObjectName("e_transport_helos_spinBox")
self.e_attack_planes_spinBox = QtWidgets.QSpinBox(self.centralwidget)
- self.e_attack_planes_spinBox.setGeometry(QtCore.QRect(510, 220, 51, 31))
+ self.e_attack_planes_spinBox.setGeometry(QtCore.QRect(510, 180, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -202,7 +202,7 @@ class Ui_MainWindow(object):
self.e_attack_planes_spinBox.setProperty("value", 1)
self.e_attack_planes_spinBox.setObjectName("e_attack_planes_spinBox")
self.e_attack_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget)
- self.e_attack_helos_spinBox.setGeometry(QtCore.QRect(510, 180, 51, 31))
+ self.e_attack_helos_spinBox.setGeometry(QtCore.QRect(510, 140, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -219,7 +219,7 @@ class Ui_MainWindow(object):
self.e_attack_helos_spinBox.setProperty("value", 1)
self.e_attack_helos_spinBox.setObjectName("e_attack_helos_spinBox")
self.scenario_label_7 = QtWidgets.QLabel(self.centralwidget)
- self.scenario_label_7.setGeometry(QtCore.QRect(570, 180, 271, 24))
+ self.scenario_label_7.setGeometry(QtCore.QRect(570, 140, 271, 24))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
@@ -239,7 +239,7 @@ class Ui_MainWindow(object):
self.scenario_label_9.setFont(font)
self.scenario_label_9.setObjectName("scenario_label_9")
self.awacs_checkBox = QtWidgets.QCheckBox(self.centralwidget)
- self.awacs_checkBox.setGeometry(QtCore.QRect(980, 246, 241, 28))
+ self.awacs_checkBox.setGeometry(QtCore.QRect(980, 266, 241, 28))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
@@ -247,7 +247,7 @@ class Ui_MainWindow(object):
self.awacs_checkBox.setChecked(True)
self.awacs_checkBox.setObjectName("awacs_checkBox")
self.tankers_checkBox = QtWidgets.QCheckBox(self.centralwidget)
- self.tankers_checkBox.setGeometry(QtCore.QRect(980, 282, 241, 28))
+ self.tankers_checkBox.setGeometry(QtCore.QRect(980, 302, 241, 28))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
@@ -277,14 +277,14 @@ class Ui_MainWindow(object):
self.game_status_checkBox.setTristate(False)
self.game_status_checkBox.setObjectName("game_status_checkBox")
self.label = QtWidgets.QLabel(self.centralwidget)
- self.label.setGeometry(QtCore.QRect(570, 340, 261, 23))
+ self.label.setGeometry(QtCore.QRect(570, 300, 261, 23))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
self.label.setFont(font)
self.label.setObjectName("label")
self.inf_spawn_spinBox = QtWidgets.QSpinBox(self.centralwidget)
- self.inf_spawn_spinBox.setGeometry(QtCore.QRect(510, 340, 51, 31))
+ self.inf_spawn_spinBox.setGeometry(QtCore.QRect(510, 300, 51, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.inf_spawn_spinBox.setFont(font)
@@ -294,7 +294,7 @@ class Ui_MainWindow(object):
self.inf_spawn_spinBox.setProperty("value", 0)
self.inf_spawn_spinBox.setObjectName("inf_spawn_spinBox")
self.troop_drop_spinBox = QtWidgets.QSpinBox(self.centralwidget)
- self.troop_drop_spinBox.setGeometry(QtCore.QRect(510, 300, 51, 31))
+ self.troop_drop_spinBox.setGeometry(QtCore.QRect(510, 260, 51, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.troop_drop_spinBox.setFont(font)
@@ -312,14 +312,14 @@ class Ui_MainWindow(object):
self.random_weather_checkBox.setTristate(False)
self.random_weather_checkBox.setObjectName("random_weather_checkBox")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
- self.label_3.setGeometry(QtCore.QRect(570, 300, 281, 23))
+ self.label_3.setGeometry(QtCore.QRect(570, 260, 281, 23))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
self.label_3.setFont(font)
self.label_3.setObjectName("label_3")
self.apcs_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget)
- self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(980, 180, 251, 27))
+ self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(980, 200, 251, 27))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
@@ -581,8 +581,8 @@ class Ui_MainWindow(object):
MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator"))
self.logistics_crates_checkBox.setStatusTip(_translate("MainWindow", "Enable a base or FARP near the start position that can spawn CTLD crates for building ground units and air defenses. Sling load the logistics containers to create new logistics sites."))
self.logistics_crates_checkBox.setText(_translate("MainWindow", "Logistics Base"))
- self.zone_sams_checkBox.setStatusTip(_translate("MainWindow", "Inactive conflict zones will be protected by SAMs. When a zone is cleared, SAMs at next active zone will be destroyed. No effect if Blue on defense."))
- self.zone_sams_checkBox.setText(_translate("MainWindow", "Protect Inactive Zones"))
+ self.advanced_defenses_checkBox.setStatusTip(_translate("MainWindow", "Each enemy conflict zone spawns a template of AA defenses and radar units that may spawn fighter intercepts for detected aircraft. A good difficulty multiplier for multiplayer."))
+ self.advanced_defenses_checkBox.setText(_translate("MainWindow", "Enemy Advanced Defenses"))
self.red_forces_label.setText(_translate("MainWindow", "Red Forces:"))
self.scenario_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own templates that include mission options like kneeboards, briefings, weather, static units, triggers, scripts, etc."))
self.description_textBrowser.setHtml(_translate("MainWindow", "\n"
diff --git a/Generator/MissionGeneratorUI.ui b/Generator/MissionGeneratorUI.ui
index 9efcc07..73380cd 100644
--- a/Generator/MissionGeneratorUI.ui
+++ b/Generator/MissionGeneratorUI.ui
@@ -54,7 +54,7 @@
980
- 211
+ 231
251
28
@@ -75,11 +75,11 @@
true
-
+
- 980
- 320
+ 510
+ 350
241
28
@@ -91,10 +91,10 @@
- Inactive conflict zones will be protected by SAMs. When a zone is cleared, SAMs at next active zone will be destroyed. No effect if Blue on defense.
+ Each enemy conflict zone spawns a template of AA defenses and radar units that may spawn fighter intercepts for detected aircraft. A good difficulty multiplier for multiplayer.
- Protect Inactive Zones
+ Enemy Advanced Defenses
@@ -190,8 +190,8 @@ p, li { white-space: pre-wrap; }
- 470
- 130
+ 980
+ 140
156
28
@@ -271,7 +271,7 @@ p, li { white-space: pre-wrap; }
570
- 220
+ 180
271
24
@@ -438,7 +438,7 @@ p, li { white-space: pre-wrap; }
570
- 260
+ 220
271
24
@@ -460,7 +460,7 @@ p, li { white-space: pre-wrap; }
510
- 260
+ 220
51
31
@@ -496,7 +496,7 @@ p, li { white-space: pre-wrap; }
510
- 220
+ 180
51
31
@@ -532,7 +532,7 @@ p, li { white-space: pre-wrap; }
510
- 180
+ 140
51
31
@@ -574,7 +574,7 @@ p, li { white-space: pre-wrap; }
570
- 180
+ 140
271
24
@@ -633,7 +633,7 @@ p, li { white-space: pre-wrap; }
980
- 246
+ 266
241
28
@@ -658,7 +658,7 @@ p, li { white-space: pre-wrap; }
980
- 282
+ 302
241
28
@@ -758,7 +758,7 @@ p, li { white-space: pre-wrap; }
570
- 340
+ 300
261
23
@@ -780,7 +780,7 @@ p, li { white-space: pre-wrap; }
510
- 340
+ 300
51
31
@@ -810,7 +810,7 @@ p, li { white-space: pre-wrap; }
510
- 300
+ 260
51
31
@@ -867,7 +867,7 @@ p, li { white-space: pre-wrap; }
570
- 300
+ 260
281
23
@@ -889,7 +889,7 @@ p, li { white-space: pre-wrap; }
980
- 180
+ 200
251
27
diff --git a/Generator/RotorOpsConflict.py b/Generator/RotorOpsConflict.py
index 55f1d86..3e5fc71 100644
--- a/Generator/RotorOpsConflict.py
+++ b/Generator/RotorOpsConflict.py
@@ -74,14 +74,14 @@ def triggerSetup(rops, options):
# dcs.action.DoScript(dcs.action.String("ctld.createRadioBeaconAtZone('" + c_zone + "','blue', 1440,'" + c_zone + "')")))
# rops.m.triggerrules.triggers.append(trig)
- # Zone protection SAMs
- if options["zone_protect_sams"]:
- for index, zone_name in enumerate(rops.conflict_zones):
- z_sams_trig = dcs.triggers.TriggerOnce(comment="Deactivate " + zone_name + " SAMs")
- z_sams_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
- z_sams_trig.actions.append(dcs.action.DoScript(
- dcs.action.String("Group.destroy(Group.getByName('Static " + zone_name + " Protection SAM'))")))
- rops.m.triggerrules.triggers.append(z_sams_trig)
+ # # Zone protection SAMs
+ # if options["zone_protect_sams"]:
+ # for index, zone_name in enumerate(rops.conflict_zones):
+ # z_sams_trig = dcs.triggers.TriggerOnce(comment="Deactivate " + zone_name + " SAMs")
+ # z_sams_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
+ # z_sams_trig.actions.append(dcs.action.DoScript(
+ # dcs.action.String("Group.destroy(Group.getByName('" + zone_name + " Protect Static'))")))
+ # rops.m.triggerrules.triggers.append(z_sams_trig)
# Deactivate zone FARPs and player slots in defensive mode:
# this will also deactivate players already in the air.
@@ -178,24 +178,31 @@ def triggerSetup(rops, options):
dcs.action.String("RotorOps.spawnTranspHelos(8," + str(options["transport_drop_qty"]) + ")")))
rops.m.triggerrules.triggers.append(z_weak_trig)
+ # Add enemy CAP spawn trigger
+ cap_trig = dcs.triggers.TriggerContinious(comment="Spawn Enemy CAP")
+ cap_trig.rules.append(dcs.condition.TimeAfter(10))
+ cap_trig.rules.append(dcs.condition.Predicate(dcs.action.String("return RotorOps.predSpawnRedCap()")))
+ cap_trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.deployFighters()")))
+ rops.m.triggerrules.triggers.append(cap_trig)
+
# Add game won/lost triggers
- # Add game won triggers
- trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON")
- trig.rules.append(dcs.condition.FlagEquals(game_flag, 99))
+ # Add game won triggers
+ trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON")
+ trig.rules.append(dcs.condition.FlagEquals(game_flag, 99))
+ trig.actions.append(
+ dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is WON")))
+ if options["end_trigger"] is not False:
trig.actions.append(
- dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is WON")))
- if options["end_trigger"] is not False:
- trig.actions.append(
- dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)")))
- rops.m.triggerrules.triggers.append(trig)
+ dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)")))
+ rops.m.triggerrules.triggers.append(trig)
- # Add game lost triggers
- trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST")
- trig.rules.append(dcs.condition.FlagEquals(game_flag, 98))
- trig.actions.append(
- dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST")))
- if options["end_trigger"] is not False:
- trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)")))
- rops.m.triggerrules.triggers.append(trig)
\ No newline at end of file
+ # Add game lost triggers
+ trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST")
+ trig.rules.append(dcs.condition.FlagEquals(game_flag, 98))
+ trig.actions.append(
+ dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST")))
+ if options["end_trigger"] is not False:
+ trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)")))
+ rops.m.triggerrules.triggers.append(trig)
\ No newline at end of file
diff --git a/Generator/RotorOpsImport.py b/Generator/RotorOpsImport.py
index b5c347d..c1dcbea 100644
--- a/Generator/RotorOpsImport.py
+++ b/Generator/RotorOpsImport.py
@@ -126,6 +126,7 @@ class ImportObjects:
group.units[0].heading)
# ng.units[0].livery_id = group.units[0].livery_id
+ ng.units[0].name = dest_name + " " + group.units[i].name
new_groups.append(ng)
else:
@@ -211,6 +212,7 @@ class ImportObjects:
group.units[0].heading)
unit_count = unit_count + 1
# new_group.units[0].livery_id = group.units[0].livery_id
+ new_group.units[0].name = dest_name + " " + group.units[i].name
else:
diff --git a/Generator/RotorOpsMission.py b/Generator/RotorOpsMission.py
index 3e054b6..18eb338 100644
--- a/Generator/RotorOpsMission.py
+++ b/Generator/RotorOpsMission.py
@@ -32,6 +32,10 @@ class RotorOpsMission:
self.res_map = {}
self.config = None # not used
self.imports = None
+ self.red_zones = {}
+ self.blue_zones = {}
+ self.primary_e_airport = None
+
class RotorOpsZone:
def __init__(self, name: str, flag: int, position: dcs.point, size: int):
@@ -215,11 +219,11 @@ class RotorOpsMission:
elif zone.name.rfind("SPAWN") >= 0:
self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
- blue_zones = self.staging_zones
- red_zones = self.conflict_zones
+ self.blue_zones = self.staging_zones
+ self.red_zones = self.conflict_zones
if options["defending"]:
- blue_zones = self.conflict_zones
- red_zones = self.staging_zones
+ self.blue_zones = self.conflict_zones
+ self.red_zones = self.staging_zones
# swap airport sides
self.swapSides(options)
@@ -229,119 +233,10 @@ class RotorOpsMission:
if options["player_hotstart"]:
start_type = dcs.mission.StartType.Warm
- # Adds vehicles as a single group (for easy late activation), and helicopters if enabled in settings
- # def addZoneFARP(_zone_name, country, file):
- #
- # farp_flag = self.m.find_group(_zone_name)
- #
- # if farp_flag:
- # farp_position = farp_flag.units[0].position
- # farp_heading = farp_flag.units[0].heading
- # else:
- # farp_position = self.all_zones[_zone_name].position
- # farp_heading = 0
- #
- # # Add the basic invisible farp object
- # farp = self.m.farp(self.m.country(country), _zone_name + " FARP", farp_position,
- # hidden=False, dead=False,
- # farp_type=dcs.unit.InvisibleFARP)
- #
- # # Use alternate template file if it has been defined in scenario config
- # if options["zone_farp_file"]:
- #
- # for i in imports:
- # if i.filename.removesuffix('.miz') == options["zone_farp_file"]:
- # file = i.path
- # # if multiple files found, we want the latest file to override the first
- #
- # i = ImportObjects(file)
- # i.anchorByGroupName("ANCHOR")
- # farp_group = i.copyVehiclesAsGroup(self.m, country, _zone_name + " FARP Static", farp_position,
- # farp_heading)
- # # Add client helicopters
- # if options["farp_spawns"]:
- # helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", farp_position, farp_heading)
- # for group in helicopter_groups:
- # self.all_zones[_zone_name].player_helo_spawns.append(group)
- #
- # return farp_group
- # # Adds statics, vehicles, and helicopters. Late activation is not possible
- # def addLogisticsZone(_zone_name, country, file, config_name, helicopters=False):
- # flag = self.m.find_group(_zone_name)
- # if flag:
- # position = flag.units[0].position
- # heading = flag.units[0].heading
- # else:
- # position = self.all_zones[_zone_name].position
- # heading = 0
- #
- # # Use alternate template file if it has been defined in scenario config
- # if options[config_name]:
- #
- # for i in imports:
- # if i.filename.removesuffix('.miz') == options[config_name]:
- # file = i.path
- # # if multiple files found, we want the latest file to override the first
- #
- # # Import statics and vehicles
- # i = ImportObjects(file)
- # i.anchorByGroupName("ANCHOR")
- # i.copyStatics(self.m, country, _zone_name + " Logistics Zone",
- # position, heading)
- # i.copyVehicles(self.m, country, _zone_name + " Logistics Zone",
- # position, heading)
- #
- # # Add client helicopters
- # if helicopters:
- # helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", position,
- # heading)
- # for group in helicopter_groups:
- # self.all_zones[_zone_name].player_helo_spawns.append(group)
-
- # Adds statics, vehicles, and helicopters (if enabled). Late activation is not possible.
- # def addDefensiveFARP(_zone_name, country, file):
- #
- # farp_flag = self.m.find_group(_zone_name)
- #
- # if farp_flag:
- # farp_position = farp_flag.units[0].position
- # farp_heading = farp_flag.units[0].heading
- # else:
- # farp_position = self.all_zones[_zone_name].position
- # farp_heading = 0
- #
- # # Add the basic invisible farp object
- # farp = self.m.farp(self.m.country(country), _zone_name + " FARP", farp_position,
- # hidden=False, dead=False,
- # farp_type=dcs.unit.InvisibleFARP)
- #
- # # Use alternate template file if it has been defined in scenario config
- # if options["defensive_farp_file"]:
- #
- # for i in imports:
- # if i.filename.removesuffix('.miz') == options["defensive_farp_file"]:
- # file = i.path
- # # if multiple files found, we want the latest file to override the first
- #
- # # Import statics and vehicles
- # i = ImportObjects(file)
- # i.anchorByGroupName("ANCHOR")
- # i.copyStatics(self.m, country, _zone_name + " Logistics Zone",
- # farp_position, farp_heading)
- # i.copyVehicles(self.m, country, _zone_name + " Logistics Zone",
- # farp_position, farp_heading)
- #
- # # Import player helicopters
- # if options["farp_spawns"]:
- # helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", farp_position,
- # farp_heading)
- # for group in helicopter_groups:
- # self.all_zones[_zone_name].player_helo_spawns.append(group)
-
- for zone_name in red_zones:
+ for zone_name in self.red_zones:
if red_forces["vehicles"]:
- self.addGroundGroups(red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"],
+ self.addGroundGroups(self.red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"],
options["red_quantity"])
if options["zone_farps"] != "farp_never" and not options["defending"]:
@@ -367,34 +262,21 @@ class RotorOpsMission:
)
vehicle_group.late_activation = True
- # For SAMs: Add vehicles as a single group (for easy late activation)
- if options["zone_protect_sams"]:
+
+ if options["advanced_defenses"]:
sam_group = self.addZoneBase(options, zone_name, jtf_red,
file=zone_protect,
config_name="zone_protect_file",
copy_vehicles=True,
- vehicles_name=zone_name + " Protect Static",
- vehicles_single_group=True
+ vehicles_name=zone_name + " Defense Static",
+ vehicles_single_group=False
)
- # farp_flag = self.m.find_group(zone_name)
- #
- # if farp_flag:
- # farp_position = farp_flag.units[0].position
- # farp_heading = farp_flag.units[0].heading
- # else:
- # farp_position = self.all_zones[zone_name].position
- # farp_heading = 0
- #
- # i = ImportObjects(zone_protect)
- # i.anchorByGroupName("ANCHOR")
- # farp_group = i.copyVehiclesAsGroup(self.m, jtf_red, "Static " + zone_name + " Protection SAM",
- # farp_position,
- # farp_heading)
+
# Populate Blue zones with ground units
- for i, zone_name in enumerate(blue_zones):
+ for i, zone_name in enumerate(self.blue_zones):
if blue_forces["vehicles"]:
- self.addGroundGroups(blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"],
+ self.addGroundGroups(self.blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"],
options["blue_quantity"])
# Add blue zone FARPS (not late activated) for defensive mode
@@ -404,7 +286,7 @@ class RotorOpsMission:
if options["farp_spawns"]:
helicopters = True
- if options["crates"] and i == len(blue_zones) - 1:
+ if options["crates"] and i == len(self.blue_zones) - 1:
# add a logistics zone to the last conflict zone
# addLogisticsZone(zone_name, jtf_blue, logistics_farp, "logistics_farp_file", helicopters)
self.addZoneBase(options, zone_name, jtf_blue,
@@ -458,7 +340,9 @@ class RotorOpsMission:
# Add player slots
window.statusBar().showMessage("Adding flights to mission...", 10000)
- if options["slots"] != "Locked to Scenario" and options["slots"] != "None":
+ if options["slots"] == "Locked to Scenario" or options["slots"] == "None":
+ pass
+ else:
self.addPlayerHelos(options)
# Add AI Flights
@@ -728,7 +612,7 @@ class RotorOpsMission:
for helicopter in dcs.helicopters.helicopter_map:
if helicopter == options["slots"]:
client_helos = [dcs.helicopters.helicopter_map[
- helicopter]] # if out ui slot option matches a specific helicopter type name
+ helicopter]] # if our ui slot option matches a specific helicopter type name
# get loadouts from miz file and put into a simple dict
default_loadouts = {}
@@ -782,6 +666,8 @@ class RotorOpsMission:
helotype = None
if helicopter_id in dcs.helicopters.helicopter_map:
helotype = dcs.helicopters.helicopter_map[helicopter_id]
+ elif helicopter_id in dcs.planes.plane_map:
+ helotype = dcs.planes.plane_map[helicopter_id]
else:
continue
if carrier:
@@ -871,7 +757,7 @@ class RotorOpsMission:
heading = enemy_heading + random.randrange(70, 110)
race_dist = random.randrange(40 * 1000, 80 * 1000)
center_pt = dcs.mapping.point_from_heading(friendly_pt.x, friendly_pt.y,
- enemy_heading - random.randrange(140, 220), 10000)
+ enemy_heading - random.randrange(140, 220), 20000)
pt1 = dcs.mapping.point_from_heading(center_pt[0], center_pt[1], enemy_heading - 90,
random.randrange(20 * 1000, 40 * 1000))
return dcs.mapping.Point(pt1[0], pt1[1], terrain), heading, race_dist
@@ -882,6 +768,7 @@ class RotorOpsMission:
friendly_airports, primary_f_airport = self.getCoalitionAirports("blue")
enemy_airports, primary_e_airport = self.getCoalitionAirports("red")
+
# find enemy carriers and farps
carrier = self.m.country(jtf_red).find_ship_group(name="HELO_CARRIER")
if not carrier:
@@ -901,6 +788,27 @@ class RotorOpsMission:
primary_f_airport.position.y
)
+ self.primary_e_airport = primary_e_airport
+ self.m.triggers.add_triggerzone(primary_e_airport.position, 1500, hidden=True, name="RED_AIRBASE")
+
+ if options["red_cap"]:
+ scenario_red_cap_spawn_zone = None
+ for zone in self.m.triggers.zones():
+ if zone.name == "RED_CAP_SPAWN":
+ scenario_red_cap_spawn_zone = True
+ if not scenario_red_cap_spawn_zone:
+ e_cap_spawn_point = primary_e_airport.position.point_from_heading(e_airport_heading, 100000)
+ self.m.triggers.add_triggerzone(e_cap_spawn_point, 30000, hidden=True, name="RED_CAP_SPAWN")
+
+ if options["blue_cap"]:
+ scenario_blue_cap_spawn_zone = None
+ for zone in self.m.triggers.zones():
+ if zone.name == "BLUE_CAP_SPAWN":
+ scenario_blue_cap_spawn_zone = True
+ if not scenario_blue_cap_spawn_zone:
+ f_cap_spawn_point = primary_f_airport.position.point_from_heading(e_airport_heading + 180, 100000)
+ self.m.triggers.add_triggerzone(f_cap_spawn_point, 30000, hidden=True, name="BLUE_CAP_SPAWN")
+
if options["f_awacs"]:
awacs_name = "AWACS"
awacs_freq = 266
@@ -987,6 +895,7 @@ class RotorOpsMission:
t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n\n"
self.m.set_description_text(briefing)
+
def zone_attack(fg, airport):
fg.set_skill(dcs.unit.Skill.High)
fg.late_activation = True
@@ -1118,6 +1027,139 @@ class RotorOpsMission:
unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id
+ if False:
+ for i in range(1,4):
+ randzone = random.choice(list(self.red_zones))
+ pt2 = self.red_zones[randzone].position
+ source_plane = None
+ if red_forces["fighter_planes"]:
+ source_group = random.choice(red_forces["fighter_planes"])
+ source_plane = source_group.units[0]
+ plane_type = source_plane.unit_type
+ group_size = random.randrange(1, 2)
+
+ else:
+ group_size = random.randrange(1, 2)
+ plane_type = random.choice(RotorOpsUnits.e_attack_helos)
+
+ airport = self.getParking(primary_e_airport, plane_type, enemy_airports, group_size)
+
+ enemy_cap = self.m.patrol_flight(airport=airport,
+ name="Enemy CAP " + str(i),
+ country=combinedJointTaskForcesRed,
+ patrol_type=plane_type,
+ pos1=primary_e_airport.position,
+ pos2=pt2,
+ altitude=3000,
+ group_size=group_size,
+ max_engage_distance=40 * 1000
+ )
+
+ # enemy_cap.points[0].tasks[0] = dcs.task.EngageTargets(max_engage_distance, [dcs.task.Targets.All.Air.Planes])
+
+ for unit in enemy_cap.units:
+ unit.skill = dcs.unit.Skill.Random
+
+ if source_plane:
+ for unit in enemy_cap.units:
+ unit.pylons = source_plane.pylons
+ unit.livery_id = source_plane.livery_id
+
+ if options["red_cap"]:
+
+ if red_forces["fighter_planes"]:
+ for fighter_plane_group in red_forces["fighter_planes"]:
+ source_group = random.choice(red_forces["fighter_planes"])
+ source_plane = source_group.units[0]
+ plane_type = source_plane.unit_type
+
+ enemy_cap = self.m.flight_group(
+ country=combinedJointTaskForcesRed,
+ name="RED CAP",
+ aircraft_type=plane_type,
+ airport=None,
+ maintask=dcs.task.CAP,
+ group_size=1,
+ position=e_cap_spawn_point,
+ altitude=5000,
+ speed=300
+ )
+
+ enemy_cap.late_activation = True
+
+ for unit in enemy_cap.units:
+ unit.skill = dcs.unit.Skill.Random
+ unit.pylons = source_plane.pylons
+ unit.livery_id = source_plane.livery_id
+
+ else:
+ plane_type = random.choice(RotorOpsUnits.e_fighter_planes)
+
+ enemy_cap = self.m.flight_group(
+ country=combinedJointTaskForcesRed,
+ name="RED CAP",
+ aircraft_type=plane_type,
+ airport=None,
+ maintask=dcs.task.CAP,
+ group_size=1,
+ position=e_cap_spawn_point,
+ altitude=5000,
+ speed=300
+ )
+
+ enemy_cap.late_activation = True
+
+ for unit in enemy_cap.units:
+ unit.skill = dcs.unit.Skill.Random
+
+ if options["blue_cap"]:
+
+ if blue_forces["fighter_planes"]:
+ for fighter_plane_group in blue_forces["fighter_planes"]:
+ source_group = random.choice(blue_forces["fighter_planes"])
+ source_plane = source_group.units[0]
+ plane_type = source_plane.unit_type
+
+ friendly_cap = self.m.flight_group(
+ country=combinedJointTaskForcesBlue,
+ name="BLUE CAP",
+ aircraft_type=plane_type,
+ airport=None,
+ maintask=dcs.task.CAP,
+ group_size=1,
+ position=f_cap_spawn_point,
+ altitude=5000,
+ speed=300
+ )
+
+ friendly_cap.late_activation = True
+
+ for unit in friendly_cap.units:
+ unit.skill = dcs.unit.Skill.Random
+ unit.pylons = source_plane.pylons
+ unit.livery_id = source_plane.livery_id
+
+ else:
+ plane_type = random.choice(RotorOpsUnits.f_fighter_planes)
+
+ friendly_cap = self.m.flight_group(
+ country=combinedJointTaskForcesBlue,
+ name="BLUE CAP",
+ aircraft_type=plane_type,
+ airport=None,
+ maintask=dcs.task.CAP,
+ group_size=1,
+ position=f_cap_spawn_point,
+ altitude=5000,
+ speed=300
+ )
+
+ friendly_cap.late_activation = True
+
+ for unit in friendly_cap.units:
+ unit.skill = dcs.unit.Skill.Random
+
+
def importObjects(self, data):
imports = data["objects"]["imports"]
diff --git a/Generator/RotorOpsUnits.py b/Generator/RotorOpsUnits.py
index e0449f7..b92a9b6 100644
--- a/Generator/RotorOpsUnits.py
+++ b/Generator/RotorOpsUnits.py
@@ -20,6 +20,7 @@ player_helos = [
dcs.helicopters.SA342Mistral,
dcs.helicopters.UH_1H,
aircraftMods.UH_60L,
+ dcs.planes.AV8BNA,
]
e_attack_helos = [
@@ -39,6 +40,14 @@ e_attack_planes = [
dcs.planes.A_10C,
]
+e_fighter_planes = [
+ dcs.planes.Su_27,
+]
+
+f_fighter_planes = [
+ dcs.planes.FA_18C_hornet,
+]
+
e_zone_sams = [
dcs.vehicles.AirDefence.Strela_10M3,
]
\ No newline at end of file
diff --git a/Generator/requirements.txt b/Generator/requirements.txt
index 43c09aa..3dd3e04 100644
Binary files a/Generator/requirements.txt and b/Generator/requirements.txt differ
diff --git a/config/blue_player_loadouts.miz b/config/blue_player_loadouts.miz
index db72de0..918a429 100644
Binary files a/config/blue_player_loadouts.miz and b/config/blue_player_loadouts.miz differ
diff --git a/scripts/RotorOps.lua b/scripts/RotorOps.lua
index eb05725..ccdebba 100644
--- a/scripts/RotorOps.lua
+++ b/scripts/RotorOps.lua
@@ -88,6 +88,7 @@ local cooldown = {
["attack_helo_msg"] = 0,
["attack_plane_msg"] = 0,
["trans_helo_msg"] = 0,
+ ["e_fighters_inbound_msg"] = 0,
}
local zone_defenders_flags = {
'ROPS_A_DEFENDERS',
@@ -202,6 +203,9 @@ RotorOps.gameMsgs = {
transp_helos_toff = {
{'ENEMY TRANSPORT HELICOPTERS INBOUND!', 'enemy_chopper_inbound.ogg'},
},
+ enemy_fighters_inbound = {
+ {'ENEMY FIGHTERS INBOUND!', 'enemy_fighters_inbound.ogg'},
+ },
}
@@ -529,9 +533,9 @@ function RotorOps.deployTroops(quantity, target_group, announce)
debugMsg("DeployTroops on group: "..target_group_obj:getName())
local valid_unit = RotorOps.getValidUnitFromGroup(target_group_obj)
if not valid_unit then return end
- local coalition = valid_unit:getCoalition()
+ local coal = valid_unit:getCoalition()
local side = "red"
- if coalition == 2 then side = "blue" end
+ if coal == 2 then side = "blue" end
local point = valid_unit:getPoint()
ctld.spawnGroupAtPoint(side, quantity, point, 1000)
@@ -831,11 +835,11 @@ function RotorOps.shiftPosition(vars)
local search_radius = vars.radius or 100
local inner_radius = 50 --minimum distance to move for randpointincircle
local first_valid_unit
- if grp:isExist() ~= true then return end
+ if grp and grp:isExist() ~= true then return end
local start_point = vars.point
if not start_point then
- env.info("RotorOps: No point provided, getting current position.")
+ --env.info("RotorOps: No point provided, getting current position.")
for index, unit in pairs(grp:getUnits()) do
if unit:isExist() == true then
first_valid_unit = unit
@@ -865,7 +869,7 @@ function RotorOps.shiftPosition(vars)
if mist.isTerrainValid(rand_point, {'LAND', 'ROAD'}) == true then
path[#path + 1] = mist.ground.buildWP(rand_point, formation, 5)
- env.info("point is valid, adding as waypoint with formation: " .. formation)
+ --env.info("point is valid, adding as waypoint with formation: " .. formation)
break
end
@@ -885,7 +889,7 @@ function RotorOps.guardPosition(vars)
local start_point = vars.point
if not start_point then
- env.info("RotorOps: No point provided, getting current position.")
+ --env.info("RotorOps: No point provided, getting current position.")
for index, unit in pairs(grp:getUnits()) do
if unit:isExist() == true then
first_valid_unit = unit
@@ -963,6 +967,9 @@ function RotorOps.aiExecute(vars)
local last_task = vars.last_task
local last_zone = vars.last_zone
local group_name = vars.group_name
+ if not vars.group_name or not tableHasKey(RotorOps.ai_tasks, group_name) then
+ return
+ end
local task = RotorOps.ai_tasks[group_name].ai_task
local zone = RotorOps.ai_tasks[group_name].zone
local point = RotorOps.ai_tasks[group_name].point
@@ -1225,7 +1232,6 @@ function RotorOps.assessUnitsInZone(var)
percent_staged_remain = math.floor((#staged_units_remaining / #RotorOps.staged_units) * 100)
trigger.action.setUserFlag(RotorOps.staged_units_flag, percent_staged_remain)
trigger.action.setUserFlag('ROPS_ATTACKERS', percent_staged_remain)
- debugMsg("Staged units remaining percent: "..percent_staged_remain.."%")
--is the game finished?
@@ -1377,7 +1383,7 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
do
local point = trigger.misc.getZone(zone.name).point
local radius = trigger.misc.getZone(zone.name).radius
- local coalition = -1
+ local coal = -1
local id = index --this must be UNIQUE!
local color = {1, 1, 1, 0.5}
local fill_color = {1, 1, 1, 0.1}
@@ -1392,11 +1398,11 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
fill_color = {1, 0, 0, 0.05}
end
if previous_point ~= nill then
- --trigger.action.lineToAll(coalition, id + 200, point, previous_point, color, line_type)
+ --trigger.action.lineToAll(coal, id + 200, point, previous_point, color, line_type)
end
previous_point = point
- trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
- trigger.action.textToAll(coalition, id + 100, point, color, text_fill_color, font_size, read_only, text)
+ trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
+ trigger.action.textToAll(coal, id + 100, point, color, text_fill_color, font_size, read_only, text)
end
for index, cpz in pairs(ctld.pickupZones) do
@@ -1407,14 +1413,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
local ctld_zone_status = cpz[4]
local point = pickup_zone.point
local radius = pickup_zone.radius
- local coalition = -1
+ local coal = -1
local id = index + 150 --this must be UNIQUE!
local color = {1, 1, 1, 0.5}
local fill_color = {0, 0.8, 0, 0.1}
local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
env.info("pickup zone is active, drawing it to the map")
- trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
+ trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
end
end
end
@@ -1429,14 +1435,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
-- local ctld_zone_status = c_zone[4]
-- local point = trigger.misc.getZone(pickup_zone).point
-- local radius = trigger.misc.getZone(pickup_zone).radius
- -- local coalition = -1
+ -- local coal = -1
-- local id = index + 150 --this must be UNIQUE!
-- local color = {1, 1, 1, 0.5}
-- local fill_color = {0, 0.8, 0, 0.1}
-- local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
-- if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
-- --debugMsg("draw the pickup zone")
- -- trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
+ -- trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
-- end
-- end
-- end
@@ -1874,7 +1880,7 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
gp.route.points[#gp.route.points + 1] = mist.heli.buildWP(initial_point, 'flyover', 100, 400, 'agl')
gp.clone = true
local new_group_data = mist.dynAdd(gp) --returns a mist group data table
- debugTable(new_group_data)
+ --debugTable(new_group_data)
-- local new_group = Group.getByName(new_group_data.groupName)
-- local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
-- grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
@@ -1885,6 +1891,275 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
end
+function RotorOps.spawnCapToZone(_target_zone, _spawn_zone, coal)
+ local target_zone = _target_zone
+ if not target_zone then
+ target_zone = RotorOps.getEnemyZones()[math.random(1, #RotorOps.getEnemyZones())]
+ end
+ local zone_point = trigger.misc.getZone(target_zone).point
+ RotorOps.spawnCap(zone_point, _spawn_zone, coal)
+end
+
+RotorOps.fighter_red_source_string = "RED CAP"
+RotorOps.fighter_blue_source_string = "BLUE CAP"
+RotorOps.fighter_engagement_dist = 20
+
+function RotorOps.spawnCap(destination_point, _spawn_zone, coal)
+ local red_zone_string = "RED_CAP_SPAWN"
+ local blue_zone_string = "BLUE_CAP_SPAWN"
+
+ local coal_zone_string = nil
+ if not coal or coal == 0 then
+ return
+ end
+ if coal == 1 then
+ coal_zone_string = red_zone_string
+ source_group_string = RotorOps.fighter_red_source_string
+ end
+ if coal == 2 then
+ coal_zone_string = blue_zone_string
+ source_group_string = RotorOps.fighter_blue_source_string
+ end
+
+ local spawn_zone = _spawn_zone
+ if not _spawn_zone then
+ local spawn_zones = {}
+ for zone, zoneobj in pairs(mist.DBs.zonesByName) do
+ if string.find(zone, coal_zone_string) then
+ spawn_zones[#spawn_zones + 1] = zone
+ --env.info("found cap spawn zone: " .. zone)
+ end
+ end
+ if #spawn_zones < 1 then
+ return
+ end
+ spawn_zone = spawn_zones[math.random(1, #spawn_zones)]
+ end
+
+ local spawn_point = mist.getRandomPointInZone(spawn_zone)
+
+
+ local altitude = math.random(2000,6000)
+ local speed = 300
+
+
+ --pick a template group at random for the source
+ fighter_groups = {} --stores group names of template groups
+ for uName, uData in pairs(mist.DBs.groupsByName) do
+ if string.find(uName, source_group_string) then
+ fighter_groups[#fighter_groups + 1] = uName
+ end
+ end
+
+ if #fighter_groups < 1 then
+ return
+ end
+
+ fighter_group_name = fighter_groups[math.random(1, #fighter_groups)]
+ local group = Group.getByName(fighter_group_name)
+
+ if not group then
+ return
+ end
+
+ local gp = mist.getGroupData(fighter_group_name)
+ --debugTable(gp)
+
+ gp.units[1].alt = altitude
+ gp.units[1].speed = speed
+ gp.units[1].x = spawn_point.x
+ gp.units[1].y = spawn_point.y
+ gp.units[1].heading = mist.utils.getHeadingPoints(spawn_point, destination_point)
+
+
+
+ local engage = {
+ id = 'EngageTargets',
+ params = {
+ maxDist = RotorOps.fighter_engagement_dist,
+ maxDistEnabled = true,
+ targetTypes = { [1] = "Air" },
+ }
+ }
+
+ local orbit = {
+ id = 'Orbit',
+ params = {
+ pattern = 'Race-Track',
+ }
+ }
+
+
+ gp.route = {points = {}}
+ -- gp.route[1] = mist.fixedWing.buildWP(random_airbase:getPoint())
+ -- gp.route[1].type = "TakeOffParking"
+ -- gp.route[1].action = "From Parking Area"
+ -- gp.route[1].airdromeId = airbase_id
+
+ gp.route.points[1] = mist.fixedWing.buildWP(spawn_point, 'turning point', speed, altitude, 'baro')
+
+ gp.route.points[1].task = {}
+ gp.route.points[1].task.id = 'ComboTask'
+ gp.route.points[1].task.params = {}
+ gp.route.points[1].task.params.tasks = {}
+ gp.route.points[1].task.params.tasks[1] = {number = 1, id = 'ControlledTask', enabled = true, params = {task = engage}}
+ gp.route.points[1].task.params.tasks[2] = {number = 2, id = 'ControlledTask', enabled = true, params = {task = orbit}}
+
+ gp.route.points[2] = mist.fixedWing.buildWP(destination_point, 'turning point', speed, altitude, 'baro')
+
+ gp.clone = true
+ local new_group_data = mist.dynAdd(gp) --returns a mist group data table
+ --debugTable(new_group_data)
+ local new_group = Group.getByName(new_group_data.name)
+ if new_group then
+ env.info("RotorOps spawned CAP: "..new_group_data.name)
+ else
+ env.error("RotorOps tried to spawn CAP but something went wrong.")
+ return
+ end
+
+ local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
+ grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+ grp_controller:setOption(AI.Option.Air.id.FLARE_USING , AI.Option.Air.val.FLARE_USING.WHEN_FLYING_NEAR_ENEMIES)
+ grp_controller:setOption(AI.Option.Air.id.ROE , AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE)
+ grp_controller:setOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_SEARCH_IF_REQUIRED)
+
+ return new_group_data.name
+
+end
+
+--fighter variables
+local fighters_by_detected_unitname = {}
+RotorOps.fighter_radar_unit_string = 'FIGHTER_DEPLOYMENT' --any unit capable of detecting aircraft by radar can be used as a detection source to spawn intercept fighters, if this string is in the unit name
+RotorOps.fighter_min_detection_alt = 305 --aircraft below this agl altitude (meters) will not be 'detected' by radar units.
+RotorOps.fighter_max_detection_dist = 7000 --default max range from radar to target in order for intercept fighters to spawn (you can also set range for individual radar sources via unit name)
+RotorOps.fighter_max_active = 8 --total maximum active deployed fighters, shared between red/blue
+
+function RotorOps.deployFighters()
+ local function spawn(dest_point, target_unit, coal)
+ fighter = RotorOps.spawnCap(dest_point, nil, coal)
+
+ if fighter and #fighters_by_detected_unitname < RotorOps.fighter_max_active then
+ _spawn_time = RotorOps.getTime()
+ fighters_by_detected_unitname[target_unit] = {
+ name = fighter,
+ spawn_time = _spawn_time,
+ rtb_time = math.random(_spawn_time + (15 * 60), _spawn_time + (25 * 60)),
+ respawn_time = math.random(_spawn_time + (5 * 60), _spawn_time + (15 * 60)),
+ }
+ if ((RotorOps.getTime() - cooldown["e_fighters_inbound_msg"]) > 90) then
+ RotorOps.gameMsg(RotorOps.gameMsgs.enemy_fighters_inbound)
+ cooldown["e_fighters_inbound_msg"] = RotorOps.getTime()
+ end
+ --debugTable(fighters_by_detected_unitname)
+ env.info(target_unit .. " was detected and we spawned a new fighter group: " .. fighter)
+ end
+ end
+
+ local function rtb(group_name)
+
+ local grp = Group.getByName(group_name)
+ if grp then
+ local coal_airbases = coalition.getAirbases(grp:getCoalition())
+ --debugTable(coal_airbases)
+ random_airbase = coal_airbases[math.random(1, #coal_airbases)]
+
+ local airbase_pos = mist.utils.makeVec2(random_airbase:getPoint())
+ local airbase_id = random_airbase:getID()
+ local rtb = {
+ id = 'Mission',
+ params = {
+ route = {
+ points = {
+ [1] = {
+ alt = 2000,
+ alt_type = "RADIO",
+ speed = 300,
+ x = airbase_pos.x,
+ y = airbase_pos.y,
+ aerodromeId = airbase_id,
+ type = "Land",
+ action = "Landing",
+ }
+ }
+ }
+ }
+ }
+
+ grp:getController():setTask(rtb)
+ env.info(group_name .. " is RTB to ".. random_airbase:getName())
+ end
+ end
+
+
+ --fighter respawning and rtb
+ for target_name, fighter_group_data in pairs(fighters_by_detected_unitname) do
+ local group = Group.getByName(fighter_group_data.name)
+ if group then --if group alive
+ if fighter_group_data.rtb_time < RotorOps.getTime() then
+ env.info(fighter_group_data.name .. " is RTB. Removing from table.")
+ rtb(fighter_group_data.name)
+ fighters_by_detected_unitname[target_name] = nil
+ end
+ else --if group dead
+ if fighter_group_data.respawn_time < RotorOps.getTime() then
+ env.info(fighter_group_data.name .. " has hit respawn_time limit. Removing from table to allow another group to spawn.")
+ fighters_by_detected_unitname[target_name] = nil
+ end
+ end
+ end
+
+
+
+
+ for uName, uData in pairs(mist.DBs.unitsByName) do
+ local str_index = string.find(uName, RotorOps.fighter_radar_unit_string)
+ if str_index then
+ --trigger.action.outText("Found radar unit: " .. uData.unitName, 2)
+ local radar_unit = Unit.getByName(uData.unitName)
+ local max_distance = RotorOps.fighter_max_detection_dist
+ local dist_str = string.sub(uName, str_index + #RotorOps.fighter_radar_unit_string + 1)
+ if #dist_str > 3 then
+ --env.info("Radar unit name has the max detection distance property:".. dist_str)
+ max_distance = tonumber(dist_str)
+ end
+
+ if radar_unit and radar_unit:getLife() > 0 then
+ --trigger.action.outText(uData.unitName .. " is searching for targets. life=" .. radar_unit:getLife(), 2)
+
+ raw_detected_units = radar_unit:getController():getDetectedTargets(Controller.Detection.RADAR)
+ if raw_detected_units then
+ for i, target in pairs(raw_detected_units) do
+ --debugTable(target)
+ if target.object then
+ local detected_unitname = target.object:getName()
+ local target_pos = target.object:getPosition().p
+ local target_distance = mist.utils.get2DDist(radar_unit:getPosition().p, target_pos)
+ local terrain_height = land.getHeight({x = target_pos.x, y = target_pos.z})
+ local target_agl = target_pos.y - terrain_height
+
+ --trigger.action.outText(uData.unitName .. "detected " .. detected_unitname .. " at " .. target_distance .. " agl:" .. target_agl, 2)
+
+ if target_distance <= max_distance and target_agl >= RotorOps.fighter_min_detection_alt then
+ --trigger.action.outText(uData.unitName .. " has detected "..detected_unitname, 2)
+
+ if tableHasKey(fighters_by_detected_unitname, detected_unitname) then
+ --trigger.action.outText(detected_unitname .. " already in table with " .. fighters_by_detected_unitname[detected_unitname], 2)
+
+ else
+ spawn(target_pos, detected_unitname, radar_unit:getCoalition())
+ end
+
+ end
+ end --end if target.object
+ end --end of raw_detected targets loop
+ end
+
+ end --end of radar_unit
+ end
+ end --end of all units by name loop
+
+end
--- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls)
@@ -1928,3 +2203,8 @@ function RotorOps.predPlayerInZone(zone_name)
end
end
+--determine if enemy CAP is needed
+function RotorOps.predSpawnRedCap()
+ return true
+end
+