For users:
-Added KA-50 III and AV8BNA Harrier to slot selection
-Changed message in mission generated success dialog
-Zone protect SAMs now part of 'Advanced Defenses' feature
-Late activated friendly/enemy CAP units are placed in mission as a template for Deployed CAP fighters (ie will not be active unless using Advanced Defenses or 'DEPLOY_FIGHTERS' name for radar ground unit)
-improve idle troop behavior at bases/FARPs

For Mission creators:
-Updated pydcs library supports new units such as technicals
-Updated pydcs library supports Falklands map
-allow troop pickup from HELO_CARRIER
-enemy units with radar can be designated to deploy intercept fighters on detection (see RotorOps.fighter options in RotorOps.lua for details) with options for min detection altitude and distance (min detection altitude allows helis to fly 'under the radar')
-Insert RotorOpsServer.lua script and trigger actions if option set in scenario config: rotorops_server: true
-scenario template triggers should now be 'untouched' after mission generation, allowing previously unsupported triggers and actions to be used, along with color coding
-block adding player helicopters if slots locked in scenario config
-Added RotorOps.draw_conflict_zones setting to give users the ability to disable or enable displaying of zones on the map.
-allow disabling spinboxes in scenario config
-mission ends 10 mins after mission success/fail
-copy helicopter start type from templates

Internal:
-github actions workflow to automatically deploy to update server
-Startup version check will ignore micro version
-bypassing triggers and merging before save (to preserve unsupported triggers in pydcs). Our goal is to leave the trigrules and trig from the source mission untouched
-if using random weather, set ice halo to auto and crystals to none
-dont put planes at airports without ILS (to avoid putting planes at helicopter airports ie. Syria)
-improved guardPosition task
-refactored 'coalition' variables to 'coal' to help prevent introducing errors in RotorOps.lua
This commit is contained in:
spencershepard
2023-01-03 12:05:18 -08:00
committed by GitHub
parent fa02707e23
commit f382a2e3cc
24 changed files with 1070 additions and 396 deletions

View File

@@ -2,15 +2,16 @@ import json
import yaml
import sys
import os
import operator
import RotorOpsMission as ROps
import RotorOpsUnits
import version
import user
import logging
import requests
from packaging import version
from packaging import version as ver
from PyQt5.QtWidgets import (
QApplication, QDialog, QMainWindow, QMessageBox, QCheckBox, QSpinBox, QSplashScreen, QFileDialog, QRadioButton,
@@ -19,7 +20,6 @@ from PyQt5.QtWidgets import (
from PyQt5 import QtGui
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import QObject, QEvent, Qt, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
import resources # pyqt resource file
from MissionGeneratorUI import Ui_MainWindow
@@ -27,20 +27,15 @@ from MissionGeneratorUI import Ui_MainWindow
import qtmodern.styles
import qtmodern.windows
# UPDATE BUILD VERSION
maj_version = 1
minor_version = 2
patch_version = 0
modules_version = 2
modules_url = 'https://dcs-helicopters.com/user-files/modules/'
version_url = 'https://dcs-helicopters.com/app-updates/versions.yaml'
modules_map_url = 'https://dcs-helicopters.com/user-files/modules/module-map-v2.yaml'
ratings_url = 'https://dcs-helicopters.com/user-files/ratings.php'
allowed_paths = ['templates\\Scenarios\\downloaded', 'templates\\Forces\\downloaded', 'templates\\Imports\\downloaded']
user_files_url = 'https://dcs-helicopters.com/user-files/'
version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'
version.version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'
#Setup logfile and exception handler
logger = logging.getLogger(__name__)
@@ -102,17 +97,16 @@ def handle_exception(exc_type, exc_value, exc_traceback):
sys.excepthook = handle_exception
version_string = str(maj_version) + "." + str(minor_version) + "." + str(patch_version)
defenders_text = "Defending Forces:"
attackers_text = "Attacking Forces:"
ratings_json = None
logger.info("RotorOps v" + version_string)
logger.info("RotorOps v" + version.version_string)
# Try to set windows app ID to display taskbar icon properly
try:
from ctypes import windll
appid = 'RotorOps.MissionGenerator.' + version_string
appid = 'RotorOps.MissionGenerator.' + version.version_string
windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
except ImportError:
pass
@@ -156,7 +150,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.statusbar = self.statusBar()
self.statusbar.setStyleSheet(
"QStatusBar{padding-left:5px;}")
self.version_label.setText("Version " + version_string)
self.version_label.setText("Version " + version.version_string)
self.scenarioChanged()
@@ -421,6 +415,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 +570,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 +587,10 @@ 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),
"rotorops_server": self.scenario.getConfigValue("rotorops_server", default=False),
}
logger.info("Generating mission with options:")
@@ -606,11 +609,9 @@ class Window(QMainWindow, Ui_MainWindow):
msg.setText("Awesome, your mission is ready! It's located in this directory: \n" +
result["directory"] + "\n" +
"\n" +
"Next, you should use the DCS Mission Editor to fine tune unit placements. Don't be afraid to edit the missions that this generator produces. \n" +
"You MUST use the DCS Mission Editor to open the mission, or else it may not work correctly. Save the mission or launch it directly from the editor.\n" +
"\n" +
"There are no hidden script changes, everything is visible in the ME. Triggers have been created to help you to add your own actions based on active zone and game status. \n" +
"\n" +
"Units can be changed or moved without issue. Player slots can be changed or moved without issue (one per group though!) \n" +
"It's also highly recommended to fine-tune ground unit placements.\n" +
"\n" +
"Don't forget, you can also create your own templates that can include any mission options, objects, or even scripts. \n" +
"\n" +
@@ -632,20 +633,20 @@ class Window(QMainWindow, Ui_MainWindow):
# works fine but no use for this currently
class myWebView(QDialog):
def __init__(self, window, parent=None):
QDialog.__init__(self, parent)
vbox = QVBoxLayout(self)
self.webEngineView = QWebEngineView()
self.webEngineView.load(QUrl('https://dcs-helicopters.com'))
vbox.addWidget(self.webEngineView)
self.setLayout(vbox)
self.setGeometry(600, 600, 700, 500)
self.setWindowTitle('QWebEngineView')
# class myWebView(QDialog):
# def __init__(self, window, parent=None):
# QDialog.__init__(self, parent)
# vbox = QVBoxLayout(self)
#
# self.webEngineView = QWebEngineView()
# self.webEngineView.load(QUrl('https://dcs-helicopters.com'))
#
# vbox.addWidget(self.webEngineView)
#
# self.setLayout(vbox)
#
# self.setGeometry(600, 600, 700, 500)
# self.setWindowTitle('QWebEngineView')
class slotDialog(QDialog):
def __init__(self, window, parent=None):
@@ -770,10 +771,14 @@ def checkVersion(splashscreen):
try:
r = requests.get(version_url, allow_redirects=False, timeout=7)
r = requests.get(version.version_url, allow_redirects=False, timeout=7)
v = yaml.safe_load(r.content)
avail_build = v["version"]
if version.parse(avail_build) > version.parse(version_string):
avail_version = ver.parse(avail_build)
current_version = ver.parse(version.version_string)
current_maj_min = ver.parse(str(current_version.major) + "." + str(current_version.minor))
avail_maj_min = ver.parse(str(avail_version.major) + "." + str(avail_version.minor))
if avail_maj_min > current_maj_min:
logger.warning("New version available. Please update to available version " + v["version"])
msg = QMessageBox()
msg.setWindowTitle(v["title"])
@@ -781,7 +786,7 @@ def checkVersion(splashscreen):
msg.setIcon(QMessageBox.Icon.Information)
x = msg.exec_()
else:
logger.info("Version check complete: running the latest version.")
logger.info("Version check complete: running the latest version. (micro version ignored)")
except:
logger.error("Online version check failed.")

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'MissionGeneratorUI.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -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)
@@ -161,18 +161,21 @@ class Ui_MainWindow(object):
self.scenario_label_4.setAlignment(QtCore.Qt.AlignCenter)
self.scenario_label_4.setObjectName("scenario_label_4")
self.version_label = QtWidgets.QLabel(self.centralwidget)
self.version_label.setGeometry(QtCore.QRect(1140, 650, 111, 20))
self.version_label.setGeometry(QtCore.QRect(1070, 650, 181, 20))
font = QtGui.QFont()
font.setPointSize(8)
self.version_label.setFont(font)
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 +190,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 +205,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 +222,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 +242,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 +250,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 +280,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 +297,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 +315,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)
@@ -563,17 +566,17 @@ class Ui_MainWindow(object):
self.menubar.addAction(self.menuPreferences.menuAction())
self.retranslateUi(MainWindow)
self.generateButton.clicked.connect(self.action_generateMission.trigger)
self.prevScenario_pushButton.clicked.connect(self.action_prevScenario.trigger)
self.defense_checkBox.clicked.connect(self.action_defensiveModeChanged.trigger)
self.slot_template_comboBox.activated['int'].connect(self.action_slotChanged.trigger)
self.scenario_comboBox.currentIndexChanged['int'].connect(self.action_scenarioSelected.trigger)
self.nextScenario_pushButton.clicked.connect(self.action_nextScenario.trigger)
self.rateButton1.clicked.connect(self.action_rateButton1.trigger)
self.rateButton2.clicked.connect(self.action_rateButton2.trigger)
self.rateButton3.clicked.connect(self.action_rateButton3.trigger)
self.rateButton4.clicked.connect(self.action_rateButton4.trigger)
self.rateButton5.clicked.connect(self.action_rateButton5.trigger)
self.generateButton.clicked.connect(self.action_generateMission.trigger) # type: ignore
self.prevScenario_pushButton.clicked.connect(self.action_prevScenario.trigger) # type: ignore
self.defense_checkBox.clicked.connect(self.action_defensiveModeChanged.trigger) # type: ignore
self.slot_template_comboBox.activated['int'].connect(self.action_slotChanged.trigger) # type: ignore
self.scenario_comboBox.currentIndexChanged['int'].connect(self.action_scenarioSelected.trigger) # type: ignore
self.nextScenario_pushButton.clicked.connect(self.action_nextScenario.trigger) # type: ignore
self.rateButton1.clicked.connect(self.action_rateButton1.trigger) # type: ignore
self.rateButton2.clicked.connect(self.action_rateButton2.trigger) # type: ignore
self.rateButton3.clicked.connect(self.action_rateButton3.trigger) # type: ignore
self.rateButton4.clicked.connect(self.action_rateButton4.trigger) # type: ignore
self.rateButton5.clicked.connect(self.action_rateButton5.trigger) # type: ignore
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
@@ -581,8 +584,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", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
@@ -624,7 +627,7 @@ class Ui_MainWindow(object):
self.game_status_checkBox.setText(_translate("MainWindow", "Game Status Display"))
self.label.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game."))
self.label.setText(_translate("MainWindow", "Infantry Spawns"))
self.inf_spawn_spinBox.setStatusTip(_translate("MainWindow", "This value is multiplied by the number of spawn zones in the mission template."))
self.inf_spawn_spinBox.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game."))
self.troop_drop_spinBox.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight."))
self.random_weather_checkBox.setStatusTip(_translate("MainWindow", "Random weather preset will be applied."))
self.random_weather_checkBox.setText(_translate("MainWindow", "Random Weather"))

View File

@@ -54,7 +54,7 @@
<property name="geometry">
<rect>
<x>980</x>
<y>211</y>
<y>231</y>
<width>251</width>
<height>28</height>
</rect>
@@ -75,11 +75,11 @@
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="zone_sams_checkBox">
<widget class="QCheckBox" name="advanced_defenses_checkBox">
<property name="geometry">
<rect>
<x>980</x>
<y>320</y>
<x>510</x>
<y>350</y>
<width>241</width>
<height>28</height>
</rect>
@@ -91,10 +91,10 @@
</font>
</property>
<property name="statusTip">
<string>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.</string>
<string>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.</string>
</property>
<property name="text">
<string>Protect Inactive Zones</string>
<string>Enemy Advanced Defenses</string>
</property>
</widget>
<widget class="QLabel" name="red_forces_label">
@@ -190,8 +190,8 @@ p, li { white-space: pre-wrap; }
</property>
<property name="geometry">
<rect>
<x>470</x>
<y>130</y>
<x>980</x>
<y>140</y>
<width>156</width>
<height>28</height>
</rect>
@@ -271,7 +271,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>570</x>
<y>220</y>
<y>180</y>
<width>271</width>
<height>24</height>
</rect>
@@ -421,12 +421,17 @@ p, li { white-space: pre-wrap; }
<widget class="QLabel" name="version_label">
<property name="geometry">
<rect>
<x>1140</x>
<x>1070</x>
<y>650</y>
<width>111</width>
<width>181</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Version string</string>
</property>
@@ -438,7 +443,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>570</x>
<y>260</y>
<y>220</y>
<width>271</width>
<height>24</height>
</rect>
@@ -460,7 +465,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>510</x>
<y>260</y>
<y>220</y>
<width>51</width>
<height>31</height>
</rect>
@@ -496,7 +501,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>510</x>
<y>220</y>
<y>180</y>
<width>51</width>
<height>31</height>
</rect>
@@ -532,7 +537,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>510</x>
<y>180</y>
<y>140</y>
<width>51</width>
<height>31</height>
</rect>
@@ -574,7 +579,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>570</x>
<y>180</y>
<y>140</y>
<width>271</width>
<height>24</height>
</rect>
@@ -633,7 +638,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>980</x>
<y>246</y>
<y>266</y>
<width>241</width>
<height>28</height>
</rect>
@@ -658,7 +663,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>980</x>
<y>282</y>
<y>302</y>
<width>241</width>
<height>28</height>
</rect>
@@ -758,7 +763,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>570</x>
<y>340</y>
<y>300</y>
<width>261</width>
<height>23</height>
</rect>
@@ -780,7 +785,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>510</x>
<y>340</y>
<y>300</y>
<width>51</width>
<height>31</height>
</rect>
@@ -791,7 +796,7 @@ p, li { white-space: pre-wrap; }
</font>
</property>
<property name="statusTip">
<string>This value is multiplied by the number of spawn zones in the mission template.</string>
<string>Total number of infantry groups to spawn per game.</string>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::PlusMinus</enum>
@@ -810,7 +815,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>510</x>
<y>300</y>
<y>260</y>
<width>51</width>
<height>31</height>
</rect>
@@ -867,7 +872,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>570</x>
<y>300</y>
<y>260</y>
<width>281</width>
<height>23</height>
</rect>
@@ -889,7 +894,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>980</x>
<y>180</y>
<y>200</y>
<width>251</width>
<height>27</height>
</rect>

75
Generator/README.md Normal file
View File

@@ -0,0 +1,75 @@
# Building the exe with build.bat
**Use build.bat to compile the UI files and build the exe.**
The steps are provided below for reference:
## build UI files
pyuic5 -x MissionGeneratorUI.ui -o MissionGeneratorUI.py
## build resources
pyrcc5 -o resources.py resources.qrc
## build exe
pyinstaller MissionGenerator.spec --distpath ..\ -i='assets\icon.ico'
# Adding update to auto-installer
**Merging into the main branch now triggers a deployment action that automatically performs the actions below when significant files are changed (defined in '.change-monitored').**
**Significant files moved/deleted are not supported and may cause issues with the deployment script**
**Version must be incremented in version.py. Only maj/min version is compared at app startup, so changes to supporting files can be made by incrementing the patch version.**
Files currently live at https://dcs-helicopters.com/app-updates
1) Add new files to /updates folder
2) Update updatescript.ini:
example:
releases{
1.1.1
1.1.2
1.2.0
}
release:1.1.1{
}
release:1.1.2{
DownloadFile:MissionGenerator.exe
}
release:1.2.0{
DownloadFile:MissionGenerator.exe
DownloadFile:RotorOps.lua,scripts\
DownloadFile:Splash_Damage_2_0.lua,scripts\
}
3) Update versioncheck.yaml
example:
---
title: "Update Available"
description: "UPDATE AVAILABLE: Please run the included updater utility (RotorOps_updater.exe) to get the latest version."
version: "1.2.0"
# Building new RotorOps_setup.exe installer package
Uses https://installforge.net/
See install-config.ifp and installforge_constants.txt
# Adding/updating downloadable modules
** Templates now live in their own repo and there is a deployment action to automatically perform the steps below **
Currently lives at https://dcs-helicopters.com/modules
1) Add new folder to remote directory, ie modules/forces
2) Trigger an update to templates by incrementing version in it's .yaml config file
3) Run server/user-files/modules/mapscript.py

View File

@@ -29,7 +29,9 @@ def triggerSetup(rops, options):
"RotorOps.zone_status_display = " + lb("game_display") + "\n\n" +
"RotorOps.inf_spawn_messages = true\n\n" +
"RotorOps.inf_spawns_total = " + lb("inf_spawn_qty") + "\n\n" +
"RotorOps.apcs_spawn_infantry = " + lb("apc_spawns_inf") + " \n\n")
"RotorOps.apcs_spawn_infantry = " + lb("apc_spawns_inf") + " \n\n" +
"RotorOps.fighter_min_detection_alt = 609\n\n" +
"RotorOps.fighter_max_active = 2\n\n")
if not options["smoke_pickup_zones"]:
script = script + 'RotorOps.pickup_zone_smoke = "none"\n\n'
trig.actions.append(dcs.action.DoScript(dcs.action.String((script))))
@@ -58,6 +60,20 @@ def triggerSetup(rops, options):
trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)")))
rops.m.triggerrules.triggers.append(trig)
if options["rotorops_server"]:
trig = dcs.triggers.TriggerOnce(comment="RotorOps Set Up Server")
trig.rules.append(dcs.condition.TimeAfter(4))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["RotorOpsServer.lua"]))
# Slot block the zone spawns if SSB is available
trig.actions.append(dcs.action.SetFlagValue('SSB', 100))
for c_zone in rops.conflict_zones:
for group in rops.all_zones[c_zone].player_helo_spawns:
trig.actions.append(dcs.action.SetFlagValue(group.name, 100))
rops.m.triggerrules.triggers.append(trig)
# Add generic zone-based triggers
for index, zone_name in enumerate(rops.conflict_zones):
z_active_trig = dcs.triggers.TriggerOnce(comment=zone_name + " Active")
@@ -74,14 +90,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.
@@ -107,11 +123,10 @@ def triggerSetup(rops, options):
z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# Activate late-activated helicopters at FARPs. Doesn't work consistently
# for group in rops.all_zones[previous_zone].player_helo_spawns:
# z_farps_trig.actions.append(
# dcs.action.ActivateGroup(
# group.id))
# Activate late-activated helicopters at FARPs if SSB slot blocking script is available
for group in rops.all_zones[previous_zone].player_helo_spawns:
z_farps_trig.actions.append(
dcs.action.SetFlagValue(group.name, 0))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig)
@@ -130,11 +145,10 @@ def triggerSetup(rops, options):
"--The 100 flag indicates which zone is active. The 111 flag value is the percentage of staged units remaining")))
z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# Activate late-activated helicopters at FARPs. Doesn't work consistently
# for group in rops.all_zones[previous_zone].player_helo_spawns:
# z_farps_trig.actions.append(
# dcs.action.ActivateGroup(
# group.id))
# Activate late-activated helicopters at FARPs if SSB slot blocking script is available
for group in rops.all_zones[previous_zone].player_helo_spawns:
z_farps_trig.actions.append(
dcs.action.SetFlagValue(group.name, 0))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig)
@@ -178,24 +192,53 @@ 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
mission_end_delay = 1200
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)")))
trig.actions.append(dcs.action.DoScript(dcs.action.String(
"timer.scheduleFunction(function()trigger.action.setUserFlag('mission_end', 2) end, {}, timer.getTime() + " + str(
mission_end_delay) + ")")))
trig.actions.append(dcs.action.MessageToAll(dcs.action.String("Time to RTB. Mission will end soon."), mission_end_delay))
# Add game lost triggers
trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST")
trig.rules.append(dcs.condition.FlagEquals(game_flag, 98))
mission_end_trigger = dcs.triggers.TriggerOnce(comment="End the mission")
mission_end_trigger.rules.append(dcs.condition.FlagEquals("mission_end", 2))
mission_end_trigger.actions.append(dcs.action.EndMission(text=dcs.action.String("Blue forces won!")))
rops.m.triggerrules.triggers.append(mission_end_trigger)
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)")))
trig.actions.append(dcs.action.DoScript(dcs.action.String(
"timer.scheduleFunction(function()trigger.action.setUserFlag('mission_end', 1) end, {}, timer.getTime() + " + str(
mission_end_delay) + ")")))
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)
dcs.action.MessageToAll(dcs.action.String("Time to RTB. Mission will end soon."), mission_end_delay))
mission_end_trigger = dcs.triggers.TriggerOnce(comment="End the mission")
mission_end_trigger.rules.append(dcs.condition.FlagEquals("mission_end", 1))
mission_end_trigger.actions.append(dcs.action.EndMission(text=dcs.action.String("Red forces won!")))
rops.m.triggerrules.triggers.append(mission_end_trigger)
rops.m.triggerrules.triggers.append(trig)

View File

@@ -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:
@@ -140,7 +141,7 @@ class ImportObjects:
return new_groups
def copyHelicopters(self, mission, dest_country_name, dest_name, dest_point, dest_heading=0,
start_type=dcs.mission.StartType.Cold):
start_type=None):
logger.info("Copying " + str(len(self.helicopters)) + " helicopters as " + dest_name)
new_groups = []
@@ -176,9 +177,12 @@ class ImportObjects:
if start_type == dcs.mission.StartType.Warm:
ng.points[0].action = dcs.point.PointAction.FromGroundAreaHot
ng.points[0].type = "TakeOffGroundHot"
else:
elif start_type == dcs.mission.StartType.Cold:
ng.points[0].action = dcs.point.PointAction.FromGroundArea
ng.points[0].type = "TakeOffGround"
else:
ng.points[0].action = group.points[0].action
ng.points[0].type = group.points[0].type
ng.units[0].heading = group.units[0].heading
ng.units[0].skill = group.units[0].skill
ng.units[0].livery_id = group.units[0].livery_id
@@ -211,6 +215,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:

View File

@@ -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):
@@ -132,7 +136,22 @@ class RotorOpsMission:
window.statusBar().showMessage("Loading scenario mission", 10000)
self.m.load_file(options["scenario_file"])
# self.m.load_file(options["scenario_file"])
# Bypass trig, triggrules, and triggers. Then load triggers
# manually. We want to get our zones from the template mission, but leave the existing trigger actions and
# conditions the same, since pydcs cannot yet handle some of them well.
self.m.load_file(options["scenario_file"], True)
self.m.triggers.load_from_dict(self.m.bypassed_triggers)
# Create some 'empty' triggerrules so that we can maintain indexing when we merge dictionaries on save
for rule in self.m.bypassed_trigrules:
trig = dcs.triggers.TriggerOnce(comment="Empty " + str(rule))
trig.rules.append(dcs.condition.TimeAfter(1))
trig.actions.append(dcs.action.DoScript(dcs.action.String("Filler " + str(rule))))
self.m.triggerrules.triggers.append(trig)
# Add countries if they're missing
if not self.m.country(jtf_red):
self.m.coalition.get("red").add_country(dcs.countries.CombinedJointTaskForcesRed())
@@ -200,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)
@@ -214,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"]:
@@ -340,8 +250,8 @@ class RotorOpsMission:
file=activated_farp,
config_name="zone_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " EMPTY",
heli_start_type=dcs.mission.StartType.Cold,
helicopters_name="ZONE " + zone_name,
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " FARP Static",
copy_statics=False,
@@ -352,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
@@ -389,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,
@@ -397,7 +294,7 @@ class RotorOpsMission:
config_name="logistics_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=start_type,
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " Logistics FARP",
copy_statics=True,
@@ -413,7 +310,7 @@ class RotorOpsMission:
config_name="defensive_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " EMPTY",
heli_start_type=dcs.mission.StartType.Cold,
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " Defensive FARP",
copy_statics=True,
@@ -443,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
@@ -482,11 +381,15 @@ class RotorOpsMission:
self.m.weather.wind_at_8000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_8000.speed = wind_speed + random.randrange(-1, 10)
self.m.weather.halo.preset = dcs.weather.Halo.Preset.Auto
self.m.weather.halo.crystals = None
logger.info("Cloud preset = " + cloud_preset.ui_name + ", ground windspeed = " + str(
self.m.weather.wind_at_ground.speed))
if options["time"] != "Default Time":
self.m.random_daytime(options["time"].lower())
print("Time set to " + options["time"])
# Save the mission file
window.statusBar().showMessage("Saving mission...", 10000)
@@ -496,12 +399,22 @@ class RotorOpsMission:
output_dir = directories.output # default dir
os.chdir(output_dir)
output_filename = options["scenario_name"] + " " + time.strftime('%a%H%M%S') + '.miz'
# dcs.mission.save will use the bypassed trig, trigrules, and triggers. Our goal is to leave the trigrules and
# trig from the source mission untouched. See comments in self.m.load_file above
#merge dictionaries
self.m.bypassed_trig = self.m.triggerrules.trig() | self.m.bypassed_trig
self.m.bypassed_trigrules = self.m.triggerrules.trigrules() | self.m.bypassed_trigrules
self.m.bypassed_triggers = self.m.triggers.dict()
success = self.m.save(output_filename)
return {"success": success, "filename": output_filename, "directory": output_dir} # let the UI know the result
# Use the ImportObjects class to place farps and bases
def addZoneBase(self, options, _zone_name, country, file, config_name=None, copy_helicopters=False,
helicopters_name="", heli_start_type=dcs.mission.StartType.Cold,
helicopters_name="", heli_start_type=None,
copy_vehicles=False, vehicles_name="", copy_statics=False, statics_names="",
vehicles_single_group=False, trigger_name=None, trigger_radius=110, farp=True):
@@ -598,7 +511,7 @@ class RotorOpsMission:
if len(airport.free_parking_slots(aircraft)) >= group_size:
if not (aircraft.id in dcs.planes.plane_map and (
len(airport.runways) == 0 or airport.runways[0].ils is None)):
len(airport.runways) == 0 or not hasattr(airport.runways[0], "ils"))):
return airport
if alt_airports:
@@ -703,7 +616,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 = {}
@@ -757,6 +670,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:
@@ -846,7 +761,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
@@ -857,6 +772,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:
@@ -876,6 +792,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
@@ -962,6 +899,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
@@ -1093,6 +1031,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"]

View File

@@ -12,6 +12,7 @@ client_helos = [
player_helos = [
dcs.helicopters.AH_64D_BLK_II,
dcs.helicopters.Ka_50,
dcs.helicopters.Ka_50_3,
dcs.helicopters.Mi_8MT,
dcs.helicopters.Mi_24P,
dcs.helicopters.SA342M,
@@ -20,6 +21,7 @@ player_helos = [
dcs.helicopters.SA342Mistral,
dcs.helicopters.UH_1H,
aircraftMods.UH_60L,
dcs.planes.AV8BNA,
]
e_attack_helos = [
@@ -39,6 +41,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,
]

View File

@@ -1,8 +0,0 @@
#build UI files
pyuic5 -x MissionGeneratorUI.ui -o MissionGeneratorUI.py
#build resources
pyrcc5 -o resources.py resources.qrc
#build exe
pyinstaller MissionGenerator.spec --distpath ..\ -i='assets\icon.ico'

13
Generator/build.bat Normal file
View File

@@ -0,0 +1,13 @@
call .\venv\Scripts\activate.bat
echo activated python venv.
pyuic5 -x MissionGeneratorUI.ui -o MissionGeneratorUI.py
echo built MissionGenerator.py from MissionsGeneratorUI.ui
pyrcc5 -o resources.py resources.qrc
echo compiled ui resource files.
echo building exe with pyinstaller...
pyinstaller MissionGenerator.spec --distpath ..\ --clean
pause >nul

Binary file not shown.

View File

@@ -1175,9 +1175,9 @@ qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x80\x16\x14\x6f\x4d\
\x00\x00\x01\x80\x25\xe1\xff\x48\
\x00\x00\x00\x2c\x00\x00\x00\x00\x00\x01\x00\x00\x22\x0d\
\x00\x00\x01\x80\x16\x13\x53\x5c\
\x00\x00\x01\x80\x25\xe1\xff\x48\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]

8
Generator/version.py Normal file
View File

@@ -0,0 +1,8 @@
# ROTOROPS VERSION
maj_version = 1
minor_version = 3
patch_version = 2
version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'
version_string = str(maj_version) + "." + str(minor_version) + "." + str(patch_version)