diff --git a/.gitignore b/.gitignore index 6c3d373..c6ca7b9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,14 @@ Generator/utils/extract units/units.txt generator.log templates/Scenarios/user templates/Scenarios/downloaded +templates/Imports/user +templates/Imports/downloaded +templates/Forces/user +templates/Forces/downloaded config/user-data.yaml *.exe +config/user-data.yaml +server/user-files/modules/mapscript-sql.py +distribution/ +MissionOutput/ + diff --git a/Generator/MissionGenerator.py b/Generator/MissionGenerator.py index 1633467..89ab13e 100644 --- a/Generator/MissionGenerator.py +++ b/Generator/MissionGenerator.py @@ -2,6 +2,7 @@ import json import yaml import sys import os +import operator import RotorOpsMission as ROps import RotorOpsUnits @@ -28,10 +29,18 @@ import qtmodern.windows # UPDATE BUILD VERSION maj_version = 1 -minor_version = 1 -patch_version = 2 +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' #Setup logfile and exception handler logger = logging.getLogger(__name__) @@ -50,27 +59,39 @@ class directories: os.chdir("..") cls.home_dir = os.getcwd() cls.scenarios = cls.home_dir + "\\templates\\Scenarios" - cls.forces = cls.home_dir + "\\templates\\Forces" + cls.forces_downloaded = cls.home_dir + "\\templates\\Forces\\downloaded" + cls.forces_user = cls.home_dir + "\\templates\\Forces\\user" cls.scripts = cls.home_dir + "\\scripts" cls.sound = cls.home_dir + "\\sound\\embedded" cls.output = cls.home_dir + "\\MissionOutput" cls.assets = cls.home_dir + "\\assets" - cls.imports = cls.home_dir + "\\templates\\Imports" + cls.imports_downloaded = cls.home_dir + "\\templates\\Imports\\downloaded" + cls.imports_user = cls.home_dir + "\\templates\\Imports\\user" cls.user_datafile_path = cls.home_dir + "\\config\\user-data.yaml" cls.scenarios_downloaded = cls.scenarios + "\\downloaded" cls.scenarios_user = cls.scenarios + "\\user" cls.default_config = cls.home_dir + '\\config\\default-config.yaml' os.chdir(current_dir) -directories.find() + @classmethod + def createDirectories(cls): + required_dirs = [cls.scenarios_user, cls.scenarios_downloaded, cls.imports_user, cls.imports_downloaded, cls.forces_user, cls.forces_downloaded, cls.output] + for path in required_dirs: + if not os.path.exists(path): + os.makedirs(path) -import MissionGeneratorScenario + +directories.find() +directories.createDirectories() + +import MissionGeneratorTemplates def handle_exception(exc_type, exc_value, exc_traceback): if issubclass(exc_type, KeyboardInterrupt): #example of handling error subclasses sys.__excepthook__(exc_type, exc_value, exc_traceback) return + QApplication.restoreOverrideCursor() logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) msg = QMessageBox() msg.setWindowTitle("Uncaught exception") @@ -82,9 +103,6 @@ sys.excepthook = handle_exception version_string = str(maj_version) + "." + str(minor_version) + "." + str(patch_version) -# scenarios = [] -red_forces_files = [] -blue_forces_files = [] defenders_text = "Defending Forces:" attackers_text = "Attacking Forces:" ratings_json = None @@ -120,16 +138,17 @@ class Window(QMainWindow, Ui_MainWindow): self.player_slots = [] self.user_output_dir = None self.user_data = None + self.forces_list = [] + self.imports_list = [] self.user_data = self.loadUserData() - self.m = ROps.RotorOpsMission() self.setupUi(self) self.connectSignalsSlots() self.populateScenarios() - self.populateForces("red", self.redforces_comboBox, red_forces_files) - self.populateForces("blue", self.blueforces_comboBox, blue_forces_files) + self.populateForces() self.populateSlotSelection() + self.getImports() # self.blue_forces_label.setText(attackers_text) # self.red_forces_label.setText(defenders_text) @@ -139,11 +158,15 @@ class Window(QMainWindow, Ui_MainWindow): "QStatusBar{padding-left:5px;}") self.version_label.setText("Version " + version_string) + self.scenarioChanged() - - - - + self.time_comboBox.addItem("Default Time") + self.time_comboBox.addItem("Day") + self.time_comboBox.addItem("Night") + self.time_comboBox.addItem("Dusk") + self.time_comboBox.addItem("Dawn") + self.time_comboBox.addItem("Noon") + self.time_comboBox.addItem("Random") def connectSignalsSlots(self): @@ -212,7 +235,7 @@ class Window(QMainWindow, Ui_MainWindow): basename = filename.removesuffix('.miz') mizpath = os.path.join(path, folder, filename) # create scenario object - s = MissionGeneratorScenario.Scenario(mizpath, basename) + s = MissionGeneratorTemplates.Scenario(mizpath, basename) #apply some properties if found in the downloads directory if path == directories.scenarios_downloaded: @@ -221,7 +244,6 @@ class Window(QMainWindow, Ui_MainWindow): s.packageID = folder if ratings_json: - print(ratings_json) for module in ratings_json: if module['package'] == folder: s.rating = module["avg_rating"] @@ -256,8 +278,8 @@ class Window(QMainWindow, Ui_MainWindow): t_scenarios.append(s) scenarios = t_scenarios.copy() - #self.scenario_comboBox.addItem(s.name) - self.scenarios_list = scenarios.copy() + self.scenarios_list = sorted(scenarios, key=lambda x: x.name, reverse=False) + for s in self.scenarios_list: self.scenario_comboBox.addItem(s.name) @@ -268,18 +290,67 @@ class Window(QMainWindow, Ui_MainWindow): self.populateScenarios() # self.scenarioChanged() haven't tried yet - def populateForces(self, side, combobox, files_list): - os.chdir(directories.home_dir) - # os.chdir(directories.forces + "/" + side) - os.chdir(directories.forces) - path = os.getcwd() - dir_list = os.listdir(path) - logger.info("Looking for " + side + " Forces files in '" + path) + def populateForces(self): + self.forces_list = [] - for filename in dir_list: - if filename.endswith(".miz"): - files_list.append(filename) - combobox.addItem(filename.removesuffix('.miz')) + for path in [directories.forces_downloaded, directories.forces_user]: + logger.info("Looking for forces files in " + path) + os.chdir(path) + module_folders = next(os.walk('.'))[1] + + for folder in module_folders: + for filename in os.listdir(folder): + if filename.endswith(".miz"): + basename = filename.removesuffix('.miz') + mizpath = os.path.join(path, folder, filename) + config_file_path = os.path.join(path, folder, basename + '.yaml') + if os.path.exists(config_file_path): + # create forces object with config + try: + config = yaml.safe_load(open(config_file_path)) + f = MissionGeneratorTemplates.Forces(mizpath, filename, config) + self.forces_list.append(f) + except: + logger.error("Error in " + config_file_path) + + else: + # create forces object without config + f = MissionGeneratorTemplates.Forces(mizpath, basename) + self.forces_list.append(f) + + self.forces_list = sorted(self.forces_list, key=lambda x: x.name, reverse=False) + + for forces in self.forces_list: + self.redforces_comboBox.addItem(forces.name) + self.blueforces_comboBox.addItem(forces.name) + + def getImports(self): + self.imports_list = [] + + for path in [directories.imports_downloaded, directories.imports_user]: + logger.info("Looking for imports files in " + path) + os.chdir(path) + module_folders = next(os.walk('.'))[1] + + for folder in module_folders: + for filename in os.listdir(folder): + if filename.endswith(".miz"): + basename = filename.removesuffix('.miz') + mizpath = os.path.join(path, folder, filename) + config_file_path = os.path.join(path, folder, basename + '.yaml') + if os.path.exists(config_file_path): + # create imports object with config + try: + config = yaml.safe_load(config_file_path) + f = MissionGeneratorTemplates.Import(mizpath, filename, config) + self.imports_list.append(f) + except: + logger.error("Error in " + config_file_path) + + else: + # create imports object without config + f = MissionGeneratorTemplates.Import(mizpath, filename) + self.imports_list.append(f) def populateSlotSelection(self): self.slot_template_comboBox.addItem("Multiple Slots") @@ -321,8 +392,7 @@ class Window(QMainWindow, Ui_MainWindow): # reset some UI elements self.defense_checkBox.setEnabled(True) - if self.lockedSlot(): - self.slot_template_comboBox.removeItem(self.lockedSlot()) + self.slot_template_comboBox.removeItem(self.lockedSlot()) self.slot_template_comboBox.setEnabled(True) self.slot_template_comboBox.setCurrentIndex(0) @@ -362,11 +432,14 @@ class Window(QMainWindow, Ui_MainWindow): button.setEnabled(True) if 'blue_forces' in config: - self.blueforces_comboBox.setCurrentIndex(self.blueforces_comboBox.findText(config['blue_forces'])) + for template in self.forces_list: + if template.basename == config['blue_forces']: + self.blueforces_comboBox.setCurrentIndex(self.blueforces_comboBox.findText(template.name)) if 'red_forces' in config: - if self.redforces_comboBox.findText(config['red_forces']) >= 0: - self.redforces_comboBox.setCurrentIndex(self.redforces_comboBox.findText(config['red_forces'])) + for template in self.forces_list: + if template.basename == config['red_forces']: + self.redforces_comboBox.setCurrentIndex(self.redforces_comboBox.findText(template.name)) except Exception as e: logger.error("Error loading config file: " + str(e)) @@ -416,16 +489,16 @@ class Window(QMainWindow, Ui_MainWindow): return QApplication.setOverrideCursor(Qt.WaitCursor) + self.slot_template_comboBox.setCurrentIndex(0) self.scenario = self.scenarios_list[self.scenario_comboBox.currentIndex()] + # reset generator options to default + default_config = self.loadScenarioConfig(directories.default_config) + self.applyScenarioConfig(default_config) + if self.scenario.config: self.applyScenarioConfig(self.scenario.config) - self.m.setConfig(self.scenario.config) - else: - default_config = self.loadScenarioConfig(directories.default_config) - self.applyScenarioConfig(default_config) - self.m.setConfig(default_config) path = self.scenario.path.removesuffix(".miz") + ".jpg" if os.path.isfile(path): @@ -462,17 +535,27 @@ class Window(QMainWindow, Ui_MainWindow): def generateMissionAction(self): QApplication.setOverrideCursor(Qt.WaitCursor) - red_forces_filename = red_forces_files[self.redforces_comboBox.currentIndex()] - blue_forces_filename = blue_forces_files[self.blueforces_comboBox.currentIndex()] + red_forces = self.forces_list[self.redforces_comboBox.currentIndex()] + blue_forces = self.forces_list[self.blueforces_comboBox.currentIndex()] scenario_name = self.scenario.name scenario_path = self.scenario.path - source = "offline" + + credits = ("'" + scenario_name + "' mission template by " + self.scenario.author + "\n" + + "'" + red_forces.name + "' by " + red_forces.author + "\n" + + "'" + blue_forces.name + "' by " + blue_forces.author + "\n" + ) + + objects = { + "imports": self.imports_list, + } + data = { - "source": source, + "objects": objects, + "credits": credits, "scenario_file": scenario_path, "scenario_name": scenario_name, - "red_forces_filename": red_forces_filename, - "blue_forces_filename": blue_forces_filename, + "red_forces_path": red_forces.path, + "blue_forces_path": blue_forces.path, "red_quantity": self.redqty_spinBox.value(), "blue_quantity": self.blueqty_spinBox.value(), "inf_spawn_qty": self.inf_spawn_spinBox.value(), @@ -483,7 +566,7 @@ class Window(QMainWindow, Ui_MainWindow): "f_awacs": self.awacs_checkBox.isChecked(), "f_tankers": self.tankers_checkBox.isChecked(), "voiceovers": self.voiceovers_checkBox.isChecked(), - "force_offroad": self.force_offroad_checkBox.isChecked(), + "force_offroad": self.scenario.getConfigValue("force_offroad", default=False), "game_display": self.game_status_checkBox.isChecked(), "defending": self.defense_checkBox.isChecked(), "slots": self.slot_template_comboBox.currentText(), @@ -494,6 +577,17 @@ class Window(QMainWindow, Ui_MainWindow): "smoke_pickup_zones": self.smoke_pickup_zone_checkBox.isChecked(), "player_slots": self.player_slots, "player_hotstart": self.hotstart_checkBox.isChecked(), + "random_weather": self.random_weather_checkBox.isChecked(), + "time": self.time_comboBox.currentText(), + "start_trigger": self.scenario.getConfigValue("start_trigger", default=True), + "end_trigger": self.scenario.getConfigValue("end_trigger", default=True), + "farp_spawns": self.farp_spawn_checkBox.isChecked(), + "staging_logistics_file": self.scenario.getConfigValue("staging_logistics_file", default=None), + "zone_farp_file": self.scenario.getConfigValue("zone_farp_file", default=None), + "defensive_farp_file": self.scenario.getConfigValue("defensive_farp_file", default=None), + "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), } logger.info("Generating mission with options:") @@ -674,7 +768,7 @@ class Window(QMainWindow, Ui_MainWindow): def checkVersion(splashscreen): - version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml' + try: r = requests.get(version_url, allow_redirects=False, timeout=7) v = yaml.safe_load(r.content) @@ -693,31 +787,33 @@ def checkVersion(splashscreen): -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.yaml' -ratings_url = 'https://dcs-helicopters.com/user-files/ratings.php' - def loadModules(splashscreen): + msg = QMessageBox() + msg.setWindowTitle("Unable to connect to server") + msg.setText( + "We were unable to connect to the RotorOps server to download content. This is a temporary problem, so please try again later. If the problem persists, please get in touch via Discord.") try: r = requests.get(modules_map_url, allow_redirects=False, timeout=7) if not r.status_code == 200: logger.error("Could not retrieve the modules map.") + x = msg.exec_() return except: logger.error("Failed to retrieve module map.") + x = msg.exec_() return module_list = yaml.safe_load(r.content) files_success = [] files_failed = [] - new_scenarios = [] - updated_scenarios = [] + new_modules = [] + updated_modules = [] + outversioned_modules = [] # Download scenarios files - #os.chdir(directories.scenarios) + if module_list: for module in module_list: @@ -725,15 +821,31 @@ def loadModules(splashscreen): should_download = False new_module = False + # only allow predefined paths + dp = module_list[module]["path"] + if dp not in allowed_paths: + logger.warning("Invalid path for module: " + module) + continue + # check if local version already exists - package_file_path = os.path.join(directories.scenarios_downloaded, module, "package.yaml") + package_file_path = os.path.join(directories.home_dir, module_list[module]["path"], module, "package.yaml") if os.path.exists(package_file_path): pkg_file = yaml.safe_load(open(package_file_path)) else: pkg_file = None - #compare local and remote versions + # compare required generator version and actual version + if 'requires' in module_list[module]: + if module_list[module]['requires'] > modules_version: + name = 'unknown module' + if 'name' in module_list[module]: + name = module_list[module]['name'] + outversioned_modules.append(name) + continue + + + # compare local and remote versions if pkg_file and 'version' in pkg_file: local_version = pkg_file['version'] @@ -744,6 +856,20 @@ def loadModules(splashscreen): should_download = True new_module = True + # delete modules with 'remove' dist property + if 'dist' in module_list[module] and module_list[module]['dist'] == 'remove': + for filename in module_list[module]["files"]: + module_dir = os.path.join(directories.home_dir, module_list[module]["path"], module) + file_path = os.path.join(module_dir, filename) + if os.path.exists(file_path): + try: + os.remove(file_path) + print("Removed module file: " + filename) + except: + logger.error("Error while trying to remove " + filename) + continue + + # download files if should_download: logger.info("Updating module: " + module) module_dir = os.path.join(directories.home_dir, module_list[module]["path"], module) @@ -751,10 +877,11 @@ def loadModules(splashscreen): # download files in remote package for filename in module_list[module]["files"]: broken_file = False + type_path = module_list[module]["type"] splash.showMessage("Downloading " + filename + " ...", Qt.AlignHCenter | Qt.AlignTop, Qt.white) app.processEvents() - url = modules_url + module + "/" + filename + url = modules_url + type_path + "/" + module + "/" + filename try: r = requests.get(url, allow_redirects=False, timeout=10) except: @@ -771,9 +898,9 @@ def loadModules(splashscreen): # do some stuff for the dialog popup if filename.endswith('.miz') and "name" in module_list[module]: if new_module: - new_scenarios.append(module_list[module]["name"]) + new_modules.append(module_list[module]["name"]) else: - updated_scenarios.append(module_list[module]["name"]) + updated_modules.append(module_list[module]["name"]) else: broken_file = True files_failed.append(filename) @@ -791,7 +918,7 @@ def loadModules(splashscreen): logger.error("Problem encountered with modules map.") # show a popup if we downloaded any packages - if len(files_success) > 0 or len(files_failed) > 0: + if len(files_success) > 0 or len(files_failed) > 0 or len(outversioned_modules) > 0: if len(files_failed) > 0: fs = "" for filename in files_failed: @@ -800,16 +927,18 @@ def loadModules(splashscreen): msg = QMessageBox() msg.setWindowTitle("Downloaded Files") message = "" - if len(new_scenarios) > 0: - message = message + "New scenarios added: \n\n" - for name in new_scenarios: + if len(new_modules) > 0: + message = message + "New modules added: \n\n" + for name in new_modules: message = message + name + "\n" - if len(updated_scenarios) > 0: - message = message + "\nScenarios updated: \n" - for name in updated_scenarios: + if len(updated_modules) > 0: + message = message + "\nModules updated: \n" + for name in updated_modules: message = message + name + "\n" if len(files_failed) > 0: message = message + "\n\n" + str(len(files_failed)) + " files failed." + if len(outversioned_modules) > 0: + message = message + "\n\n" + str(len(outversioned_modules)) + " modules did not download because you need an required update." msg.setText(message) x = msg.exec_() else: diff --git a/Generator/MissionGeneratorScenario.py b/Generator/MissionGeneratorTemplates.py similarity index 79% rename from Generator/MissionGeneratorScenario.py rename to Generator/MissionGeneratorTemplates.py index 90a2a0e..d071414 100644 --- a/Generator/MissionGeneratorScenario.py +++ b/Generator/MissionGeneratorTemplates.py @@ -19,6 +19,8 @@ class Scenario: self.rating_qty = None self.packageID = None self.local_rating = None + self.author = "unknown" + def applyConfig(self, config): self.config = config @@ -31,8 +33,16 @@ class Scenario: if 'tags' in config: for tag in config['tags']: self.tags.append(tag) + if 'author' in config: + self.author = config["author"] + def getConfigValue(self, key, default): + if self.config and key in self.config: + return self.config[key] + else: + return default + def evaluateMiz(self): # check if we have the miz file @@ -116,3 +126,35 @@ class Scenario: +class Forces: + + def __init__(self, path, filename, config=None): + self.path = path + self.filename = filename + self.basename = filename.removesuffix('.miz') + self.name = filename.removesuffix('.miz') + self.author = "unknown" + + + if config: + if 'name' in config: + self.name = config["name"] + + if 'author' in config: + self.author = config["author"] + +class Import: + + def __init__(self, path, filename, config=None): + self.path = path + self.filename = filename + self.name = filename.removesuffix('.miz') + self.author = "unknown" + + + if config: + if 'name' in config: + self.name = config["name"] + + if 'author' in config: + self.author = config["author"] diff --git a/Generator/MissionGeneratorUI.py b/Generator/MissionGeneratorUI.py index 41fc35e..2a2dacf 100644 --- a/Generator/MissionGeneratorUI.py +++ b/Generator/MissionGeneratorUI.py @@ -34,7 +34,7 @@ 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(990, 211, 251, 28)) + self.logistics_crates_checkBox.setGeometry(QtCore.QRect(980, 211, 251, 28)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) @@ -42,7 +42,7 @@ class Ui_MainWindow(object): 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(990, 320, 241, 28)) + self.zone_sams_checkBox.setGeometry(QtCore.QRect(980, 320, 241, 28)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) @@ -79,9 +79,9 @@ 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, 120, 156, 28)) + self.defense_checkBox.setGeometry(QtCore.QRect(470, 130, 156, 28)) font = QtGui.QFont() - font.setPointSize(10) + font.setPointSize(11) font.setBold(False) self.defense_checkBox.setFont(font) self.defense_checkBox.setCheckable(True) @@ -116,7 +116,7 @@ class Ui_MainWindow(object): self.scenario_label_8.setFont(font) self.scenario_label_8.setObjectName("scenario_label_8") self.slot_template_comboBox = QtWidgets.QComboBox(self.centralwidget) - self.slot_template_comboBox.setGeometry(QtCore.QRect(960, 384, 271, 33)) + self.slot_template_comboBox.setGeometry(QtCore.QRect(980, 474, 271, 33)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) @@ -216,7 +216,7 @@ class Ui_MainWindow(object): self.e_attack_helos_spinBox.setKeyboardTracking(True) self.e_attack_helos_spinBox.setMinimum(0) self.e_attack_helos_spinBox.setMaximum(8) - self.e_attack_helos_spinBox.setProperty("value", 2) + 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)) @@ -226,20 +226,20 @@ class Ui_MainWindow(object): self.scenario_label_7.setFont(font) self.scenario_label_7.setObjectName("scenario_label_7") self.label_2 = QtWidgets.QLabel(self.centralwidget) - self.label_2.setGeometry(QtCore.QRect(840, 390, 111, 24)) + self.label_2.setGeometry(QtCore.QRect(860, 480, 111, 24)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) self.label_2.setFont(font) self.label_2.setObjectName("label_2") self.scenario_label_9 = QtWidgets.QLabel(self.centralwidget) - self.scenario_label_9.setGeometry(QtCore.QRect(490, 450, 251, 23)) + self.scenario_label_9.setGeometry(QtCore.QRect(480, 401, 251, 23)) font = QtGui.QFont() font.setPointSize(10) 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(990, 246, 241, 28)) + self.awacs_checkBox.setGeometry(QtCore.QRect(980, 246, 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(990, 282, 241, 28)) + self.tankers_checkBox.setGeometry(QtCore.QRect(980, 282, 241, 28)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) @@ -255,21 +255,21 @@ class Ui_MainWindow(object): self.tankers_checkBox.setChecked(True) self.tankers_checkBox.setObjectName("tankers_checkBox") self.voiceovers_checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.voiceovers_checkBox.setGeometry(QtCore.QRect(960, 517, 171, 24)) + self.voiceovers_checkBox.setGeometry(QtCore.QRect(500, 594, 171, 31)) font = QtGui.QFont() font.setPointSize(9) self.voiceovers_checkBox.setFont(font) self.voiceovers_checkBox.setChecked(True) self.voiceovers_checkBox.setObjectName("voiceovers_checkBox") self.smoke_pickup_zone_checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.smoke_pickup_zone_checkBox.setGeometry(QtCore.QRect(960, 460, 271, 24)) + self.smoke_pickup_zone_checkBox.setGeometry(QtCore.QRect(500, 541, 231, 20)) font = QtGui.QFont() font.setPointSize(9) self.smoke_pickup_zone_checkBox.setFont(font) self.smoke_pickup_zone_checkBox.setChecked(False) self.smoke_pickup_zone_checkBox.setObjectName("smoke_pickup_zone_checkBox") self.game_status_checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.game_status_checkBox.setGeometry(QtCore.QRect(960, 490, 271, 24)) + self.game_status_checkBox.setGeometry(QtCore.QRect(500, 570, 221, 21)) font = QtGui.QFont() font.setPointSize(9) self.game_status_checkBox.setFont(font) @@ -277,24 +277,24 @@ 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, 380, 261, 23)) + self.label.setGeometry(QtCore.QRect(570, 340, 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, 380, 47, 31)) + self.inf_spawn_spinBox.setGeometry(QtCore.QRect(510, 340, 51, 31)) font = QtGui.QFont() font.setPointSize(12) self.inf_spawn_spinBox.setFont(font) self.inf_spawn_spinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus) self.inf_spawn_spinBox.setMinimum(0) self.inf_spawn_spinBox.setMaximum(20) - self.inf_spawn_spinBox.setProperty("value", 2) + 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, 330, 47, 31)) + self.troop_drop_spinBox.setGeometry(QtCore.QRect(510, 300, 51, 31)) font = QtGui.QFont() font.setPointSize(12) self.troop_drop_spinBox.setFont(font) @@ -303,23 +303,23 @@ class Ui_MainWindow(object): self.troop_drop_spinBox.setMaximum(10) self.troop_drop_spinBox.setProperty("value", 4) self.troop_drop_spinBox.setObjectName("troop_drop_spinBox") - self.force_offroad_checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.force_offroad_checkBox.setGeometry(QtCore.QRect(960, 548, 161, 24)) + self.random_weather_checkBox = QtWidgets.QCheckBox(self.centralwidget) + self.random_weather_checkBox.setGeometry(QtCore.QRect(980, 420, 211, 24)) font = QtGui.QFont() font.setPointSize(9) - self.force_offroad_checkBox.setFont(font) - self.force_offroad_checkBox.setChecked(False) - self.force_offroad_checkBox.setTristate(False) - self.force_offroad_checkBox.setObjectName("force_offroad_checkBox") + self.random_weather_checkBox.setFont(font) + self.random_weather_checkBox.setChecked(False) + 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, 330, 281, 23)) + self.label_3.setGeometry(QtCore.QRect(570, 300, 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(990, 180, 251, 27)) + self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(980, 180, 251, 27)) font = QtGui.QFont() font.setPointSize(10) font.setBold(False) @@ -328,7 +328,7 @@ class Ui_MainWindow(object): self.apcs_spawn_checkBox.setObjectName("apcs_spawn_checkBox") self.generateButton = QtWidgets.QPushButton(self.centralwidget) self.generateButton.setEnabled(True) - self.generateButton.setGeometry(QtCore.QRect(710, 600, 231, 51)) + self.generateButton.setGeometry(QtCore.QRect(750, 600, 231, 51)) font = QtGui.QFont() font.setPointSize(8) font.setBold(True) @@ -336,7 +336,7 @@ class Ui_MainWindow(object): self.generateButton.setStyleSheet("") self.generateButton.setObjectName("generateButton") self.farp_always = QtWidgets.QRadioButton(self.centralwidget) - self.farp_always.setGeometry(QtCore.QRect(510, 480, 261, 24)) + self.farp_always.setGeometry(QtCore.QRect(500, 431, 261, 24)) font = QtGui.QFont() font.setPointSize(9) self.farp_always.setFont(font) @@ -345,14 +345,14 @@ class Ui_MainWindow(object): self.farp_buttonGroup.setObjectName("farp_buttonGroup") self.farp_buttonGroup.addButton(self.farp_always) self.farp_never = QtWidgets.QRadioButton(self.centralwidget) - self.farp_never.setGeometry(QtCore.QRect(510, 540, 271, 24)) + self.farp_never.setGeometry(QtCore.QRect(500, 491, 271, 24)) font = QtGui.QFont() font.setPointSize(9) self.farp_never.setFont(font) self.farp_never.setObjectName("farp_never") self.farp_buttonGroup.addButton(self.farp_never) self.farp_gunits = QtWidgets.QRadioButton(self.centralwidget) - self.farp_gunits.setGeometry(QtCore.QRect(510, 509, 261, 24)) + self.farp_gunits.setGeometry(QtCore.QRect(500, 460, 261, 24)) font = QtGui.QFont() font.setPointSize(9) self.farp_gunits.setFont(font) @@ -397,7 +397,7 @@ class Ui_MainWindow(object): self.rateButton1.setText("") self.rateButton1.setObjectName("rateButton1") self.hotstart_checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.hotstart_checkBox.setGeometry(QtCore.QRect(960, 430, 271, 24)) + self.hotstart_checkBox.setGeometry(QtCore.QRect(980, 520, 271, 24)) font = QtGui.QFont() font.setPointSize(9) self.hotstart_checkBox.setFont(font) @@ -440,6 +440,21 @@ class Ui_MainWindow(object): self.rateButton5.setStyleSheet("border-image:url(\'../assets/star_full.png\');") self.rateButton5.setText("") self.rateButton5.setObjectName("rateButton5") + self.time_comboBox = QtWidgets.QComboBox(self.centralwidget) + self.time_comboBox.setGeometry(QtCore.QRect(980, 370, 161, 33)) + font = QtGui.QFont() + font.setPointSize(10) + font.setBold(False) + self.time_comboBox.setFont(font) + self.time_comboBox.setObjectName("time_comboBox") + self.farp_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget) + self.farp_spawn_checkBox.setGeometry(QtCore.QRect(980, 550, 271, 24)) + font = QtGui.QFont() + font.setPointSize(9) + self.farp_spawn_checkBox.setFont(font) + self.farp_spawn_checkBox.setChecked(False) + self.farp_spawn_checkBox.setTristate(False) + self.farp_spawn_checkBox.setObjectName("farp_spawn_checkBox") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 29)) @@ -564,10 +579,10 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator")) - self.logistics_crates_checkBox.setStatusTip(_translate("MainWindow", "Enable CTLD logistics crates for building ground units and air defenses. Pickup logistics containers to create new logistics sites.")) - self.logistics_crates_checkBox.setText(_translate("MainWindow", "Logistics")) - 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.")) - self.zone_sams_checkBox.setText(_translate("MainWindow", "Inactive Zone SAMs")) + 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.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" @@ -575,6 +590,7 @@ class Ui_MainWindow(object): "p, li { white-space: pre-wrap; }\n" "
\n" "Provide close air support for our convoys as we take back Las Vegas from the enemy!