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 +