Merge pull request #37 from spencershepard/develop

v1.2.0
This commit is contained in:
spencershepard 2022-06-30 09:04:05 -07:00 committed by GitHub
commit 2f6c89983a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1481 additions and 8393 deletions

9
.gitignore vendored
View File

@ -15,5 +15,14 @@ Generator/utils/extract units/units.txt
generator.log generator.log
templates/Scenarios/user templates/Scenarios/user
templates/Scenarios/downloaded templates/Scenarios/downloaded
templates/Imports/user
templates/Imports/downloaded
templates/Forces/user
templates/Forces/downloaded
config/user-data.yaml config/user-data.yaml
*.exe *.exe
config/user-data.yaml
server/user-files/modules/mapscript-sql.py
distribution/
MissionOutput/

View File

@ -2,6 +2,7 @@ import json
import yaml import yaml
import sys import sys
import os import os
import operator
import RotorOpsMission as ROps import RotorOpsMission as ROps
import RotorOpsUnits import RotorOpsUnits
@ -28,10 +29,18 @@ import qtmodern.windows
# UPDATE BUILD VERSION # UPDATE BUILD VERSION
maj_version = 1 maj_version = 1
minor_version = 1 minor_version = 2
patch_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/' user_files_url = 'https://dcs-helicopters.com/user-files/'
version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'
#Setup logfile and exception handler #Setup logfile and exception handler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -50,27 +59,39 @@ class directories:
os.chdir("..") os.chdir("..")
cls.home_dir = os.getcwd() cls.home_dir = os.getcwd()
cls.scenarios = cls.home_dir + "\\templates\\Scenarios" 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.scripts = cls.home_dir + "\\scripts"
cls.sound = cls.home_dir + "\\sound\\embedded" cls.sound = cls.home_dir + "\\sound\\embedded"
cls.output = cls.home_dir + "\\MissionOutput" cls.output = cls.home_dir + "\\MissionOutput"
cls.assets = cls.home_dir + "\\assets" 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.user_datafile_path = cls.home_dir + "\\config\\user-data.yaml"
cls.scenarios_downloaded = cls.scenarios + "\\downloaded" cls.scenarios_downloaded = cls.scenarios + "\\downloaded"
cls.scenarios_user = cls.scenarios + "\\user" cls.scenarios_user = cls.scenarios + "\\user"
cls.default_config = cls.home_dir + '\\config\\default-config.yaml' cls.default_config = cls.home_dir + '\\config\\default-config.yaml'
os.chdir(current_dir) 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): def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt): #example of handling error subclasses if issubclass(exc_type, KeyboardInterrupt): #example of handling error subclasses
sys.__excepthook__(exc_type, exc_value, exc_traceback) sys.__excepthook__(exc_type, exc_value, exc_traceback)
return return
QApplication.restoreOverrideCursor()
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
msg = QMessageBox() msg = QMessageBox()
msg.setWindowTitle("Uncaught exception") msg.setWindowTitle("Uncaught exception")
@ -82,9 +103,6 @@ sys.excepthook = handle_exception
version_string = str(maj_version) + "." + str(minor_version) + "." + str(patch_version) version_string = str(maj_version) + "." + str(minor_version) + "." + str(patch_version)
# scenarios = []
red_forces_files = []
blue_forces_files = []
defenders_text = "Defending Forces:" defenders_text = "Defending Forces:"
attackers_text = "Attacking Forces:" attackers_text = "Attacking Forces:"
ratings_json = None ratings_json = None
@ -120,16 +138,17 @@ class Window(QMainWindow, Ui_MainWindow):
self.player_slots = [] self.player_slots = []
self.user_output_dir = None self.user_output_dir = None
self.user_data = None self.user_data = None
self.forces_list = []
self.imports_list = []
self.user_data = self.loadUserData() self.user_data = self.loadUserData()
self.m = ROps.RotorOpsMission()
self.setupUi(self) self.setupUi(self)
self.connectSignalsSlots() self.connectSignalsSlots()
self.populateScenarios() self.populateScenarios()
self.populateForces("red", self.redforces_comboBox, red_forces_files) self.populateForces()
self.populateForces("blue", self.blueforces_comboBox, blue_forces_files)
self.populateSlotSelection() self.populateSlotSelection()
self.getImports()
# self.blue_forces_label.setText(attackers_text) # self.blue_forces_label.setText(attackers_text)
# self.red_forces_label.setText(defenders_text) # self.red_forces_label.setText(defenders_text)
@ -139,11 +158,15 @@ class Window(QMainWindow, Ui_MainWindow):
"QStatusBar{padding-left:5px;}") "QStatusBar{padding-left:5px;}")
self.version_label.setText("Version " + version_string) 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): def connectSignalsSlots(self):
@ -212,7 +235,7 @@ class Window(QMainWindow, Ui_MainWindow):
basename = filename.removesuffix('.miz') basename = filename.removesuffix('.miz')
mizpath = os.path.join(path, folder, filename) mizpath = os.path.join(path, folder, filename)
# create scenario object # create scenario object
s = MissionGeneratorScenario.Scenario(mizpath, basename) s = MissionGeneratorTemplates.Scenario(mizpath, basename)
#apply some properties if found in the downloads directory #apply some properties if found in the downloads directory
if path == directories.scenarios_downloaded: if path == directories.scenarios_downloaded:
@ -221,7 +244,6 @@ class Window(QMainWindow, Ui_MainWindow):
s.packageID = folder s.packageID = folder
if ratings_json: if ratings_json:
print(ratings_json)
for module in ratings_json: for module in ratings_json:
if module['package'] == folder: if module['package'] == folder:
s.rating = module["avg_rating"] s.rating = module["avg_rating"]
@ -256,8 +278,8 @@ class Window(QMainWindow, Ui_MainWindow):
t_scenarios.append(s) t_scenarios.append(s)
scenarios = t_scenarios.copy() scenarios = t_scenarios.copy()
#self.scenario_comboBox.addItem(s.name) self.scenarios_list = sorted(scenarios, key=lambda x: x.name, reverse=False)
self.scenarios_list = scenarios.copy()
for s in self.scenarios_list: for s in self.scenarios_list:
self.scenario_comboBox.addItem(s.name) self.scenario_comboBox.addItem(s.name)
@ -268,18 +290,67 @@ class Window(QMainWindow, Ui_MainWindow):
self.populateScenarios() self.populateScenarios()
# self.scenarioChanged() haven't tried yet # self.scenarioChanged() haven't tried yet
def populateForces(self, side, combobox, files_list): def populateForces(self):
os.chdir(directories.home_dir) self.forces_list = []
# 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)
for filename in dir_list: 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"): if filename.endswith(".miz"):
files_list.append(filename) basename = filename.removesuffix('.miz')
combobox.addItem(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): def populateSlotSelection(self):
self.slot_template_comboBox.addItem("Multiple Slots") self.slot_template_comboBox.addItem("Multiple Slots")
@ -321,7 +392,6 @@ class Window(QMainWindow, Ui_MainWindow):
# reset some UI elements # reset some UI elements
self.defense_checkBox.setEnabled(True) 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.setEnabled(True)
@ -362,11 +432,14 @@ class Window(QMainWindow, Ui_MainWindow):
button.setEnabled(True) button.setEnabled(True)
if 'blue_forces' in config: 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 'red_forces' in config:
if self.redforces_comboBox.findText(config['red_forces']) >= 0: for template in self.forces_list:
self.redforces_comboBox.setCurrentIndex(self.redforces_comboBox.findText(config['red_forces'])) if template.basename == config['red_forces']:
self.redforces_comboBox.setCurrentIndex(self.redforces_comboBox.findText(template.name))
except Exception as e: except Exception as e:
logger.error("Error loading config file: " + str(e)) logger.error("Error loading config file: " + str(e))
@ -416,16 +489,16 @@ class Window(QMainWindow, Ui_MainWindow):
return return
QApplication.setOverrideCursor(Qt.WaitCursor) QApplication.setOverrideCursor(Qt.WaitCursor)
self.slot_template_comboBox.setCurrentIndex(0)
self.scenario = self.scenarios_list[self.scenario_comboBox.currentIndex()] self.scenario = self.scenarios_list[self.scenario_comboBox.currentIndex()]
if self.scenario.config: # reset generator options to default
self.applyScenarioConfig(self.scenario.config)
self.m.setConfig(self.scenario.config)
else:
default_config = self.loadScenarioConfig(directories.default_config) default_config = self.loadScenarioConfig(directories.default_config)
self.applyScenarioConfig(default_config) self.applyScenarioConfig(default_config)
self.m.setConfig(default_config)
if self.scenario.config:
self.applyScenarioConfig(self.scenario.config)
path = self.scenario.path.removesuffix(".miz") + ".jpg" path = self.scenario.path.removesuffix(".miz") + ".jpg"
if os.path.isfile(path): if os.path.isfile(path):
@ -462,17 +535,27 @@ class Window(QMainWindow, Ui_MainWindow):
def generateMissionAction(self): def generateMissionAction(self):
QApplication.setOverrideCursor(Qt.WaitCursor) QApplication.setOverrideCursor(Qt.WaitCursor)
red_forces_filename = red_forces_files[self.redforces_comboBox.currentIndex()] red_forces = self.forces_list[self.redforces_comboBox.currentIndex()]
blue_forces_filename = blue_forces_files[self.blueforces_comboBox.currentIndex()] blue_forces = self.forces_list[self.blueforces_comboBox.currentIndex()]
scenario_name = self.scenario.name scenario_name = self.scenario.name
scenario_path = self.scenario.path 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 = { data = {
"source": source, "objects": objects,
"credits": credits,
"scenario_file": scenario_path, "scenario_file": scenario_path,
"scenario_name": scenario_name, "scenario_name": scenario_name,
"red_forces_filename": red_forces_filename, "red_forces_path": red_forces.path,
"blue_forces_filename": blue_forces_filename, "blue_forces_path": blue_forces.path,
"red_quantity": self.redqty_spinBox.value(), "red_quantity": self.redqty_spinBox.value(),
"blue_quantity": self.blueqty_spinBox.value(), "blue_quantity": self.blueqty_spinBox.value(),
"inf_spawn_qty": self.inf_spawn_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_awacs": self.awacs_checkBox.isChecked(),
"f_tankers": self.tankers_checkBox.isChecked(), "f_tankers": self.tankers_checkBox.isChecked(),
"voiceovers": self.voiceovers_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(), "game_display": self.game_status_checkBox.isChecked(),
"defending": self.defense_checkBox.isChecked(), "defending": self.defense_checkBox.isChecked(),
"slots": self.slot_template_comboBox.currentText(), "slots": self.slot_template_comboBox.currentText(),
@ -494,6 +577,17 @@ class Window(QMainWindow, Ui_MainWindow):
"smoke_pickup_zones": self.smoke_pickup_zone_checkBox.isChecked(), "smoke_pickup_zones": self.smoke_pickup_zone_checkBox.isChecked(),
"player_slots": self.player_slots, "player_slots": self.player_slots,
"player_hotstart": self.hotstart_checkBox.isChecked(), "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:") logger.info("Generating mission with options:")
@ -674,7 +768,7 @@ class Window(QMainWindow, Ui_MainWindow):
def checkVersion(splashscreen): def checkVersion(splashscreen):
version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'
try: try:
r = requests.get(version_url, allow_redirects=False, timeout=7) r = requests.get(version_url, allow_redirects=False, timeout=7)
v = yaml.safe_load(r.content) 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): 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: try:
r = requests.get(modules_map_url, allow_redirects=False, timeout=7) r = requests.get(modules_map_url, allow_redirects=False, timeout=7)
if not r.status_code == 200: if not r.status_code == 200:
logger.error("Could not retrieve the modules map.") logger.error("Could not retrieve the modules map.")
x = msg.exec_()
return return
except: except:
logger.error("Failed to retrieve module map.") logger.error("Failed to retrieve module map.")
x = msg.exec_()
return return
module_list = yaml.safe_load(r.content) module_list = yaml.safe_load(r.content)
files_success = [] files_success = []
files_failed = [] files_failed = []
new_scenarios = [] new_modules = []
updated_scenarios = [] updated_modules = []
outversioned_modules = []
# Download scenarios files # Download scenarios files
#os.chdir(directories.scenarios)
if module_list: if module_list:
for module in module_list: for module in module_list:
@ -725,14 +821,30 @@ def loadModules(splashscreen):
should_download = False should_download = False
new_module = 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 # 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): if os.path.exists(package_file_path):
pkg_file = yaml.safe_load(open(package_file_path)) pkg_file = yaml.safe_load(open(package_file_path))
else: else:
pkg_file = None pkg_file = None
# 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 # compare local and remote versions
if pkg_file and 'version' in pkg_file: if pkg_file and 'version' in pkg_file:
local_version = pkg_file['version'] local_version = pkg_file['version']
@ -744,6 +856,20 @@ def loadModules(splashscreen):
should_download = True should_download = True
new_module = 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: if should_download:
logger.info("Updating module: " + module) logger.info("Updating module: " + module)
module_dir = os.path.join(directories.home_dir, module_list[module]["path"], 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 # download files in remote package
for filename in module_list[module]["files"]: for filename in module_list[module]["files"]:
broken_file = False broken_file = False
type_path = module_list[module]["type"]
splash.showMessage("Downloading " + filename + " ...", Qt.AlignHCenter | Qt.AlignTop, Qt.white) splash.showMessage("Downloading " + filename + " ...", Qt.AlignHCenter | Qt.AlignTop, Qt.white)
app.processEvents() app.processEvents()
url = modules_url + module + "/" + filename url = modules_url + type_path + "/" + module + "/" + filename
try: try:
r = requests.get(url, allow_redirects=False, timeout=10) r = requests.get(url, allow_redirects=False, timeout=10)
except: except:
@ -771,9 +898,9 @@ def loadModules(splashscreen):
# do some stuff for the dialog popup # do some stuff for the dialog popup
if filename.endswith('.miz') and "name" in module_list[module]: if filename.endswith('.miz') and "name" in module_list[module]:
if new_module: if new_module:
new_scenarios.append(module_list[module]["name"]) new_modules.append(module_list[module]["name"])
else: else:
updated_scenarios.append(module_list[module]["name"]) updated_modules.append(module_list[module]["name"])
else: else:
broken_file = True broken_file = True
files_failed.append(filename) files_failed.append(filename)
@ -791,7 +918,7 @@ def loadModules(splashscreen):
logger.error("Problem encountered with modules map.") logger.error("Problem encountered with modules map.")
# show a popup if we downloaded any packages # 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: if len(files_failed) > 0:
fs = "" fs = ""
for filename in files_failed: for filename in files_failed:
@ -800,16 +927,18 @@ def loadModules(splashscreen):
msg = QMessageBox() msg = QMessageBox()
msg.setWindowTitle("Downloaded Files") msg.setWindowTitle("Downloaded Files")
message = "" message = ""
if len(new_scenarios) > 0: if len(new_modules) > 0:
message = message + "New scenarios added: \n\n" message = message + "New modules added: \n\n"
for name in new_scenarios: for name in new_modules:
message = message + name + "\n" message = message + name + "\n"
if len(updated_scenarios) > 0: if len(updated_modules) > 0:
message = message + "\nScenarios updated: \n" message = message + "\nModules updated: \n"
for name in updated_scenarios: for name in updated_modules:
message = message + name + "\n" message = message + name + "\n"
if len(files_failed) > 0: if len(files_failed) > 0:
message = message + "\n\n" + str(len(files_failed)) + " files failed." 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) msg.setText(message)
x = msg.exec_() x = msg.exec_()
else: else:

View File

@ -19,6 +19,8 @@ class Scenario:
self.rating_qty = None self.rating_qty = None
self.packageID = None self.packageID = None
self.local_rating = None self.local_rating = None
self.author = "unknown"
def applyConfig(self, config): def applyConfig(self, config):
self.config = config self.config = config
@ -31,8 +33,16 @@ class Scenario:
if 'tags' in config: if 'tags' in config:
for tag in config['tags']: for tag in config['tags']:
self.tags.append(tag) 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): def evaluateMiz(self):
# check if we have the miz file # 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"]

View File

@ -34,7 +34,7 @@ class Ui_MainWindow(object):
self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.logistics_crates_checkBox = QtWidgets.QCheckBox(self.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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -42,7 +42,7 @@ class Ui_MainWindow(object):
self.logistics_crates_checkBox.setChecked(True) self.logistics_crates_checkBox.setChecked(True)
self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox") self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox")
self.zone_sams_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -79,9 +79,9 @@ class Ui_MainWindow(object):
self.description_textBrowser.setObjectName("description_textBrowser") self.description_textBrowser.setObjectName("description_textBrowser")
self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.defense_checkBox.setEnabled(True) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(11)
font.setBold(False) font.setBold(False)
self.defense_checkBox.setFont(font) self.defense_checkBox.setFont(font)
self.defense_checkBox.setCheckable(True) self.defense_checkBox.setCheckable(True)
@ -116,7 +116,7 @@ class Ui_MainWindow(object):
self.scenario_label_8.setFont(font) self.scenario_label_8.setFont(font)
self.scenario_label_8.setObjectName("scenario_label_8") self.scenario_label_8.setObjectName("scenario_label_8")
self.slot_template_comboBox = QtWidgets.QComboBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -216,7 +216,7 @@ class Ui_MainWindow(object):
self.e_attack_helos_spinBox.setKeyboardTracking(True) self.e_attack_helos_spinBox.setKeyboardTracking(True)
self.e_attack_helos_spinBox.setMinimum(0) self.e_attack_helos_spinBox.setMinimum(0)
self.e_attack_helos_spinBox.setMaximum(8) 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.e_attack_helos_spinBox.setObjectName("e_attack_helos_spinBox")
self.scenario_label_7 = QtWidgets.QLabel(self.centralwidget) 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, 180, 271, 24))
@ -226,20 +226,20 @@ class Ui_MainWindow(object):
self.scenario_label_7.setFont(font) self.scenario_label_7.setFont(font)
self.scenario_label_7.setObjectName("scenario_label_7") self.scenario_label_7.setObjectName("scenario_label_7")
self.label_2 = QtWidgets.QLabel(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.label_2.setFont(font) self.label_2.setFont(font)
self.label_2.setObjectName("label_2") self.label_2.setObjectName("label_2")
self.scenario_label_9 = QtWidgets.QLabel(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
self.scenario_label_9.setFont(font) self.scenario_label_9.setFont(font)
self.scenario_label_9.setObjectName("scenario_label_9") self.scenario_label_9.setObjectName("scenario_label_9")
self.awacs_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -247,7 +247,7 @@ class Ui_MainWindow(object):
self.awacs_checkBox.setChecked(True) self.awacs_checkBox.setChecked(True)
self.awacs_checkBox.setObjectName("awacs_checkBox") self.awacs_checkBox.setObjectName("awacs_checkBox")
self.tankers_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -255,21 +255,21 @@ class Ui_MainWindow(object):
self.tankers_checkBox.setChecked(True) self.tankers_checkBox.setChecked(True)
self.tankers_checkBox.setObjectName("tankers_checkBox") self.tankers_checkBox.setObjectName("tankers_checkBox")
self.voiceovers_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.voiceovers_checkBox.setFont(font) self.voiceovers_checkBox.setFont(font)
self.voiceovers_checkBox.setChecked(True) self.voiceovers_checkBox.setChecked(True)
self.voiceovers_checkBox.setObjectName("voiceovers_checkBox") self.voiceovers_checkBox.setObjectName("voiceovers_checkBox")
self.smoke_pickup_zone_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.smoke_pickup_zone_checkBox.setFont(font) self.smoke_pickup_zone_checkBox.setFont(font)
self.smoke_pickup_zone_checkBox.setChecked(False) self.smoke_pickup_zone_checkBox.setChecked(False)
self.smoke_pickup_zone_checkBox.setObjectName("smoke_pickup_zone_checkBox") self.smoke_pickup_zone_checkBox.setObjectName("smoke_pickup_zone_checkBox")
self.game_status_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.game_status_checkBox.setFont(font) self.game_status_checkBox.setFont(font)
@ -277,24 +277,24 @@ class Ui_MainWindow(object):
self.game_status_checkBox.setTristate(False) self.game_status_checkBox.setTristate(False)
self.game_status_checkBox.setObjectName("game_status_checkBox") self.game_status_checkBox.setObjectName("game_status_checkBox")
self.label = QtWidgets.QLabel(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.label.setFont(font) self.label.setFont(font)
self.label.setObjectName("label") self.label.setObjectName("label")
self.inf_spawn_spinBox = QtWidgets.QSpinBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(12) font.setPointSize(12)
self.inf_spawn_spinBox.setFont(font) self.inf_spawn_spinBox.setFont(font)
self.inf_spawn_spinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus) self.inf_spawn_spinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus)
self.inf_spawn_spinBox.setMinimum(0) self.inf_spawn_spinBox.setMinimum(0)
self.inf_spawn_spinBox.setMaximum(20) 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.inf_spawn_spinBox.setObjectName("inf_spawn_spinBox")
self.troop_drop_spinBox = QtWidgets.QSpinBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(12) font.setPointSize(12)
self.troop_drop_spinBox.setFont(font) self.troop_drop_spinBox.setFont(font)
@ -303,23 +303,23 @@ class Ui_MainWindow(object):
self.troop_drop_spinBox.setMaximum(10) self.troop_drop_spinBox.setMaximum(10)
self.troop_drop_spinBox.setProperty("value", 4) self.troop_drop_spinBox.setProperty("value", 4)
self.troop_drop_spinBox.setObjectName("troop_drop_spinBox") self.troop_drop_spinBox.setObjectName("troop_drop_spinBox")
self.force_offroad_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.random_weather_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.force_offroad_checkBox.setGeometry(QtCore.QRect(960, 548, 161, 24)) self.random_weather_checkBox.setGeometry(QtCore.QRect(980, 420, 211, 24))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.force_offroad_checkBox.setFont(font) self.random_weather_checkBox.setFont(font)
self.force_offroad_checkBox.setChecked(False) self.random_weather_checkBox.setChecked(False)
self.force_offroad_checkBox.setTristate(False) self.random_weather_checkBox.setTristate(False)
self.force_offroad_checkBox.setObjectName("force_offroad_checkBox") self.random_weather_checkBox.setObjectName("random_weather_checkBox")
self.label_3 = QtWidgets.QLabel(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.label_3.setFont(font) self.label_3.setFont(font)
self.label_3.setObjectName("label_3") self.label_3.setObjectName("label_3")
self.apcs_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -328,7 +328,7 @@ class Ui_MainWindow(object):
self.apcs_spawn_checkBox.setObjectName("apcs_spawn_checkBox") self.apcs_spawn_checkBox.setObjectName("apcs_spawn_checkBox")
self.generateButton = QtWidgets.QPushButton(self.centralwidget) self.generateButton = QtWidgets.QPushButton(self.centralwidget)
self.generateButton.setEnabled(True) 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 = QtGui.QFont()
font.setPointSize(8) font.setPointSize(8)
font.setBold(True) font.setBold(True)
@ -336,7 +336,7 @@ class Ui_MainWindow(object):
self.generateButton.setStyleSheet("") self.generateButton.setStyleSheet("")
self.generateButton.setObjectName("generateButton") self.generateButton.setObjectName("generateButton")
self.farp_always = QtWidgets.QRadioButton(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.farp_always.setFont(font) self.farp_always.setFont(font)
@ -345,14 +345,14 @@ class Ui_MainWindow(object):
self.farp_buttonGroup.setObjectName("farp_buttonGroup") self.farp_buttonGroup.setObjectName("farp_buttonGroup")
self.farp_buttonGroup.addButton(self.farp_always) self.farp_buttonGroup.addButton(self.farp_always)
self.farp_never = QtWidgets.QRadioButton(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.farp_never.setFont(font) self.farp_never.setFont(font)
self.farp_never.setObjectName("farp_never") self.farp_never.setObjectName("farp_never")
self.farp_buttonGroup.addButton(self.farp_never) self.farp_buttonGroup.addButton(self.farp_never)
self.farp_gunits = QtWidgets.QRadioButton(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.farp_gunits.setFont(font) self.farp_gunits.setFont(font)
@ -397,7 +397,7 @@ class Ui_MainWindow(object):
self.rateButton1.setText("") self.rateButton1.setText("")
self.rateButton1.setObjectName("rateButton1") self.rateButton1.setObjectName("rateButton1")
self.hotstart_checkBox = QtWidgets.QCheckBox(self.centralwidget) 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 = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.hotstart_checkBox.setFont(font) 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.setStyleSheet("border-image:url(\'../assets/star_full.png\');")
self.rateButton5.setText("") self.rateButton5.setText("")
self.rateButton5.setObjectName("rateButton5") 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) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 29)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 29))
@ -564,10 +579,10 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator")) 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.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")) 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.")) 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", "Inactive Zone SAMs")) self.zone_sams_checkBox.setText(_translate("MainWindow", "Protect Inactive Zones"))
self.red_forces_label.setText(_translate("MainWindow", "Red Forces:")) 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.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" 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"
@ -575,6 +590,7 @@ class Ui_MainWindow(object):
"p, li { white-space: pre-wrap; }\n" "p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Segoe UI\'; font-size:9pt; font-weight:400; font-style:normal;\">\n" "</style></head><body style=\" font-family:\'Segoe UI\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'MS Shell Dlg 2\'; font-size:10pt;\">Provide close air support for our convoys as we take back Las Vegas from the enemy!</span></p></body></html>")) "<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'MS Shell Dlg 2\'; font-size:10pt;\">Provide close air support for our convoys as we take back Las Vegas from the enemy!</span></p></body></html>"))
self.defense_checkBox.setStatusTip(_translate("MainWindow", "Turn the tables and defend your zones against the enemy\'s attack."))
self.defense_checkBox.setText(_translate("MainWindow", "Blue on Defense")) self.defense_checkBox.setText(_translate("MainWindow", "Blue on Defense"))
self.redqty_spinBox.setStatusTip(_translate("MainWindow", "Red vehicle groups per staging or conflict zone.")) self.redqty_spinBox.setStatusTip(_translate("MainWindow", "Red vehicle groups per staging or conflict zone."))
self.redforces_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own custom ground forces groups to be automatically generated.")) self.redforces_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own custom ground forces groups to be automatically generated."))
@ -602,20 +618,20 @@ class Ui_MainWindow(object):
self.tankers_checkBox.setText(_translate("MainWindow", "Friendly Tankers")) self.tankers_checkBox.setText(_translate("MainWindow", "Friendly Tankers"))
self.voiceovers_checkBox.setStatusTip(_translate("MainWindow", "Voiceovers from the ground commander. Helps keep focus on the active zone.")) self.voiceovers_checkBox.setStatusTip(_translate("MainWindow", "Voiceovers from the ground commander. Helps keep focus on the active zone."))
self.voiceovers_checkBox.setText(_translate("MainWindow", "Voiceovers")) self.voiceovers_checkBox.setText(_translate("MainWindow", "Voiceovers"))
self.smoke_pickup_zone_checkBox.setStatusTip(_translate("MainWindow", "Infinite troop pickup zones will be marked with blue smoke.")) self.smoke_pickup_zone_checkBox.setStatusTip(_translate("MainWindow", "Troop pickup zones and FARPs will be marked with blue smoke."))
self.smoke_pickup_zone_checkBox.setText(_translate("MainWindow", "Smoke at Troop Pickup Zones")) self.smoke_pickup_zone_checkBox.setText(_translate("MainWindow", "Smoke at Troop Pickup Zones"))
self.game_status_checkBox.setStatusTip(_translate("MainWindow", "Enable an onscreen zone status display. This helps keep focus on the active conflict zone.")) self.game_status_checkBox.setStatusTip(_translate("MainWindow", "Enable an onscreen zone status display. This helps keep focus on the active conflict zone."))
self.game_status_checkBox.setText(_translate("MainWindow", "Game Status Display")) self.game_status_checkBox.setText(_translate("MainWindow", "Game Status Display"))
self.label.setStatusTip(_translate("MainWindow", "This value is multiplied by the number of spawn zones in the mission template.")) self.label.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game."))
self.label.setText(_translate("MainWindow", "Infantry Spawns per zone")) 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", "This value is multiplied by the number of spawn zones in the mission template."))
self.troop_drop_spinBox.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight.")) self.troop_drop_spinBox.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight."))
self.force_offroad_checkBox.setStatusTip(_translate("MainWindow", "May help prevent long travel times or pathfinding issues. ")) self.random_weather_checkBox.setStatusTip(_translate("MainWindow", "Random weather preset will be applied."))
self.force_offroad_checkBox.setText(_translate("MainWindow", "Force Offroad")) self.random_weather_checkBox.setText(_translate("MainWindow", "Random Weather"))
self.label_3.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight.")) self.label_3.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight."))
self.label_3.setText(_translate("MainWindow", "Transport Drop Points")) self.label_3.setText(_translate("MainWindow", "Transport Drop Points"))
self.apcs_spawn_checkBox.setStatusTip(_translate("MainWindow", "Friendly/enemy APCs will drop infantry when reaching a new conflict zone. Disables infinite troop pickups from conflict zones (you must pick up existing troops).")) self.apcs_spawn_checkBox.setStatusTip(_translate("MainWindow", "Friendly/enemy APCs will drop infantry when reaching a new conflict zone. "))
self.apcs_spawn_checkBox.setText(_translate("MainWindow", "Dynamic Troops")) self.apcs_spawn_checkBox.setText(_translate("MainWindow", "APCs Spawn Infantry"))
self.generateButton.setStatusTip(_translate("MainWindow", "Click to generate mission.")) self.generateButton.setStatusTip(_translate("MainWindow", "Click to generate mission."))
self.generateButton.setText(_translate("MainWindow", "GENERATE MISSION")) self.generateButton.setText(_translate("MainWindow", "GENERATE MISSION"))
self.farp_always.setStatusTip(_translate("MainWindow", "Always spawn a FARP in defeated conflict zones.")) self.farp_always.setStatusTip(_translate("MainWindow", "Always spawn a FARP in defeated conflict zones."))
@ -627,12 +643,15 @@ class Ui_MainWindow(object):
self.nextScenario_pushButton.setText(_translate("MainWindow", ">")) self.nextScenario_pushButton.setText(_translate("MainWindow", ">"))
self.prevScenario_pushButton.setText(_translate("MainWindow", "<")) self.prevScenario_pushButton.setText(_translate("MainWindow", "<"))
self.rateButton1.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario.")) self.rateButton1.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario."))
self.hotstart_checkBox.setStatusTip(_translate("MainWindow", "Player helicopters start with engines running on the ground. No effect if player slots says \'Locked to scenario\'")) self.hotstart_checkBox.setStatusTip(_translate("MainWindow", "Player helicopters start with engines running on the ground. No effect for FARP spawns or if player slots says \'Locked to scenario\'"))
self.hotstart_checkBox.setText(_translate("MainWindow", "Player Hotstart")) self.hotstart_checkBox.setText(_translate("MainWindow", "Player Hotstart"))
self.rateButton2.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario.")) self.rateButton2.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario."))
self.rateButton3.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario.")) self.rateButton3.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario."))
self.rateButton4.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario.")) self.rateButton4.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario."))
self.rateButton5.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario.")) self.rateButton5.setStatusTip(_translate("MainWindow", "Submit a review for this mission scenario."))
self.time_comboBox.setStatusTip(_translate("MainWindow", "Mission start time of day. \'Default\' is the start time as defined by the mission template designer."))
self.farp_spawn_checkBox.setStatusTip(_translate("MainWindow", "Add helicopter slots where zone FARPs will be built. Helicopters will be empty fuel, requiring the FARP to be established to refuel and rearm."))
self.farp_spawn_checkBox.setText(_translate("MainWindow", "Spawns at zone FARPs"))
self.menuMap.setTitle(_translate("MainWindow", "Map")) self.menuMap.setTitle(_translate("MainWindow", "Map"))
self.menuFilter.setTitle(_translate("MainWindow", "Filter")) self.menuFilter.setTitle(_translate("MainWindow", "Filter"))
self.menuPreferences.setTitle(_translate("MainWindow", "Preferences")) self.menuPreferences.setTitle(_translate("MainWindow", "Preferences"))
@ -658,8 +677,8 @@ class Ui_MainWindow(object):
self.action_downloadButton.setToolTip(_translate("MainWindow", "_downloadButton")) self.action_downloadButton.setToolTip(_translate("MainWindow", "_downloadButton"))
self.action_rateButton1.setText(_translate("MainWindow", "_rateButton1")) self.action_rateButton1.setText(_translate("MainWindow", "_rateButton1"))
self.action_rateButton1.setToolTip(_translate("MainWindow", "_rateButton1")) self.action_rateButton1.setToolTip(_translate("MainWindow", "_rateButton1"))
self.actionSingle_Player.setText(_translate("MainWindow", "Single-Player")) self.actionSingle_Player.setText(_translate("MainWindow", "Single-Player Only"))
self.actionCo_Op.setText(_translate("MainWindow", "Co-Op")) self.actionCo_Op.setText(_translate("MainWindow", "Co-Op Only"))
self.actionMapMenu.setText(_translate("MainWindow", "actionMapMenu")) self.actionMapMenu.setText(_translate("MainWindow", "actionMapMenu"))
self.actionFilterMenu.setText(_translate("MainWindow", "FilterMenu")) self.actionFilterMenu.setText(_translate("MainWindow", "FilterMenu"))
self.action_rateButton2.setText(_translate("MainWindow", "_rateButton2")) self.action_rateButton2.setText(_translate("MainWindow", "_rateButton2"))

View File

@ -53,7 +53,7 @@
<widget class="QCheckBox" name="logistics_crates_checkBox"> <widget class="QCheckBox" name="logistics_crates_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>990</x> <x>980</x>
<y>211</y> <y>211</y>
<width>251</width> <width>251</width>
<height>28</height> <height>28</height>
@ -66,10 +66,10 @@
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Enable CTLD logistics crates for building ground units and air defenses. Pickup logistics containers to create new logistics sites.</string> <string>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.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Logistics</string> <string>Logistics Base</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -78,7 +78,7 @@
<widget class="QCheckBox" name="zone_sams_checkBox"> <widget class="QCheckBox" name="zone_sams_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>990</x> <x>980</x>
<y>320</y> <y>320</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
@ -91,10 +91,10 @@
</font> </font>
</property> </property>
<property name="statusTip"> <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.</string> <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>
</property> </property>
<property name="text"> <property name="text">
<string>Inactive Zone SAMs</string> <string>Protect Inactive Zones</string>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="red_forces_label"> <widget class="QLabel" name="red_forces_label">
@ -191,17 +191,20 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>470</x> <x>470</x>
<y>120</y> <y>130</y>
<width>156</width> <width>156</width>
<height>28</height> <height>28</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
<font> <font>
<pointsize>10</pointsize> <pointsize>11</pointsize>
<bold>false</bold> <bold>false</bold>
</font> </font>
</property> </property>
<property name="statusTip">
<string>Turn the tables and defend your zones against the enemy's attack.</string>
</property>
<property name="text"> <property name="text">
<string>Blue on Defense</string> <string>Blue on Defense</string>
</property> </property>
@ -289,8 +292,8 @@ p, li { white-space: pre-wrap; }
<widget class="QComboBox" name="slot_template_comboBox"> <widget class="QComboBox" name="slot_template_comboBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>980</x>
<y>384</y> <y>474</y>
<width>271</width> <width>271</width>
<height>33</height> <height>33</height>
</rect> </rect>
@ -564,7 +567,7 @@ p, li { white-space: pre-wrap; }
<number>8</number> <number>8</number>
</property> </property>
<property name="value"> <property name="value">
<number>2</number> <number>1</number>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="scenario_label_7"> <widget class="QLabel" name="scenario_label_7">
@ -592,8 +595,8 @@ p, li { white-space: pre-wrap; }
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>840</x> <x>860</x>
<y>390</y> <y>480</y>
<width>111</width> <width>111</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -611,8 +614,8 @@ p, li { white-space: pre-wrap; }
<widget class="QLabel" name="scenario_label_9"> <widget class="QLabel" name="scenario_label_9">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>490</x> <x>480</x>
<y>450</y> <y>401</y>
<width>251</width> <width>251</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -629,7 +632,7 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="awacs_checkBox"> <widget class="QCheckBox" name="awacs_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>990</x> <x>980</x>
<y>246</y> <y>246</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
@ -654,7 +657,7 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="tankers_checkBox"> <widget class="QCheckBox" name="tankers_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>990</x> <x>980</x>
<y>282</y> <y>282</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
@ -679,10 +682,10 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="voiceovers_checkBox"> <widget class="QCheckBox" name="voiceovers_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>500</x>
<y>517</y> <y>594</y>
<width>171</width> <width>171</width>
<height>24</height> <height>31</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -703,10 +706,10 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="smoke_pickup_zone_checkBox"> <widget class="QCheckBox" name="smoke_pickup_zone_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>500</x>
<y>460</y> <y>541</y>
<width>271</width> <width>231</width>
<height>24</height> <height>20</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -715,7 +718,7 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Infinite troop pickup zones will be marked with blue smoke.</string> <string>Troop pickup zones and FARPs will be marked with blue smoke.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Smoke at Troop Pickup Zones</string> <string>Smoke at Troop Pickup Zones</string>
@ -727,10 +730,10 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="game_status_checkBox"> <widget class="QCheckBox" name="game_status_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>500</x>
<y>490</y> <y>570</y>
<width>271</width> <width>221</width>
<height>24</height> <height>21</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -755,7 +758,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>380</y> <y>340</y>
<width>261</width> <width>261</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -767,18 +770,18 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <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>
<property name="text"> <property name="text">
<string>Infantry Spawns per zone</string> <string>Infantry Spawns</string>
</property> </property>
</widget> </widget>
<widget class="QSpinBox" name="inf_spawn_spinBox"> <widget class="QSpinBox" name="inf_spawn_spinBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>380</y> <y>340</y>
<width>47</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
</property> </property>
@ -800,15 +803,15 @@ p, li { white-space: pre-wrap; }
<number>20</number> <number>20</number>
</property> </property>
<property name="value"> <property name="value">
<number>2</number> <number>0</number>
</property> </property>
</widget> </widget>
<widget class="QSpinBox" name="troop_drop_spinBox"> <widget class="QSpinBox" name="troop_drop_spinBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>330</y> <y>300</y>
<width>47</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
</property> </property>
@ -833,12 +836,12 @@ p, li { white-space: pre-wrap; }
<number>4</number> <number>4</number>
</property> </property>
</widget> </widget>
<widget class="QCheckBox" name="force_offroad_checkBox"> <widget class="QCheckBox" name="random_weather_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>980</x>
<y>548</y> <y>420</y>
<width>161</width> <width>211</width>
<height>24</height> <height>24</height>
</rect> </rect>
</property> </property>
@ -848,10 +851,10 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>May help prevent long travel times or pathfinding issues. </string> <string>Random weather preset will be applied.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Force Offroad</string> <string>Random Weather</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>false</bool>
@ -864,7 +867,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>330</y> <y>300</y>
<width>281</width> <width>281</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -885,7 +888,7 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="apcs_spawn_checkBox"> <widget class="QCheckBox" name="apcs_spawn_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>990</x> <x>980</x>
<y>180</y> <y>180</y>
<width>251</width> <width>251</width>
<height>27</height> <height>27</height>
@ -898,10 +901,10 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Friendly/enemy APCs will drop infantry when reaching a new conflict zone. Disables infinite troop pickups from conflict zones (you must pick up existing troops).</string> <string>Friendly/enemy APCs will drop infantry when reaching a new conflict zone. </string>
</property> </property>
<property name="text"> <property name="text">
<string>Dynamic Troops</string> <string>APCs Spawn Infantry</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -913,7 +916,7 @@ p, li { white-space: pre-wrap; }
</property> </property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>710</x> <x>750</x>
<y>600</y> <y>600</y>
<width>231</width> <width>231</width>
<height>51</height> <height>51</height>
@ -938,8 +941,8 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_always"> <widget class="QRadioButton" name="farp_always">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>500</x>
<y>480</y> <y>431</y>
<width>261</width> <width>261</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -962,8 +965,8 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_never"> <widget class="QRadioButton" name="farp_never">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>500</x>
<y>540</y> <y>491</y>
<width>271</width> <width>271</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -986,8 +989,8 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_gunits"> <widget class="QRadioButton" name="farp_gunits">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>500</x>
<y>509</y> <y>460</y>
<width>261</width> <width>261</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -1131,8 +1134,8 @@ p, li { white-space: pre-wrap; }
<widget class="QCheckBox" name="hotstart_checkBox"> <widget class="QCheckBox" name="hotstart_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>960</x> <x>980</x>
<y>430</y> <y>520</y>
<width>271</width> <width>271</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -1143,7 +1146,7 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Player helicopters start with engines running on the ground. No effect if player slots says 'Locked to scenario'</string> <string>Player helicopters start with engines running on the ground. No effect for FARP spawns or if player slots says 'Locked to scenario'</string>
</property> </property>
<property name="text"> <property name="text">
<string>Player Hotstart</string> <string>Player Hotstart</string>
@ -1263,6 +1266,52 @@ p, li { white-space: pre-wrap; }
<string/> <string/>
</property> </property>
</widget> </widget>
<widget class="QComboBox" name="time_comboBox">
<property name="geometry">
<rect>
<x>980</x>
<y>370</y>
<width>161</width>
<height>33</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="statusTip">
<string>Mission start time of day. 'Default' is the start time as defined by the mission template designer.</string>
</property>
</widget>
<widget class="QCheckBox" name="farp_spawn_checkBox">
<property name="geometry">
<rect>
<x>980</x>
<y>550</y>
<width>271</width>
<height>24</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>Add helicopter slots where zone FARPs will be built. Helicopters will be empty fuel, requiring the FARP to be established to refuel and rearm.</string>
</property>
<property name="text">
<string>Spawns at zone FARPs</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</widget> </widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">
<property name="geometry"> <property name="geometry">
@ -1471,7 +1520,7 @@ p, li { white-space: pre-wrap; }
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Single-Player</string> <string>Single-Player Only</string>
</property> </property>
</action> </action>
<action name="actionCo_Op"> <action name="actionCo_Op">
@ -1482,7 +1531,7 @@ p, li { white-space: pre-wrap; }
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Co-Op</string> <string>Co-Op Only</string>
</property> </property>
</action> </action>
<action name="actionMapMenu"> <action name="actionMapMenu">

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,6 @@ def triggerSetup(rops, options):
# Add the first trigger # Add the first trigger
trig = dcs.triggers.TriggerOnce(comment="RotorOps Setup Scripts") trig = dcs.triggers.TriggerOnce(comment="RotorOps Setup Scripts")
trig.rules.append(dcs.condition.TimeAfter(1)) trig.rules.append(dcs.condition.TimeAfter(1))
#trig.actions.append(dcs.action.DoScriptFile(rops.scripts["mist_4_4_90.lua"]))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["mist_4_5_107_grimm.lua"])) trig.actions.append(dcs.action.DoScriptFile(rops.scripts["mist_4_5_107_grimm.lua"]))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["Splash_Damage_2_0.lua"])) trig.actions.append(dcs.action.DoScriptFile(rops.scripts["Splash_Damage_2_0.lua"]))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["CTLD.lua"])) trig.actions.append(dcs.action.DoScriptFile(rops.scripts["CTLD.lua"]))
@ -29,11 +28,13 @@ def triggerSetup(rops, options):
"RotorOps.voice_overs = " + lb("voiceovers") + "\n\n" + "RotorOps.voice_overs = " + lb("voiceovers") + "\n\n" +
"RotorOps.zone_status_display = " + lb("game_display") + "\n\n" + "RotorOps.zone_status_display = " + lb("game_display") + "\n\n" +
"RotorOps.inf_spawn_messages = true\n\n" + "RotorOps.inf_spawn_messages = true\n\n" +
"RotorOps.inf_spawns_per_zone = " + lb("inf_spawn_qty") + "\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")
if not options["smoke_pickup_zones"]: if not options["smoke_pickup_zones"]:
script = script + 'RotorOps.pickup_zone_smoke = "none"\n\n' script = script + 'RotorOps.pickup_zone_smoke = "none"\n\n'
trig.actions.append(dcs.action.DoScript(dcs.action.String((script)))) trig.actions.append(dcs.action.DoScript(dcs.action.String((script))))
if options["script"]:
trig.actions.append(dcs.action.DoScript(dcs.action.String((options["script"]))))
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)
# Add the second trigger # Add the second trigger
@ -50,7 +51,8 @@ def triggerSetup(rops, options):
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)
# Add the third trigger # Add the start trigger
if options["start_trigger"] is not False:
trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict Start") trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict Start")
trig.rules.append(dcs.condition.TimeAfter(10)) trig.rules.append(dcs.condition.TimeAfter(10))
trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)"))) trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)")))
@ -81,6 +83,19 @@ def triggerSetup(rops, options):
dcs.action.String("Group.destroy(Group.getByName('Static " + zone_name + " Protection SAM'))"))) dcs.action.String("Group.destroy(Group.getByName('Static " + zone_name + " Protection SAM'))")))
rops.m.triggerrules.triggers.append(z_sams_trig) 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.
# if options["defending"]:
# for index, zone_name in enumerate(rops.conflict_zones):
# z_farps_trig = dcs.triggers.TriggerOnce(comment="Deactivate " + zone_name + " FARP")
# z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
# z_farps_trig.actions.append(dcs.action.DeactivateGroup(rops.m.country(jtf_blue).find_group(zone_name + " FARP Static").id))
# for group in rops.all_zones[zone_name].player_helo_spawns:
# z_farps_trig.actions.append(
# dcs.action.DeactivateGroup(
# group.id))
# rops.m.triggerrules.triggers.append(z_farps_trig)
# Zone FARPS always # Zone FARPS always
if options["zone_farps"] == "farp_always" and not options["defending"]: if options["zone_farps"] == "farp_always" and not options["defending"]:
for index, zone_name in enumerate(rops.conflict_zones): for index, zone_name in enumerate(rops.conflict_zones):
@ -92,9 +107,13 @@ def triggerSetup(rops, options):
z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1)) z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
z_farps_trig.actions.append( z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id)) dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# z_farps_trig.actions.append(dcs.action.SoundToAll(str(rops.res_map['forward_base_established.ogg']))) # 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))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String( z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ")"))) "RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig) rops.m.triggerrules.triggers.append(z_farps_trig)
# Zone FARPS conditional on staged units remaining # Zone FARPS conditional on staged units remaining
@ -111,9 +130,13 @@ def triggerSetup(rops, options):
"--The 100 flag indicates which zone is active. The 111 flag value is the percentage of staged units remaining"))) "--The 100 flag indicates which zone is active. The 111 flag value is the percentage of staged units remaining")))
z_farps_trig.actions.append( z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id)) dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# z_farps_trig.actions.append(dcs.action.SoundToAll(str(rops.res_map['forward_base_established.ogg']))) # 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))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String( z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ")"))) "RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig) rops.m.triggerrules.triggers.append(z_farps_trig)
# Add attack helos triggers # Add attack helos triggers
@ -156,17 +179,23 @@ def triggerSetup(rops, options):
rops.m.triggerrules.triggers.append(z_weak_trig) rops.m.triggerrules.triggers.append(z_weak_trig)
# Add game won/lost triggers # Add game won/lost triggers
# Add game won triggers
trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON") trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON")
trig.rules.append(dcs.condition.FlagEquals(game_flag, 99)) trig.rules.append(dcs.condition.FlagEquals(game_flag, 99))
trig.actions.append( trig.actions.append(
dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is WON"))) 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( trig.actions.append(
dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)"))) dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)")))
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)
# Add game lost triggers
trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST") trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST")
trig.rules.append(dcs.condition.FlagEquals(game_flag, 98)) trig.rules.append(dcs.condition.FlagEquals(game_flag, 98))
trig.actions.append( trig.actions.append(
dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST"))) dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST")))
trig.actions.append( if options["end_trigger"] is not False:
dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)"))) trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)")))
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)

View File

@ -1,12 +1,15 @@
import math import math
import dcs import dcs
from MissionGenerator import logger from MissionGenerator import logger
import os
class ImportObjects: class ImportObjects:
def __init__(self, mizfile): def __init__(self, mizfile):
self.pad_unit = True # todo: use this to hold a unit for helicopter placement on ships ie flight_group_from_unit self.pad_unit = True # todo: use this to hold a unit for helicopter placement on ships ie flight_group_from_unit
if not mizfile or not os.path.exists(mizfile):
raise Exception("Cannot find required file: " + str(mizfile))
logger.info("Importing objects from " + mizfile) logger.info("Importing objects from " + mizfile)
self.source_mission = dcs.mission.Mission() self.source_mission = dcs.mission.Mission()
self.source_mission.load_file(mizfile) self.source_mission.load_file(mizfile)
@ -32,7 +35,6 @@ class ImportObjects:
self.copyVehicles(mission, dest_country_name, dest_name, dest_point, dest_heading), \ self.copyVehicles(mission, dest_country_name, dest_name, dest_point, dest_heading), \
self.copyHelicopters(mission, dest_country_name, dest_name, dest_point, dest_heading) self.copyHelicopters(mission, dest_country_name, dest_name, dest_point, dest_heading)
def anchorByGroupName(self, group_name): def anchorByGroupName(self, group_name):
group = self.source_mission.find_group(group_name) group = self.source_mission.find_group(group_name)
if group: if group:
@ -49,7 +51,10 @@ class ImportObjects:
coalition = self.source_mission.coalition.get(side) coalition = self.source_mission.coalition.get(side)
for country_name in coalition.countries: for country_name in coalition.countries:
group_types = [coalition.countries[country_name].static_group, coalition.countries[country_name].vehicle_group, coalition.countries[country_name].helicopter_group, coalition.countries[country_name].plane_group, group_types = [coalition.countries[country_name].static_group,
coalition.countries[country_name].vehicle_group,
coalition.countries[country_name].helicopter_group,
coalition.countries[country_name].plane_group,
coalition.countries[country_name].ship_group] coalition.countries[country_name].ship_group]
for index, group_type in enumerate(group_types): for index, group_type in enumerate(group_types):
@ -66,7 +71,6 @@ class ImportObjects:
elif index == 4: elif index == 4:
logger.warn(group.name + ": Ships not available for import") logger.warn(group.name + ": Ships not available for import")
def copyStatics(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0): def copyStatics(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0):
logger.info("Copying " + str(len(self.statics)) + " static objects as " + dest_name) logger.info("Copying " + str(len(self.statics)) + " static objects as " + dest_name)
new_groups = [] new_groups = []
@ -77,10 +81,8 @@ class ImportObjects:
# Statics # Statics
statics_copy = self.statics.copy() statics_copy = self.statics.copy()
for group in statics_copy: for group in statics_copy:
self.groupToPoint(group, self.source_point, dest_point, self.source_heading, dest_heading) self.groupToPoint(group, self.source_point, dest_point, self.source_heading, dest_heading)
class temp(dcs.unittype.StaticType): class temp(dcs.unittype.StaticType):
id = group.units[0].type id = group.units[0].type
name = group.units[0].name name = group.units[0].name
@ -89,7 +91,6 @@ class ImportObjects:
can_cargo = group.units[0].can_cargo can_cargo = group.units[0].can_cargo
mass = group.units[0].mass mass = group.units[0].mass
ng = mission.static_group(mission.country(dest_country_name), ng = mission.static_group(mission.country(dest_country_name),
dest_name + " " + group.name, dest_name + " " + group.name,
temp, temp,
@ -104,9 +105,6 @@ class ImportObjects:
return new_groups return new_groups
def copyVehicles(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0): def copyVehicles(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0):
logger.info("Copying " + str(len(self.vehicles)) + " vehicle groups as " + dest_name) logger.info("Copying " + str(len(self.vehicles)) + " vehicle groups as " + dest_name)
new_groups = [] new_groups = []
@ -127,25 +125,25 @@ class ImportObjects:
group.units[0].position, group.units[0].position,
group.units[0].heading) group.units[0].heading)
new_groups.append(ng) # will this hold units we add later? # ng.units[0].livery_id = group.units[0].livery_id
new_groups.append(ng)
else: else:
u = mission.vehicle(dest_name + " " + group.units[i].name, dcs.vehicles.vehicle_map[group.units[i].type]) u = mission.vehicle(dest_name + " " + group.units[i].name,
dcs.vehicles.vehicle_map[group.units[i].type])
u.position = group.units[i].position u.position = group.units[i].position
u.heading = group.units[i].heading u.heading = group.units[i].heading
# u.livery_id = group.units[i].livery_id
ng.add_unit(u) ng.add_unit(u)
return new_groups return new_groups
def copyHelicopters(self, mission, dest_country_name, dest_name, dest_point, dest_heading=0,
def copyHelicopters(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0): start_type=dcs.mission.StartType.Cold):
logger.info("Copying " + str(len(self.helicopters)) + " helicopters as " + dest_name) logger.info("Copying " + str(len(self.helicopters)) + " helicopters as " + dest_name)
new_groups = [] new_groups = []
if not dest_point:
dest_point = dcs.Point(mission.terrain.bullseye_blue["x"], mission.terrain.bullseye_blue["y"])
helicopters_copy = self.helicopters.copy() helicopters_copy = self.helicopters.copy()
for group in helicopters_copy: for group in helicopters_copy:
@ -158,39 +156,46 @@ class ImportObjects:
# trying to move the units into position after adding the flight group moves the 2D graphic of the helicopter, but the unit marker remains stacked on top # trying to move the units into position after adding the flight group moves the 2D graphic of the helicopter, but the unit marker remains stacked on top
# of the unit marker in ME # of the unit marker in ME
# farp = mission.country(country_name).find_group(self.pad_unit.name) # farp = mission.country(country_name).find_group(self.pad_unit.name)
#
farp = mission.farp(mission.country(dest_country_name), dest_name + " " + group.name + " Pad", group.units[0].position, hidden=True, dead=False, # farp = mission.farp(mission.country(dest_country_name), dest_name + " " + group.name + " Pad", group.units[0].position, hidden=True, dead=False,
farp_type=dcs.unit.InvisibleFARP) # farp_type=dcs.unit.InvisibleFARP)
#
#
# ng = mission.flight_group_from_unit(mission.country(dest_country_name),
ng = mission.flight_group_from_unit(mission.country(dest_country_name), # dest_name + " " + group.name,
# dcs.helicopters.helicopter_map[group.units[0].type],
# farp,
# group_size=1, start_type=start_type)
ng = mission.flight_group(mission.country(dest_country_name),
dest_name + " " + group.name, dest_name + " " + group.name,
dcs.helicopters.helicopter_map[group.units[0].type], dcs.helicopters.helicopter_map[group.units[0].type],
farp, airport=None,
group_size=1) position=group.units[0].position,
group_size=1, start_type=start_type)
if start_type == dcs.mission.StartType.Warm:
ng.points[0].action = dcs.point.PointAction.FromGroundAreaHot
ng.points[0].type = "TakeOffGroundHot"
else:
ng.points[0].action = dcs.point.PointAction.FromGroundArea ng.points[0].action = dcs.point.PointAction.FromGroundArea
ng.points[0].type = "TakeOffGround" ng.points[0].type = "TakeOffGround"
ng.units[0].heading = group.units[0].heading ng.units[0].heading = group.units[0].heading
ng.units[0].skill = group.units[0].skill ng.units[0].skill = group.units[0].skill
ng.units[0].livery_id = group.units[0].livery_id ng.units[0].livery_id = group.units[0].livery_id
ng.units[0].pylons = group.units[0].pylons ng.units[0].pylons = group.units[0].pylons
ng.units[0].fuel = group.units[0].fuel
ng.units[0].gun = group.units[0].gun
ng.units[0].hardpoint_racks = group.units[0].hardpoint_racks
new_groups.append(ng) new_groups.append(ng)
else: else:
logger.warn("No pad unit (ie FARP, carrier) found, so can't add helicopters.") logger.warn("No pad unit (ie FARP, carrier) found, so can't add helicopters.")
return new_groups return new_groups
def copyVehiclesAsGroup(self, mission, dest_country_name, dest_name, dest_point, dest_heading=0):
def copyVehiclesAsGroup(self, mission, dest_country_name, dest_name, dest_point=None, dest_heading=0):
logger.info("Copying " + str(len(self.vehicles)) + " vehicle groups as single group name: " + dest_name) logger.info("Copying " + str(len(self.vehicles)) + " vehicle groups as single group name: " + dest_name)
new_group = None new_group = None
if not dest_point:
dest_point = dcs.Point(mission.terrain.bullseye_blue["x"], mission.terrain.bullseye_blue["y"])
unit_count = 0 unit_count = 0
vehicles_copy = self.vehicles.copy() vehicles_copy = self.vehicles.copy()
for group in vehicles_copy: for group in vehicles_copy:
@ -198,29 +203,32 @@ class ImportObjects:
for i, unit in enumerate(group.units): for i, unit in enumerate(group.units):
if unit_count == 0: if unit_count == 0:
print("Group:" + group.name) # print("Group:" + group.name)
new_group = mission.vehicle_group(mission.country(dest_country_name), new_group = mission.vehicle_group(mission.country(dest_country_name),
dest_name, dest_name,
dcs.vehicles.vehicle_map[group.units[0].type], dcs.vehicles.vehicle_map[group.units[0].type],
group.units[0].position, group.units[0].position,
group.units[0].heading) group.units[0].heading)
unit_count = unit_count + 1 unit_count = unit_count + 1
# new_group.units[0].livery_id = group.units[0].livery_id
else: else:
print("Unit:" + group.units[i].name) # print("Unit:" + group.units[i].name)
u = mission.vehicle(dest_name + " " + group.units[i].name, dcs.vehicles.vehicle_map[group.units[i].type]) u = mission.vehicle(dest_name + " " + group.units[i].name,
dcs.vehicles.vehicle_map[group.units[i].type])
u.position = group.units[i].position u.position = group.units[i].position
u.heading = group.units[i].heading u.heading = group.units[i].heading
# u.livery_id = group.units[i].livery_id
new_group.add_unit(u) new_group.add_unit(u)
unit_count = unit_count + 1 unit_count = unit_count + 1
print("Made a group with units: " + str(unit_count)) print("Made a group with units: " + str(unit_count))
print("group actually has units: " + str(len(new_group.units))) # print("group actually has units: " + str(len(new_group.units)))
return new_group return new_group
@staticmethod @staticmethod
def groupToPoint(group, src_point, dest_point, src_heading=0, dest_heading=0): def groupToPoint(group, src_point, dest_point, src_heading=0, dest_heading=0):
for unit in group.units: for unit in group.units:

View File

@ -1,10 +1,10 @@
from tokenize import String from tokenize import String
import dcs import dcs
import dcs.cloud_presets
import os import os
import random import random
import RotorOpsGroups import RotorOpsGroups
import RotorOpsUnits import RotorOpsUnits
import RotorOpsUtils import RotorOpsUtils
@ -18,6 +18,7 @@ from MissionGenerator import directories
jtf_red = "Combined Joint Task Forces Red" jtf_red = "Combined Joint Task Forces Red"
jtf_blue = "Combined Joint Task Forces Blue" jtf_blue = "Combined Joint Task Forces Blue"
class RotorOpsMission: class RotorOpsMission:
def __init__(self): def __init__(self):
@ -26,10 +27,11 @@ class RotorOpsMission:
self.conflict_zones = {} self.conflict_zones = {}
self.staging_zones = {} self.staging_zones = {}
self.spawn_zones = {} self.spawn_zones = {}
self.all_zones = {}
self.scripts = {} self.scripts = {}
self.res_map = {} self.res_map = {}
self.config = None self.config = None # not used
self.imports = None
class RotorOpsZone: class RotorOpsZone:
def __init__(self, name: str, flag: int, position: dcs.point, size: int): def __init__(self, name: str, flag: int, position: dcs.point, size: int):
@ -38,6 +40,8 @@ class RotorOpsMission:
self.position = position self.position = position
self.size = size self.size = size
self.player_helo_spawns = []
self.base_position = position
def getMission(self): def getMission(self):
return self.m return self.m
@ -62,7 +66,6 @@ class RotorOpsMission:
key = self.m.map_resource.add_resource_file(filename) key = self.m.map_resource.add_resource_file(filename)
self.res_map[filename] = key self.res_map[filename] = key
# add all of our lua scripts # add all of our lua scripts
os.chdir(script_directory) os.chdir(script_directory)
path = os.getcwd() path = os.getcwd()
@ -128,31 +131,54 @@ class RotorOpsMission:
logger.info("Looking for mission files in " + os.getcwd()) logger.info("Looking for mission files in " + os.getcwd())
window.statusBar().showMessage("Loading scenario mission", 10000) window.statusBar().showMessage("Loading scenario mission", 10000)
self.m.load_file(options["scenario_file"]) self.m.load_file(options["scenario_file"])
self.addMods() # Add countries if they're missing
self.importObjects() if not self.m.country(jtf_red):
self.m.coalition.get("red").add_country(dcs.countries.CombinedJointTaskForcesRed())
#todo: test if not self.m.country(jtf_blue):
self.m.coalition.get("blue").add_country(dcs.countries.CombinedJointTaskForcesBlue())
if not self.m.country(
dcs.countries.UnitedNationsPeacekeepers.name):
self.m.coalition.get("neutrals").add_country(dcs.countries.UnitedNationsPeacekeepers()) self.m.coalition.get("neutrals").add_country(dcs.countries.UnitedNationsPeacekeepers())
if not self.m.country("Russia"):
if not self.m.country(jtf_red) or not self.m.country(jtf_blue) or not self.m.country(dcs.countries.UnitedNationsPeacekeepers.name):
failure_msg = "You must include a CombinedJointTaskForcesBlue and CombinedJointTaskForcesRed unit in the scenario template. See the instructions in " + directories.scenarios
return {"success": False, "failure_msg": failure_msg}
# red_forces = self.getUnitsFromMiz(directories.forces + "/red/" + options["red_forces_filename"], "red")
# blue_forces = self.getUnitsFromMiz(directories.forces + "/blue/" + options["blue_forces_filename"], "blue")
red_forces = self.getUnitsFromMiz(directories.forces + "/" + options["red_forces_filename"], "both")
blue_forces = self.getUnitsFromMiz(directories.forces + "/" + options["blue_forces_filename"], "both")
# Add coalitions (we may be able to add CJTF here instead of requiring templates to have objects of those coalitions)
self.m.coalition.get("red").add_country(dcs.countries.Russia()) self.m.coalition.get("red").add_country(dcs.countries.Russia())
if not self.m.country("USA"):
self.m.coalition.get("blue").add_country(dcs.countries.USA()) self.m.coalition.get("blue").add_country(dcs.countries.USA())
# blue = self.m.coalition.get("blue")
# blue.add_country(dcs.countries.CombinedJointTaskForcesBlue())
self.addMods()
self.importObjects(options)
red_forces = self.getUnitsFromMiz(options["red_forces_path"], "both")
blue_forces = self.getUnitsFromMiz(options["blue_forces_path"], "both")
# add images to briefing
self.m.add_picture_blue(directories.assets + '/briefing1.png') self.m.add_picture_blue(directories.assets + '/briefing1.png')
self.m.add_picture_blue(directories.assets + '/briefing2.png') self.m.add_picture_blue(directories.assets + '/briefing2.png')
# get import objects for generic farps etc
self.imports = options["objects"]["imports"]
activated_farp = None
defensive_farp = None
logistics_farp = None
logistics_base = None
zone_protect = None
for i in self.imports:
if i.filename == ("FARP_ACTIVATED_ZONE.miz"):
activated_farp = i.path
if i.filename == ("FARP_DEFENSIVE_ZONE.miz"):
defensive_farp = i.path
if i.filename == ("FARP_LOGISTICS_ZONE.miz"):
logistics_farp = i.path
if i.filename == ("STAGING_LOGISTICS_BASE.miz"):
logistics_base = i.path
if i.filename == ("ZONE_ACTIVATED_DEFENSE.miz"):
zone_protect = i.path
# it's possible to have import templates with the same filename, but we will let the latest override others
# todo: verify we have the required templates
# add zones to target mission # add zones to target mission
zone_names = ["ALPHA", "BRAVO", "CHARLIE", "DELTA"] zone_names = ["ALPHA", "BRAVO", "CHARLIE", "DELTA"]
@ -160,17 +186,20 @@ class RotorOpsMission:
for zone_name in zone_names: for zone_name in zone_names:
for zone in self.m.triggers.zones(): for zone in self.m.triggers.zones():
if zone.name == zone_name: if zone.name == zone_name:
self.addZone(self.conflict_zones, self.RotorOpsZone(zone_name, zone_flag, zone.position, zone.radius)) self.addZone(self.conflict_zones,
self.RotorOpsZone(zone_name, zone_flag, zone.position, zone.radius))
zone_flag = zone_flag + 1 zone_flag = zone_flag + 1
for zone in self.m.triggers.zones(): for zone in self.m.triggers.zones():
if zone.name.rfind("STAGING") >= 0: self.addZone(self.all_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
if zone_name == "STAGING":
self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
continue
if zone.name.rfind("STAGING") >= 0: # find additional staging zones
self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius)) self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
elif zone.name.rfind("SPAWN") >= 0: elif zone.name.rfind("SPAWN") >= 0:
self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius)) self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
blue_zones = self.staging_zones blue_zones = self.staging_zones
red_zones = self.conflict_zones red_zones = self.conflict_zones
if options["defending"]: if options["defending"]:
@ -179,101 +208,238 @@ class RotorOpsMission:
# swap airport sides # swap airport sides
self.swapSides(options) self.swapSides(options)
# Populate Red zones with ground units # Populate Red zones with ground units
window.statusBar().showMessage("Populating units into mission...", 10000) window.statusBar().showMessage("Populating units into mission...", 10000)
start_type = dcs.mission.StartType.Cold
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 red_zones:
if red_forces["vehicles"]: if red_forces["vehicles"]:
self.addGroundGroups(red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"], options["red_quantity"]) self.addGroundGroups(red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"],
options["red_quantity"])
#Add red FARPS
if options["zone_farps"] != "farp_never" and not options["defending"]: if options["zone_farps"] != "farp_never" and not options["defending"]:
# RotorOpsGroups.VehicleTemplate.CombinedJointTaskForcesBlue.zone_farp(self.m, self.m.country(jtf_blue), helicopters = False
# self.m.country(jtf_blue), if options["farp_spawns"]:
# red_zones[zone_name].position, helicopters = True
# 180, zone_name + " FARP", late_activation=True)
#new_statics, new_vehicles, new_helicopters = i.copyAll(self.m, dcs.countries.UnitedNationsPeacekeepers.name, zone_name, red_zones[zone_name].position) # Add red zone FARPS
farp_flag = self.m.find_group(zone_name) vehicle_group = self.addZoneBase(options, zone_name, jtf_blue,
file=activated_farp,
if farp_flag: config_name="zone_farp_file",
farp_position = farp_flag.units[0].position copy_helicopters=helicopters,
farp_heading = farp_flag.units[0].heading helicopters_name="ZONE " + zone_name + " EMPTY",
else: heli_start_type=dcs.mission.StartType.Cold,
farp_position = red_zones[zone_name].position copy_vehicles=True,
farp_heading = 0 vehicles_name=zone_name + " FARP Static",
copy_statics=False,
farp = self.m.farp(self.m.country(jtf_blue), zone_name + " FARP", farp_position, statics_names="",
hidden=False, dead=False, vehicles_single_group=True,
farp_type=dcs.unit.InvisibleFARP) trigger_name=zone_name + "_FARP",
trigger_radius=110
os.chdir(directories.imports)
if self.config and self.config["zone_farp_file"]:
filename = self.config["zone_farp_file"]
else:
filename = "FARP_DEFAULT_ZONE.miz"
i = ImportObjects(filename)
i.anchorByGroupName("ANCHOR")
farp_group = i.copyVehiclesAsGroup(self.m, jtf_blue, zone_name + " FARP Static", farp_position, farp_heading)
farp_group.late_activation = True
if options["zone_protect_sams"]:
self.m.vehicle_group(
self.m.country(jtf_red),
"Static " + zone_name + " Protection SAM",
random.choice(RotorOpsUnits.e_zone_sams),
red_zones[zone_name].position,
heading=random.randint(0, 359),
group_size=6,
formation=dcs.unitgroup.VehicleGroup.Formation.Star
) )
vehicle_group.late_activation = True
# For SAMs: Add vehicles as a single group (for easy late activation)
if options["zone_protect_sams"]:
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
)
# 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 # Populate Blue zones with ground units
for zone_name in blue_zones: for i, zone_name in enumerate(blue_zones):
if blue_forces["vehicles"]: if blue_forces["vehicles"]:
self.addGroundGroups(blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"], self.addGroundGroups(blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"],
options["blue_quantity"]) options["blue_quantity"])
#Add blue FARPS
# Add blue zone FARPS (not late activated) for defensive mode
if options["zone_farps"] != "farp_never" and options["defending"]: if options["zone_farps"] != "farp_never" and options["defending"]:
RotorOpsGroups.VehicleTemplate.CombinedJointTaskForcesBlue.zone_farp(self.m, self.m.country(jtf_blue),
self.m.country(jtf_blue),
blue_zones[zone_name].position,
180, zone_name + " FARP", late_activation=False)
#add logistics sites helicopters = False
if options["crates"] and zone_name == "STAGING": if options["farp_spawns"]:
os.chdir(directories.imports) helicopters = True
staging_flag = self.m.find_group(zone_name)
if staging_flag: if options["crates"] and i == len(blue_zones) - 1:
staging_position = staging_flag.units[0].position # add a logistics zone to the last conflict zone
staging_heading = staging_flag.units[0].heading # addLogisticsZone(zone_name, jtf_blue, logistics_farp, "logistics_farp_file", helicopters)
self.addZoneBase(options, zone_name, jtf_blue,
file=logistics_farp,
config_name="logistics_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=start_type,
copy_vehicles=True,
vehicles_name=zone_name + " Logistics FARP",
copy_statics=True,
statics_names=zone_name + " Logistics FARP",
vehicles_single_group=False,
trigger_name=zone_name + "_FARP",
trigger_radius=110
)
else: else:
staging_position = blue_zones[zone_name].position # addDefensiveFARP(zone_name, jtf_blue, defensive_farp)
staging_heading = 0 self.addZoneBase(options, zone_name, jtf_blue,
i = ImportObjects("STAGING_LOGISTIC_HUB.miz") file=defensive_farp,
i.anchorByGroupName("ANCHOR") config_name="defensive_farp_file",
i.copyAll(self.m, jtf_blue, "Staging Logistics Zone", copy_helicopters=helicopters,
staging_position, staging_heading) helicopters_name="ZONE " + zone_name + " EMPTY",
heli_start_type=dcs.mission.StartType.Cold,
if options["zone_protect_sams"] and options["defending"]: copy_vehicles=True,
vg = self.m.vehicle_group( vehicles_name=zone_name + " Defensive FARP",
self.m.country(jtf_blue), copy_statics=True,
"Static " + zone_name + " Protection SAM", statics_names=zone_name + " Defensive FARP",
random.choice(RotorOpsUnits.e_zone_sams), vehicles_single_group=False,
blue_zones[zone_name].position, trigger_name=zone_name + "_FARP",
heading=random.randint(0, 359), trigger_radius=110
group_size=6,
formation=dcs.unitgroup.VehicleGroup.Formation.Star
) )
# add main logistics base
if options["crates"] and zone_name == "STAGING":
# addLogisticsZone(zone_name, jtf_blue, logistics_base, "staging_logistics_file", helicopters=True)
self.addZoneBase(options, zone_name, jtf_blue,
file=logistics_base,
config_name="staging_logistics_file",
copy_helicopters=True,
helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=start_type,
copy_vehicles=True,
vehicles_name=zone_name + " Logistics Base",
copy_statics=True,
statics_names=zone_name + " Logistics Base",
vehicles_single_group=False,
trigger_name="STAGING_BASE",
trigger_radius=170
)
# Add player slots # Add player slots
window.statusBar().showMessage("Adding flights to mission...", 10000) window.statusBar().showMessage("Adding flights to mission...", 10000)
@ -293,6 +459,34 @@ class RotorOpsMission:
self.addResources(directories.sound, directories.scripts) self.addResources(directories.sound, directories.scripts)
RotorOpsConflict.triggerSetup(self, options) RotorOpsConflict.triggerSetup(self, options)
# finalize the mission briefing
briefing = self.m.description_text() + '## RotorOps Credits ##\n\n' + options["credits"]
briefing = briefing + "\nFor more info on RotorOps, visit: DCS-HELICOPTERS.COM"
self.m.set_description_text(briefing)
# set the weather and time
if options["random_weather"]:
# self.m.random_weather = True
max = len(dcs.cloud_presets.CLOUD_PRESETS) - 1
preset_name = list(dcs.cloud_presets.CLOUD_PRESETS)[random.randint(0, max)]
cloud_preset = dcs.weather.CloudPreset.by_name(preset_name)
self.m.weather.clouds_base = random.randrange(cloud_preset.min_base, cloud_preset.max_base)
self.m.weather.clouds_preset = cloud_preset
wind_dir = random.randrange(0, 359) + 180
wind_speed = random.randrange(5, 10)
self.m.weather.wind_at_ground.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_ground.speed = wind_speed + random.randrange(-4, -1)
self.m.weather.wind_at_2000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_2000.speed = wind_speed + random.randrange(-2, 2)
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)
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())
# Save the mission file # Save the mission file
window.statusBar().showMessage("Saving mission...", 10000) window.statusBar().showMessage("Saving mission...", 10000)
@ -305,6 +499,63 @@ class RotorOpsMission:
success = self.m.save(output_filename) success = self.m.save(output_filename)
return {"success": success, "filename": output_filename, "directory": output_dir} # let the UI know the result 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,
copy_vehicles=False, vehicles_name="", copy_statics=False, statics_names="",
vehicles_single_group=False, trigger_name=None, trigger_radius=110, farp=True):
# look for a marker object to position the base at a position other than zone center
flag = self.m.find_group(_zone_name)
if flag:
position = flag.units[0].position
heading = flag.units[0].heading
self.all_zones[_zone_name].base_position = position
else:
position = self.all_zones[_zone_name].position
heading = 0
if farp:
farp = self.m.farp(self.m.country(country), _zone_name + " FARP",
position, hidden=True, dead=False, farp_type=dcs.unit.InvisibleFARP)
# Add a trigger zone
if trigger_name:
self.m.triggers.add_triggerzone(position, trigger_radius, False, trigger_name)
# Use alternate template file if it has been defined in scenario config
if config_name and options[config_name]:
for i in self.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")
if copy_statics:
i.copyStatics(self.m, country, statics_names,
position, heading)
vehicle_group = None
if copy_vehicles:
if vehicles_single_group:
vehicle_group = i.copyVehiclesAsGroup(self.m, country, vehicles_name, position,
heading)
else:
i.copyVehicles(self.m, country, vehicles_name,
position, heading)
# Add client helicopters and farp objects
if copy_helicopters:
helicopter_groups = i.copyHelicopters(self.m, jtf_blue, helicopters_name, position,
heading, heli_start_type)
for group in helicopter_groups:
self.all_zones[_zone_name].player_helo_spawns.append(group)
return vehicle_group # for setting properties such as late activation
def addGroundGroups(self, zone, _country, groups, quantity): def addGroundGroups(self, zone, _country, groups, quantity):
for a in range(0, quantity): for a in range(0, quantity):
@ -318,12 +569,11 @@ class RotorOpsMission:
country, country,
zone.name + '-GND ' + str(a + 1), zone.name + '-GND ' + str(a + 1),
unit_types, unit_types,
zone.position.random_point_within(zone.size / 1.2, 100), zone.position.random_point_within(zone.size / 1.3, 100),
heading=random.randint(0, 359), heading=random.randint(0, 359),
formation=dcs.unitgroup.VehicleGroup.Formation.Scattered, formation=dcs.unitgroup.VehicleGroup.Formation.Scattered,
) )
def getCoalitionAirports(self, side: str): def getCoalitionAirports(self, side: str):
coalition_airports = [] coalition_airports = []
primary_airport = None primary_airport = None
@ -335,7 +585,8 @@ class RotorOpsMission:
coalition_airports.append(airportobj) coalition_airports.append(airportobj)
start = self.staging_zones[list(self.staging_zones)[0]] start = self.staging_zones[list(self.staging_zones)[0]]
dist_from_start = dcs.mapping._distance(airportobj.position.x, airportobj.position.y, start.position.x, start.position.y) dist_from_start = dcs.mapping._distance(airportobj.position.x, airportobj.position.y, start.position.x,
start.position.y)
if dist_from_start < shortest_dist: if dist_from_start < shortest_dist:
primary_airport = airportobj primary_airport = airportobj
@ -346,10 +597,10 @@ class RotorOpsMission:
def getParking(self, airport, aircraft, alt_airports=None, group_size=1): def getParking(self, airport, aircraft, alt_airports=None, group_size=1):
if len(airport.free_parking_slots(aircraft)) >= group_size: 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)): if not (aircraft.id in dcs.planes.plane_map and (
len(airport.runways) == 0 or airport.runways[0].ils is None)):
return airport return airport
if alt_airports: if alt_airports:
for airport in alt_airports: for airport in alt_airports:
if len(airport.free_parking_slots(aircraft)) >= group_size: if len(airport.free_parking_slots(aircraft)) >= group_size:
@ -363,7 +614,6 @@ class RotorOpsMission:
def getUnitParking(self, aircraft): def getUnitParking(self, aircraft):
return return
def swapSides(self, options): def swapSides(self, options):
# Swap airports # Swap airports
@ -379,7 +629,6 @@ class RotorOpsMission:
combinedJointTaskForcesBlue = self.m.country(jtf_blue) combinedJointTaskForcesBlue = self.m.country(jtf_blue)
combinedJointTaskForcesRed = self.m.country(jtf_red) combinedJointTaskForcesRed = self.m.country(jtf_red)
# Swap ships # Swap ships
blue_ships = combinedJointTaskForcesBlue.ship_group.copy() blue_ships = combinedJointTaskForcesBlue.ship_group.copy()
@ -390,13 +639,10 @@ class RotorOpsMission:
combinedJointTaskForcesRed.add_ship_group(group) combinedJointTaskForcesRed.add_ship_group(group)
combinedJointTaskForcesBlue.ship_group.remove(group) combinedJointTaskForcesBlue.ship_group.remove(group)
for group in red_ships: for group in red_ships:
combinedJointTaskForcesBlue.add_ship_group(group) combinedJointTaskForcesBlue.add_ship_group(group)
combinedJointTaskForcesRed.ship_group.remove(group) combinedJointTaskForcesRed.ship_group.remove(group)
# Swap statics # Swap statics
blue_statics = combinedJointTaskForcesBlue.static_group.copy() blue_statics = combinedJointTaskForcesBlue.static_group.copy()
@ -410,7 +656,6 @@ class RotorOpsMission:
combinedJointTaskForcesRed.static_group.remove(group) combinedJointTaskForcesRed.static_group.remove(group)
combinedJointTaskForcesBlue.add_static_group(group) combinedJointTaskForcesBlue.add_static_group(group)
# Swap vehicles # Swap vehicles
blue_vehicles = combinedJointTaskForcesBlue.vehicle_group.copy() blue_vehicles = combinedJointTaskForcesBlue.vehicle_group.copy()
@ -424,7 +669,6 @@ class RotorOpsMission:
combinedJointTaskForcesRed.vehicle_group.remove(group) combinedJointTaskForcesRed.vehicle_group.remove(group)
combinedJointTaskForcesBlue.add_vehicle_group(group) combinedJointTaskForcesBlue.add_vehicle_group(group)
# Swap planes # Swap planes
blue_planes = combinedJointTaskForcesBlue.plane_group.copy() blue_planes = combinedJointTaskForcesBlue.plane_group.copy()
@ -438,7 +682,6 @@ class RotorOpsMission:
combinedJointTaskForcesRed.plane_group.remove(group) combinedJointTaskForcesRed.plane_group.remove(group)
combinedJointTaskForcesBlue.add_plane_group(group) combinedJointTaskForcesBlue.add_plane_group(group)
# Swap helicopters # Swap helicopters
blue_helos = combinedJointTaskForcesBlue.helicopter_group.copy() blue_helos = combinedJointTaskForcesBlue.helicopter_group.copy()
@ -452,12 +695,15 @@ class RotorOpsMission:
combinedJointTaskForcesRed.helicopter_group.remove(group) combinedJointTaskForcesRed.helicopter_group.remove(group)
combinedJointTaskForcesBlue.add_helicopter_group(group) combinedJointTaskForcesBlue.add_helicopter_group(group)
def addPlayerHelos(self, options): def addPlayerHelos(self, options):
client_helos = RotorOpsUnits.client_helos client_helos = RotorOpsUnits.client_helos
unslotted_count = 0
slotted_count = 0
for helicopter in dcs.helicopters.helicopter_map: for helicopter in dcs.helicopters.helicopter_map:
if helicopter == options["slots"]: if helicopter == options["slots"]:
client_helos = [dcs.helicopters.helicopter_map[helicopter]] #if out ui slot option matches a specific helicopter type name client_helos = [dcs.helicopters.helicopter_map[
helicopter]] # if out ui slot option matches a specific helicopter type name
# get loadouts from miz file and put into a simple dict # get loadouts from miz file and put into a simple dict
default_loadouts = {} default_loadouts = {}
@ -476,10 +722,14 @@ class RotorOpsMission:
farp = self.m.country(jtf_blue).find_static_group("HELO_FARP") farp = self.m.country(jtf_blue).find_static_group("HELO_FARP")
if not farp: if not farp:
farp = self.m.country(jtf_blue).find_static_group("HELO_FARP_1") farp = self.m.country(jtf_blue).find_static_group("HELO_FARP_1")
heading = 0
if farp:
farp_heading = farp.units[0].heading
heading = farp_heading
friendly_airports, primary_f_airport = self.getCoalitionAirports("blue") friendly_airports, primary_f_airport = self.getCoalitionAirports("blue")
heading = 0
group_size = 1 group_size = 1
player_helicopters = [] player_helicopters = []
if options["slots"] == "Multiple Slots": if options["slots"] == "Multiple Slots":
@ -490,34 +740,75 @@ class RotorOpsMission:
if len(client_helos) == 1: if len(client_helos) == 1:
group_size = 2 # add a wingman if singleplayer group_size = 2 # add a wingman if singleplayer
# Hot/Cold start options
start_type = dcs.mission.StartType.Cold start_type = dcs.mission.StartType.Cold
start_type_string = ""
start_type_point_type = "TakeOffGround"
start_type_action = dcs.point.PointAction.FromGroundArea
if options["player_hotstart"]: if options["player_hotstart"]:
start_type = dcs.mission.StartType.Warm start_type = dcs.mission.StartType.Warm
start_type_string = "HOT "
start_type_point_type = "TakeOffGroundHot"
start_type_action = dcs.point.PointAction.FromGroundAreaHot
farp_helicopter_count = 1 farp_helicopter_count = 1
for helicopter_id in player_helicopters: for helicopter_id in player_helicopters:
fg = None
helotype = None helotype = None
if helicopter_id in dcs.helicopters.helicopter_map: if helicopter_id in dcs.helicopters.helicopter_map:
helotype = dcs.helicopters.helicopter_map[helicopter_id] helotype = dcs.helicopters.helicopter_map[helicopter_id]
else: else:
continue continue
if carrier: if carrier:
fg = self.m.flight_group_from_unit(self.m.country(jtf_blue), "CARRIER " + helotype.id, helotype, carrier, fg = self.m.flight_group_from_unit(self.m.country(jtf_blue),
"CARRIER " + start_type_string + helotype.id, helotype,
carrier,
dcs.task.CAS, group_size=group_size, start_type=start_type) dcs.task.CAS, group_size=group_size, start_type=start_type)
elif farp and farp_helicopter_count <= 4: elif farp and farp_helicopter_count <= 4:
farp_helicopter_count = farp_helicopter_count + 1
fg = self.m.flight_group_from_unit(self.m.country(jtf_blue), "FARP " + helotype.id, helotype, farp,
#old ugly FARPs, or single player groups with wingman require fg from unit
if farp.units[0].type != 'Invisible FARP':
print("making flight group from unit")
fg = self.m.flight_group_from_unit(self.m.country(jtf_blue),
"FARP " + start_type_string + helotype.id, helotype, farp,
dcs.task.CAS, group_size=group_size, start_type=start_type) dcs.task.CAS, group_size=group_size, start_type=start_type)
# invisible farps need manual unit placement for multiple units # invisible farps need manual unit placement for multiple units
if farp.units[0].type == 'Invisible FARP': elif farp.units[0].type == 'Invisible FARP':
fg.points[0].action = dcs.point.PointAction.FromGroundArea print("making standard flight group")
fg.points[0].type = "TakeOffGround" pos = farp.units[0].position.point_from_heading(heading, 20)
fg.units[0].position = fg.units[0].position.point_from_heading(heading, 20) farp_helicopter_count = farp_helicopter_count + 1
fg = self.m.flight_group(self.m.country(jtf_blue), "FARP " + start_type_string + helotype.id,
helotype, airport=None, position=pos, maintask=dcs.task.CAS, group_size=group_size, start_type=start_type)
fg.units[0].heading = farp_heading
if group_size > 1:
# move wingman if present
fg.units[1].position = farp.units[0].position.point_from_heading(180, 20)
fg.units[1].heading = farp_heading
# change heading for next helicopter placement
heading += 90 heading += 90
# hot or cold start
fg.points[0].action = start_type_action
fg.points[0].type = start_type_point_type
else: else:
fg = self.m.flight_group_from_airport(self.m.country(jtf_blue), primary_f_airport.name + " " + helotype.id, helotype, parking = self.getParking(primary_f_airport, helotype, friendly_airports,
self.getParking(primary_f_airport, helotype), group_size=group_size, start_type=start_type) group_size=group_size)
if parking:
fg = self.m.flight_group_from_airport(self.m.country(jtf_blue),
primary_f_airport.name + " " + start_type_string + helotype.id,
helotype,
parking, group_size=group_size, start_type=start_type)
# if we were able to find a slot and create a flight group
if fg:
slotted_count = slotted_count + 1
fg.units[0].set_client() fg.units[0].set_client()
# fg.load_task_default_loadout(dcs.task.CAS) # fg.load_task_default_loadout(dcs.task.CAS)
if helotype.id in default_loadouts: if helotype.id in default_loadouts:
@ -531,7 +822,13 @@ class RotorOpsMission:
fg.units[1].pylons = fg.units[0].pylons fg.units[1].pylons = fg.units[0].pylons
fg.units[1].livery_id = fg.units[0].livery_id fg.units[1].livery_id = fg.units[0].livery_id
fg.units[1].fuel = fg.units[0].fuel fg.units[1].fuel = fg.units[0].fuel
else:
logger.warn("No parking available for " + helotype.id)
unslotted_count = unslotted_count + 1
if unslotted_count > 0:
raise Exception("Player slots error: Unable to find parking for " + str(
unslotted_count) + " players. Maximum parking slots found was " + str(slotted_count))
class TrainingScenario(): class TrainingScenario():
@staticmethod @staticmethod
@ -548,8 +845,10 @@ class RotorOpsMission:
def perpRacetrack(enemy_heading, friendly_pt, terrain): def perpRacetrack(enemy_heading, friendly_pt, terrain):
heading = enemy_heading + random.randrange(70, 110) heading = enemy_heading + random.randrange(70, 110)
race_dist = random.randrange(40 * 1000, 80 * 1000) 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) center_pt = dcs.mapping.point_from_heading(friendly_pt.x, friendly_pt.y,
pt1 = dcs.mapping.point_from_heading(center_pt[0], center_pt[1], enemy_heading - 90, random.randrange(20 * 1000, 40 * 1000)) enemy_heading - random.randrange(140, 220), 10000)
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 return dcs.mapping.Point(pt1[0], pt1[1], terrain), heading, race_dist
def addFlights(self, options, red_forces, blue_forces): def addFlights(self, options, red_forces, blue_forces):
@ -568,19 +867,21 @@ class RotorOpsMission:
farp = self.m.country(jtf_red).find_static_group("HELO_FARP_1") farp = self.m.country(jtf_red).find_static_group("HELO_FARP_1")
e_airport_heading = dcs.mapping.heading_between_points( e_airport_heading = dcs.mapping.heading_between_points(
friendly_airports[0].position.x, friendly_airports[0].position.y, enemy_airports[0].position.x, primary_e_airport.position.y friendly_airports[0].position.x, friendly_airports[0].position.y, enemy_airports[0].position.x,
primary_e_airport.position.y
) )
e_airport_distance = dcs.mapping._distance( e_airport_distance = dcs.mapping._distance(
primary_f_airport.position.x, primary_f_airport.position.y, primary_f_airport.position.x, primary_f_airport.position.y primary_f_airport.position.x, primary_f_airport.position.y, primary_f_airport.position.x,
primary_f_airport.position.y
) )
if options["f_awacs"]: if options["f_awacs"]:
awacs_name = "AWACS" awacs_name = "AWACS"
awacs_freq = 266 awacs_freq = 266
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect) # pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position, self.m.terrain) pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
awacs = self.m.awacs_flight( awacs = self.m.awacs_flight(
combinedJointTaskForcesBlue, combinedJointTaskForcesBlue,
awacs_name, awacs_name,
@ -613,7 +914,6 @@ class RotorOpsMission:
unit.pylons = source_plane.pylons unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id unit.livery_id = source_plane.livery_id
# add text to mission briefing with radio freq # add text to mission briefing with radio freq
briefing = self.m.description_text() + "\n\n" + awacs_name + " " + str(awacs_freq) + ".00 " + "\n" briefing = self.m.description_text() + "\n\n" + awacs_name + " " + str(awacs_freq) + ".00 " + "\n"
self.m.set_description_text(briefing) self.m.set_description_text(briefing)
@ -626,7 +926,8 @@ class RotorOpsMission:
t2_freq = 256 t2_freq = 256
t2_tac = "101Y" t2_tac = "101Y"
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect) # pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position, self.m.terrain) pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
refuel_net = self.m.refuel_flight( refuel_net = self.m.refuel_flight(
combinedJointTaskForcesBlue, combinedJointTaskForcesBlue,
t1_name, t1_name,
@ -642,7 +943,8 @@ class RotorOpsMission:
tacanchannel=t1_tac) tacanchannel=t1_tac)
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect) # pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position, self.m.terrain) pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
refuel_rod = self.m.refuel_flight( refuel_rod = self.m.refuel_flight(
combinedJointTaskForcesBlue, combinedJointTaskForcesBlue,
t2_name, t2_name,
@ -656,7 +958,8 @@ class RotorOpsMission:
tacanchannel=t2_tac) tacanchannel=t2_tac)
# add text to mission briefing # add text to mission briefing
briefing = self.m.description_text() + "\n\n" + t1_name + " " + str(t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n" briefing = self.m.description_text() + "\n\n" + t1_name + " " + str(
t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n\n"
self.m.set_description_text(briefing) self.m.set_description_text(briefing)
def zone_attack(fg, airport): def zone_attack(fg, airport):
@ -676,9 +979,6 @@ class RotorOpsMission:
fg.points[0].tasks.append(dcs.task.OptReactOnThreat(dcs.task.OptReactOnThreat.Values.EvadeFire)) fg.points[0].tasks.append(dcs.task.OptReactOnThreat(dcs.task.OptReactOnThreat.Values.EvadeFire))
fg.points[0].tasks.append(dcs.task.OptROE(dcs.task.OptROE.Values.OpenFire)) fg.points[0].tasks.append(dcs.task.OptROE(dcs.task.OptROE.Values.OpenFire))
if options["e_attack_helos"]: if options["e_attack_helos"]:
source_helo = None source_helo = None
if red_forces["attack_helos"]: if red_forces["attack_helos"]:
@ -736,8 +1036,6 @@ class RotorOpsMission:
unit.pylons = source_helo.pylons unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id unit.livery_id = source_helo.livery_id
if options["e_attack_planes"]: if options["e_attack_planes"]:
source_plane = None source_plane = None
if red_forces["attack_planes"]: if red_forces["attack_planes"]:
@ -795,12 +1093,9 @@ class RotorOpsMission:
unit.pylons = source_helo.pylons unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id unit.livery_id = source_helo.livery_id
def importObjects(self, data):
imports = data["objects"]["imports"]
def importObjects(self):
os.chdir(directories.imports)
logger.info("Looking for import .miz files in '" + os.getcwd())
for side in "red", "blue", "neutrals": for side in "red", "blue", "neutrals":
coalition = self.m.coalition.get(side) coalition = self.m.coalition.get(side)
@ -810,8 +1105,10 @@ class RotorOpsMission:
if group.name.find(prefix) == 0: if group.name.find(prefix) == 0:
if group.units[0].name.find('IMPORT-') == 0: if group.units[0].name.find('IMPORT-') == 0:
logger.error( logger.error(
group.units[0].name + " IMPORT group's unit name cannot start with 'IMPORT'. Check the scenario template.") group.units[
raise Exception("Scenario file error: " + group.units[0].name + " IMPORT group's unit name cannot start with 'IMPORT'") 0].name + " IMPORT group's unit name cannot start with 'IMPORT'. Check the scenario template.")
raise Exception("Scenario file error: " + group.units[
0].name + " IMPORT group's unit name cannot start with 'IMPORT'")
# trim the groupname to our filename convention # trim the groupname to our filename convention
filename = group.name.removeprefix(prefix) filename = group.name.removeprefix(prefix)
@ -820,10 +1117,16 @@ class RotorOpsMission:
filename = filename[0:i] filename = filename[0:i]
print(filename) print(filename)
filename = filename + ".miz" for imp in imports:
i = ImportObjects(filename) if imp.filename == (filename + ".miz"):
i = ImportObjects(imp.path)
i.anchorByGroupName("ANCHOR") i.anchorByGroupName("ANCHOR")
new_statics, new_vehicles, new_helicopters = i.copyAll(self.m, country_name, group.units[0].name, group.units[0].position, group.units[0].heading) new_statics, new_vehicles, new_helicopters = i.copyAll(self.m, country_name,
group.units[0].name,
group.units[0].position,
group.units[0].heading)
break
def addMods(self): def addMods(self):
dcs.helicopters.helicopter_map["UH-60L"] = aircraftMods.UH_60L dcs.helicopters.helicopter_map["UH-60L"] = aircraftMods.UH_60L

Binary file not shown.

11
Generator/tests.py Normal file
View File

@ -0,0 +1,11 @@
import dcs
import dcs.cloud_presets
testm = dcs.mission.Mission()
# testCloudPresets
for i in range(0, len(dcs.cloud_presets.CLOUD_PRESETS)):
preset_name = list(dcs.cloud_presets.CLOUD_PRESETS)[i]
cloud_preset = dcs.weather.CloudPreset.by_name(preset_name)
testm.weather.clouds_preset = cloud_preset
print("Cloud preset = " + cloud_preset.ui_name)

View File

@ -1,3 +0,0 @@
*
*/
!.gitignore

View File

@ -21,7 +21,7 @@ spinboxes:
e_attack_planes_spinBox: 1 e_attack_planes_spinBox: 1
e_transport_helos_spinBox: 1 e_transport_helos_spinBox: 1
troop_drop_spinBox: 4 troop_drop_spinBox: 4
inf_spawn_spinBox: 2 inf_spawn_spinBox: 0
radiobuttons: farp_gunits, radiobuttons: farp_gunits,
red_forces: "RED Default Armor (HARD)" red_forces: "RED Default Armor (HARD)"
blue_forces: "BLUE Default US Armor" blue_forces: "BLUE Default US Armor"

View File

@ -1,2 +0,0 @@
local_ratings:
C:\RotorOps\templates\Scenarios\included\007d3d\Nevada Conflict - Vegas Tour (GRIMM).miz: 4

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
RotorOps = {} RotorOps = {}
RotorOps.version = "1.2.8" RotorOps.version = "1.3.0"
local debug = true local debug = true
@ -8,7 +8,7 @@ local debug = true
--- Protip: change these options from the mission editor rather than changing the script file itself. See documentation on github for details. --- Protip: change these options from the mission editor rather than changing the script file itself. See documentation on github for details.
--RotorOps settings that are safe to change dynamically (ideally from the mission editor in DO SCRIPT for portability). You can change these while the script is running, at any time. --RotorOps settings that can be changed dynamically (ideally from the mission editor in DO SCRIPT for portability). You can change these while the script is running, at any time. Be sure of your syntax and test...errors may crash the script.
RotorOps.voice_overs = true RotorOps.voice_overs = true
RotorOps.ground_speed = 60 --max speed for ground vehicles moving between zones. Doesn't have much effect since always limited by slowest vehicle in group RotorOps.ground_speed = 60 --max speed for ground vehicles moving between zones. Doesn't have much effect since always limited by slowest vehicle in group
RotorOps.zone_status_display = true --constantly show units remaining and zone status on screen RotorOps.zone_status_display = true --constantly show units remaining and zone status on screen
@ -21,8 +21,14 @@ RotorOps.defending_vehicles_disperse = true
RotorOps.inf_spawns_avail = 0 --this is the number of infantry group spawn events remaining in the active zone RotorOps.inf_spawns_avail = 0 --this is the number of infantry group spawn events remaining in the active zone
RotorOps.inf_spawn_chance = 25 -- 0-100 the chance of spawning infantry in an active zone spawn zone, per 'assessUnitsInZone' loop (10 seconds) RotorOps.inf_spawn_chance = 25 -- 0-100 the chance of spawning infantry in an active zone spawn zone, per 'assessUnitsInZone' loop (10 seconds)
RotorOps.inf_spawn_trigger_percent = 70 --infantry has a chance of spawning if the percentage of defenders remaining in zone is less than this value RotorOps.inf_spawn_trigger_percent = 70 --infantry has a chance of spawning if the percentage of defenders remaining in zone is less than this value
RotorOps.inf_spawns_per_zone = 3 --number of infantry groups to spawn per zone --RotorOps.inf_spawns_per_zone = 3 --number of infantry groups to spawn per zone
RotorOps.inf_spawn_messages = true --voiceovers and messages for infantry spawns RotorOps.inf_spawn_messages = true --voiceovers and messages for infantry spawns
RotorOps.inf_spawn_blue = {mg=1,at=0,aa=0,inf=4,mortar=0} --can be an integer quantity, or a ctld defined group table
RotorOps.inf_spawn_red = {mg=1,at=0,aa=0,inf=4,mortar=0} --can be an integer quantity, or a ctld defined group table
RotorOps.inf_apc_group = {mg=1,at=0,aa=0,inf=3,mortar=0} --can be an integer quantity, or a ctld defined group table
RotorOps.inf_spawns_total = 0 --number of infantry groups to spawn per game
RotorOps.farp_smoke_color = 2 -- Green=0 Red=1 White=2 Orange=3 Blue=4 NONE= -1
--RotorOps settings that are safe to change only before calling setupConflict() --RotorOps settings that are safe to change only before calling setupConflict()
@ -31,8 +37,18 @@ RotorOps.CTLD_crates = false
RotorOps.CTLD_sound_effects = true --sound effects for troop pickup/dropoffs RotorOps.CTLD_sound_effects = true --sound effects for troop pickup/dropoffs
RotorOps.exclude_ai_group_name = "Static" --include this somewhere in a group name to exclude the group from being tasked in the active zone RotorOps.exclude_ai_group_name = "Static" --include this somewhere in a group name to exclude the group from being tasked in the active zone
RotorOps.pickup_zone_smoke = "blue" RotorOps.pickup_zone_smoke = "blue"
RotorOps.apc_group = {mg=1,at=0,aa=0,inf=3,mortar=0} --not used yet, but we should define the CTLD groups RotorOps.ai_task_by_name = true --allow tasking all groups that include key strings in their group names eg 'Patrol'
RotorOps.ai_task_by_name_scheduler = true --continually search active groups for key strings and ai tasking
RotorOps.patrol_task_string = 'patrol' --default string to search group names for the patrol task. requires ai_task_by_name
RotorOps.aggressive_task_string = 'aggressive' --default string to search group names for the patrol task. requires ai_task_by_name
RotorOps.move_to_active_task_string = "activezone" --default string to search group names for the move to active zone task. requires ai_task_by_name
RotorOps.shift_task_string = "shift"
RotorOps.guard_task_string = "guard"
--RotorOps.patrol_task_radius = 100 --patrol search radius
--RotorOps.aggressive_task_radius = 1000 --aggressive search radius --not implementing for now until more time for testing
RotorOps.defending_vehicles_behavior = "shift" --available options: 'none', 'patrol', 'shift'
RotorOps.farp_pickups = true --allow ctld troop pickup at FARPs
RotorOps.enable_staging_pickzones = true
---[[END OF OPTIONS]]--- ---[[END OF OPTIONS]]---
@ -47,14 +63,13 @@ RotorOps.active_zone = "" --name of the active zone
RotorOps.active_zone_index = 0 RotorOps.active_zone_index = 0
RotorOps.game_state_flag = 1 --user flag to store the game state RotorOps.game_state_flag = 1 --user flag to store the game state
RotorOps.staging_zones = {} RotorOps.staging_zones = {}
RotorOps.ctld_pickup_zones = {} --keep track of ctld zones we've added, mainly for map markup
RotorOps.ai_defending_infantry_groups = {} RotorOps.ai_defending_infantry_groups = {}
RotorOps.ai_attacking_infantry_groups = {} RotorOps.ai_attacking_infantry_groups = {}
RotorOps.ai_defending_vehicle_groups = {} RotorOps.ai_defending_vehicle_groups = {}
RotorOps.ai_attacking_vehicle_groups = {} RotorOps.ai_attacking_vehicle_groups = {}
RotorOps.ai_tasks = {} RotorOps.ai_tasks = {}
RotorOps.defending = false RotorOps.defending = false
RotorOps.staged_units_flag = 111 RotorOps.staged_units_flag = 111 -- shows a percentage of the units found in the staging zone when the game starts. you can also use 'ROPS_ATTACKERS' for readability
trigger.action.outText("ROTOR OPS STARTED: "..RotorOps.version, 5) trigger.action.outText("ROTOR OPS STARTED: "..RotorOps.version, 5)
env.info("ROTOR OPS STARTED: "..RotorOps.version) env.info("ROTOR OPS STARTED: "..RotorOps.version)
@ -74,6 +89,13 @@ local cooldown = {
["attack_plane_msg"] = 0, ["attack_plane_msg"] = 0,
["trans_helo_msg"] = 0, ["trans_helo_msg"] = 0,
} }
local zone_defenders_flags = {
'ROPS_A_DEFENDERS',
'ROPS_B_DEFENDERS',
'ROPS_C_DEFENDERS',
'ROPS_D_DEFENDERS',
}
RotorOps.farp_names = {}
RotorOps.gameMsgs = { RotorOps.gameMsgs = {
@ -425,14 +447,14 @@ function RotorOps.getValidUnitFromGroup(grp)
else else
group_obj = grp group_obj = grp
end end
if not grp then if not group_obj then
return nil return nil
end end
if grp:isExist() ~= true then if group_obj:isExist() ~= true then
return nil return nil
end end
local first_valid_unit local first_valid_unit
for index, unit in pairs(grp:getUnits()) for index, unit in pairs(group_obj:getUnits())
do do
if unit:isExist() == true then if unit:isExist() == true then
first_valid_unit = unit first_valid_unit = unit
@ -443,7 +465,35 @@ function RotorOps.getValidUnitFromGroup(grp)
return first_valid_unit return first_valid_unit
end end
--"static" in this case, is our groups/units that we don't want controlled by conflict zone tasks
local function isStaticUnit(unit)
local unit_obj
if type(unit) == 'string' then
unit_obj = Unit.getByName(unit)
else
unit_obj = unit
end
if string.find(unit_obj:getGroup():getName():lower(), RotorOps.exclude_ai_group_name:lower()) then
return true
else
return false
end
end
--"static" in this case, is our groups/units that we don't want controlled by conflict zone tasks
local function isStaticGroup(group)
local group_obj
if type(group) == 'string' then
group_obj = Group.getByName(group)
else
group_obj = group
end
if string.find(group_obj:getName():lower(), RotorOps.exclude_ai_group_name:lower()) then
return true
else
return false
end
end
----USEFUL PUBLIC FUNCTIONS FOR THE MISSION EDITOR--- ----USEFUL PUBLIC FUNCTIONS FOR THE MISSION EDITOR---
@ -500,21 +550,20 @@ end
--see list of tasks in aiExecute. Zone is optional for many tasks --see list of tasks in aiExecute. Zone/point is optional for many tasks. Works with group name or object/table
function RotorOps.aiTask(grp, task, zone) function RotorOps.aiTask(grp, task, zone, point)
local group_name local group_name
if type(grp) == 'string' then if type(grp) == 'string' then
group_name = grp group_name = grp
else else
group_name = Group.getName(grp) group_name = Group.getName(grp)
end end
if string.find(group_name:lower(), RotorOps.exclude_ai_group_name:lower()) then --exclude groups that the user specifies with a special group name
return
end
if tableHasKey(RotorOps.ai_tasks, group_name) == true then --if we already have this group in our list to manage if tableHasKey(RotorOps.ai_tasks, group_name) == true then --if we already have this group in our list to manage
--debugMsg("timer already exists, updating task for "..group_name.." : ".. RotorOps.ai_tasks[group_name].ai_task.." to "..task) --debugMsg("timer already exists, updating task for "..group_name.." : ".. RotorOps.ai_tasks[group_name].ai_task.." to "..task)
RotorOps.ai_tasks[group_name].ai_task = task RotorOps.ai_tasks[group_name].ai_task = task
RotorOps.ai_tasks[group_name].zone = zone RotorOps.ai_tasks[group_name].zone = zone
RotorOps.ai_tasks[group_name].point = point
else else
local vars = {} local vars = {}
vars.group_name = group_name vars.group_name = group_name
@ -522,8 +571,11 @@ function RotorOps.aiTask(grp, task, zone)
if zone then if zone then
vars.zone = zone vars.zone = zone
end end
if point then
vars.point = point
end
local timer_id = timer.scheduleFunction(RotorOps.aiExecute, vars, timer.getTime() + 5) local timer_id = timer.scheduleFunction(RotorOps.aiExecute, vars, timer.getTime() + 5)
RotorOps.ai_tasks[group_name] = {['timer_id'] = timer_id, ['ai_task'] = task, ['zone'] = zone} RotorOps.ai_tasks[group_name] = {['timer_id'] = timer_id, ['ai_task'] = task, ['zone'] = zone, ['point'] = point}
end end
end end
@ -541,23 +593,24 @@ function RotorOps.tallyZone(zone_name)
for index, unit in pairs(new_units) do for index, unit in pairs(new_units) do
if not hasValue(RotorOps.staged_units, unit) then if not hasValue(RotorOps.staged_units, unit) then
if not isStaticUnit(unit) then
env.info("RotorOps adding new units to staged_units: "..#new_units) env.info("RotorOps adding new units to staged_units: "..#new_units)
table.insert(RotorOps.staged_units, unit) table.insert(RotorOps.staged_units, unit)
RotorOps.aiTask(unit:getGroup(),"move_to_active_zone", RotorOps.zones[RotorOps.active_zone_index].name) RotorOps.aiTask(unit:getGroup(),"move_to_active_zone", RotorOps.zones[RotorOps.active_zone_index].name)
end
else else
--env.info("unit already in table") --env.info("unit already in table")
end end
end end
end end
--
-- for index, unit in pairs(RotorOps.staged_units) do
-- if string.find(Unit.getGroup(unit):getName():lower(), RotorOps.exclude_ai_group_name:lower()) then
-- RotorOps.staged_units[index] = nil --remove 'static' units
-- end
-- end
end end
--display a text message to all players with a radio sound effect
function RotorOps.radioText(message)
RotorOps.gameMsg({message, 'radio_effect.ogg'})
end
---AI CORE BEHAVIOR-- ---AI CORE BEHAVIOR--
@ -772,6 +825,138 @@ function RotorOps.patrolRadius(vars)
end end
function RotorOps.shiftPosition(vars)
--debugMsg("patrol radius: "..mist.utils.tableShow(vars.grp))
local grp = vars.grp
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
local start_point = vars.point
if not start_point then
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
break
else --trigger.action.outText("a unit no longer exists", 15)
end
end
if first_valid_unit == nil then return end
start_point = first_valid_unit:getPoint()
end
local max_waypoints = 2
local urban = RotorOps.pointIsUrban(start_point, 100)
formation = 'Cone'
if urban then
formation = 'On Road'
end
local path = {}
path[1] = mist.ground.buildWP(start_point, '', 5)
for i = #path, max_waypoints, 1 do
for i = 1, 4, 1 do
local rand_point = mist.getRandPointInCircle(start_point, search_radius, inner_radius)
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)
break
end
end
end
mist.goRoute(grp, path)
end
function RotorOps.guardPosition(vars)
--debugMsg("patrol radius: "..mist.utils.tableShow(vars.grp))
local grp = vars.grp
local search_radius = vars.radius or 100
local first_valid_unit
if 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.")
for index, unit in pairs(grp:getUnits()) do
if unit:isExist() == true then
first_valid_unit = unit
break
else --trigger.action.outText("a unit no longer exists", 15)
end
end
if first_valid_unit == nil then return end
start_point = first_valid_unit:getPoint()
end
local object_vol_thresh = 0
local max_waypoints = 1
local foundUnits = {}
local volS = {
id = world.VolumeType.SPHERE,
params = {
point = grp:getUnit(1):getPoint(), --check if exists, maybe itterate through grp
radius = search_radius
}
}
local ifFound = function(foundItem, val)
--trigger.action.outText("found item: "..foundItem:getTypeName(), 5)
if foundItem:hasAttribute("Infantry") ~= true then --disregard infantry...we only want objects that might provide cover
if getObjectVolume(foundItem) > object_vol_thresh then
foundUnits[#foundUnits + 1] = foundItem
--trigger.action.outText("valid cover item: "..foundItem:getTypeName(), 5)
else --debugMsg("object not large enough: "..foundItem:getTypeName())
end
else --trigger.action.outText("object not the right type", 5)
end
return true
end
world.searchObjects(1, volS, ifFound)
world.searchObjects(3, volS, ifFound)
world.searchObjects(5, volS, ifFound)
--world.searchObjects(Object.Category.BASE, volS, ifFound)
if #foundUnits > 0 then
local path = {}
path[1] = mist.ground.buildWP(start_point, '', 5)
local rand_index = math.random(1,#foundUnits)
path[#path + 1] = mist.ground.buildWP(foundUnits[rand_index]:getPoint(), '', 3)
mist.goRoute(grp, path)
end
end
--helper function to try to determine a point is near many scenery objects
function RotorOps.pointIsUrban(_point, _radius)
local volS = {
id = world.VolumeType.SPHERE,
params = {
point = _point,
radius = _radius
}
}
local foundUnits = {}
local ifFound = function(foundItem, val)
foundUnits[#foundUnits + 1] = foundItem
end
world.searchObjects(5, volS, ifFound)
--env.info("Found scenery objects: " .. #foundUnits)
if #foundUnits > 10 then
return true
end
return false
end
function RotorOps.aiExecute(vars) function RotorOps.aiExecute(vars)
local update_interval = 60 local update_interval = 60
@ -780,6 +965,7 @@ function RotorOps.aiExecute(vars)
local group_name = vars.group_name local group_name = vars.group_name
local task = RotorOps.ai_tasks[group_name].ai_task local task = RotorOps.ai_tasks[group_name].ai_task
local zone = RotorOps.ai_tasks[group_name].zone local zone = RotorOps.ai_tasks[group_name].zone
local point = RotorOps.ai_tasks[group_name].point
-- if vars.zone then zone = vars.zone end -- if vars.zone then zone = vars.zone end
@ -862,6 +1048,20 @@ function RotorOps.aiExecute(vars)
local speed = RotorOps.ground_speed local speed = RotorOps.ground_speed
local force_offroad = RotorOps.force_offroad local force_offroad = RotorOps.force_offroad
mist.groupToPoint(group_name, RotorOps.active_zone, formation, final_heading, speed, force_offroad) mist.groupToPoint(group_name, RotorOps.active_zone, formation, final_heading, speed, force_offroad)
elseif task == "shift" then
local vars = {}
vars.grp = Group.getByName(group_name)
vars.radius = 250
vars.point = point
RotorOps.shiftPosition(vars) --takes a group object, not name
update_interval = math.random(60,360)
elseif task == "guard" then
local vars = {}
vars.grp = Group.getByName(group_name)
vars.radius = 100
vars.point = point
RotorOps.guardPosition(vars) --takes a group object, not name
update_interval = math.random(60,120)
end end
@ -917,27 +1117,37 @@ function RotorOps.assessUnitsInZone(var)
RotorOps.ai_attacking_infantry_groups = RotorOps.groupsFromUnits(attacking_infantry) RotorOps.ai_attacking_infantry_groups = RotorOps.groupsFromUnits(attacking_infantry)
RotorOps.ai_attacking_vehicle_groups = RotorOps.groupsFromUnits(attacking_vehicles) RotorOps.ai_attacking_vehicle_groups = RotorOps.groupsFromUnits(attacking_vehicles)
for index, group in pairs(RotorOps.ai_defending_infantry_groups) do for index, group in pairs(RotorOps.ai_defending_infantry_groups) do
if group then if group and not isStaticGroup(group) then
RotorOps.aiTask(group, "patrol") RotorOps.aiTask(group, "patrol")
end end
end end
for index, group in pairs(RotorOps.ai_attacking_infantry_groups) do for index, group in pairs(RotorOps.ai_attacking_infantry_groups) do
if group then if group and not isStaticGroup(group) then
RotorOps.aiTask(group, "clear_zone", RotorOps.active_zone) RotorOps.aiTask(group, "clear_zone", RotorOps.active_zone)
end end
end end
for index, group in pairs(RotorOps.ai_attacking_vehicle_groups) do for index, group in pairs(RotorOps.ai_attacking_vehicle_groups) do
if group then if group and not isStaticGroup(group) then
RotorOps.aiTask(group, "clear_zone", RotorOps.active_zone) RotorOps.aiTask(group, "clear_zone", RotorOps.active_zone)
end end
end end
for index, group in pairs(RotorOps.ai_defending_vehicle_groups) do for index, group in pairs(RotorOps.ai_defending_vehicle_groups) do
if group then if group and not isStaticGroup(group) then
Group.getByName(group):getController():setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK , RotorOps.defending_vehicles_disperse) Group.getByName(group):getController():setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK , RotorOps.defending_vehicles_disperse)
if RotorOps.defending_vehicles_behavior == "patrol" then
RotorOps.aiTask(group, "patrol")
elseif RotorOps.defending_vehicles_behavior == "shift" then
local unit = RotorOps.getValidUnitFromGroup(group)
if unit then
RotorOps.aiTask(group, "shift", nil, unit:getPoint())
end
end
end end
end end
@ -953,13 +1163,20 @@ function RotorOps.assessUnitsInZone(var)
--sort infantry spawn zones and spawn quantity --sort infantry spawn zones and spawn quantity
inf_spawn_zones = {} inf_spawn_zones = {}
local total_spawn_zones = 0
for zone, zoneobj in pairs(mist.DBs.zonesByName) do for zone, zoneobj in pairs(mist.DBs.zonesByName) do
if string.find(zone, RotorOps.active_zone) and string.find(zone:lower(), "spawn") then --if we find a zone that has the active zone name and the word spawn if string.find(zone, RotorOps.active_zone) and string.find(zone:lower(), "spawn") then --if we find a zone that has the active zone name and the word spawn
inf_spawn_zones[#inf_spawn_zones + 1] = zone inf_spawn_zones[#inf_spawn_zones + 1] = zone
env.info("ROTOR OPS: spawn zone found:"..zone) env.info("ROTOR OPS: spawn zone found:"..zone)
end end
if string.find(zone:lower(), "spawn") then
total_spawn_zones = total_spawn_zones + 1
end
end
--RotorOps.inf_spawns_avail = RotorOps.inf_spawns_per_zone * RotorOps.inf_spawn_multiplier[RotorOps.active_zone_index]
if total_spawn_zones > 0 then
RotorOps.inf_spawns_avail = (RotorOps.inf_spawns_total / total_spawn_zones) * #inf_spawn_zones
end end
RotorOps.inf_spawns_avail = RotorOps.inf_spawns_per_zone * #inf_spawn_zones
env.info("ROTOR OPS: zone activated: "..RotorOps.active_zone..", inf spawns avail:"..RotorOps.inf_spawns_avail..", spawn zones:"..#inf_spawn_zones) env.info("ROTOR OPS: zone activated: "..RotorOps.active_zone..", inf spawns avail:"..RotorOps.inf_spawns_avail..", spawn zones:"..#inf_spawn_zones)
end end
@ -973,6 +1190,7 @@ function RotorOps.assessUnitsInZone(var)
active_zone_initial_defenders = nil active_zone_initial_defenders = nil
defenders_remaining_percent = 0 defenders_remaining_percent = 0
trigger.action.setUserFlag(defenders_status_flag, 0) --set the zone's flag to cleared trigger.action.setUserFlag(defenders_status_flag, 0) --set the zone's flag to cleared
trigger.action.setUserFlag(zone_defenders_flags[RotorOps.active_zone_index], 0) --set the zone's flag to cleared
if RotorOps.defending == true then if RotorOps.defending == true then
RotorOps.gameMsg(RotorOps.gameMsgs.enemy_cleared_zone, RotorOps.active_zone_index) RotorOps.gameMsg(RotorOps.gameMsgs.enemy_cleared_zone, RotorOps.active_zone_index)
else else
@ -984,6 +1202,7 @@ function RotorOps.assessUnitsInZone(var)
else else
trigger.action.setUserFlag(defenders_status_flag, defenders_remaining_percent) --set the zones flag to indicate the status of remaining defenders trigger.action.setUserFlag(defenders_status_flag, defenders_remaining_percent) --set the zones flag to indicate the status of remaining defenders
trigger.action.setUserFlag(zone_defenders_flags[RotorOps.active_zone_index], defenders_remaining_percent)
end end
--are all zones clear? --are all zones clear?
@ -998,13 +1217,14 @@ function RotorOps.assessUnitsInZone(var)
--update staged units remaining flag --update staged units remaining flag
local staged_units_remaining = {} local staged_units_remaining = {}
for index, unit in pairs(RotorOps.staged_units) do for index, unit in pairs(RotorOps.staged_units) do
if unit:isExist() then if unit:isExist() and unit:getLife() > 0 then
staged_units_remaining[#staged_units_remaining + 1] = unit staged_units_remaining[#staged_units_remaining + 1] = unit
end end
end end
local percent_staged_remain = 0 local percent_staged_remain = 0
percent_staged_remain = math.floor((#staged_units_remaining / #RotorOps.staged_units) * 100) 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(RotorOps.staged_units_flag, percent_staged_remain)
trigger.action.setUserFlag('ROPS_ATTACKERS', percent_staged_remain)
debugMsg("Staged units remaining percent: "..percent_staged_remain.."%") debugMsg("Staged units remaining percent: "..percent_staged_remain.."%")
@ -1013,9 +1233,11 @@ function RotorOps.assessUnitsInZone(var)
if RotorOps.defending == true then if RotorOps.defending == true then
RotorOps.game_state = RotorOps.game_states.lost RotorOps.game_state = RotorOps.game_states.lost
trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.lost) trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.lost)
trigger.action.setUserFlag('ROPS_GAMESTATE', RotorOps.game_states.lost)
else else
RotorOps.game_state = RotorOps.game_states.won RotorOps.game_state = RotorOps.game_states.won
trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.won) trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.won)
trigger.action.setUserFlag('ROPS_GAMESTATE', RotorOps.game_states.won)
end end
return --we won't reset our timer to fire this function again return --we won't reset our timer to fire this function again
end end
@ -1030,6 +1252,7 @@ function RotorOps.assessUnitsInZone(var)
if RotorOps.defending and defending_game_won then if RotorOps.defending and defending_game_won then
RotorOps.game_state = RotorOps.game_states.won RotorOps.game_state = RotorOps.game_states.won
trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.won) trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.won)
trigger.action.setUserFlag('ROPS_GAMESTATE', RotorOps.game_states.won)
return --we won't reset our timer to fire this function again return --we won't reset our timer to fire this function again
end end
@ -1064,7 +1287,7 @@ function RotorOps.assessUnitsInZone(var)
local function timedDeploy() local function timedDeploy()
if vehicle:isExist() then if vehicle:isExist() then
env.info(vehicle:getName().." is deploying troops.") env.info(vehicle:getName().." is deploying troops.")
RotorOps.deployTroops(4, vehicle:getGroup(), false) RotorOps.deployTroops(RotorOps.inf_apc_group, vehicle:getGroup(), false)
end end
end end
@ -1086,9 +1309,9 @@ function RotorOps.assessUnitsInZone(var)
local zone = inf_spawn_zones[rand_index] local zone = inf_spawn_zones[rand_index]
if RotorOps.defending then if RotorOps.defending then
ctld.spawnGroupAtTrigger("blue", 5, zone, 1000) ctld.spawnGroupAtTrigger("blue", RotorOps.inf_spawn_blue, zone, 1000)
else else
ctld.spawnGroupAtTrigger("red", 5, zone, 1000) ctld.spawnGroupAtTrigger("red", RotorOps.inf_spawn_red, zone, 1000)
RotorOps.gameMsg(RotorOps.gameMsgs.infantry_spawned, math.random(1, #RotorOps.gameMsgs.infantry_spawned)) RotorOps.gameMsg(RotorOps.gameMsgs.infantry_spawned, math.random(1, #RotorOps.gameMsgs.infantry_spawned))
end end
@ -1120,12 +1343,19 @@ function RotorOps.assessUnitsInZone(var)
local message = "" local message = ""
local header = "" local header = ""
local body = "" local body = ""
-- if RotorOps.defending == true then
-- header = "[DEFEND "..RotorOps.active_zone .. "] "
-- body = "RED: " ..#attacking_infantry.. " infantry, " .. #attacking_vehicles .. " vehicles. BLUE: "..#defending_infantry.. " infantry, " .. #defending_vehicles.." vehicles. ["..defenders_remaining_percent.."%]"
-- else
-- header = "[ATTACK "..RotorOps.active_zone .. "] "
-- body = "RED: " ..#defending_infantry.. " infantry, " .. #defending_vehicles .. " vehicles. BLUE: "..#attacking_infantry.. " infantry, " .. #attacking_vehicles.." vehicles. ["..defenders_remaining_percent.."%]"
-- end
if RotorOps.defending == true then if RotorOps.defending == true then
header = "[DEFEND "..RotorOps.active_zone .. "] " header = "[DEFEND "..RotorOps.active_zone .. "] "
body = "RED: " ..#attacking_infantry.. " infantry, " .. #attacking_vehicles .. " vehicles. BLUE: "..#defending_infantry.. " infantry, " .. #defending_vehicles.." vehicles. ["..defenders_remaining_percent.."%]" body = "BLUE: "..#defending_infantry.. " infantry, " .. #defending_vehicles.." vehicles. RED CONVOY: " .. #staged_units_remaining .." vehicles. ["..percent_staged_remain.."%]"
else else
header = "[ATTACK "..RotorOps.active_zone .. "] " header = "[ATTACK "..RotorOps.active_zone .. "] "
body = "RED: " ..#defending_infantry.. " infantry, " .. #defending_vehicles .. " vehicles. BLUE: "..#attacking_infantry.. " infantry, " .. #attacking_vehicles.." vehicles. ["..defenders_remaining_percent.."%]" body = "RED: " ..#defending_infantry.. " infantry, " .. #defending_vehicles .. " vehicles. BLUE CONVOY: " .. #staged_units_remaining .." vehicles. ["..percent_staged_remain.."%]"
end end
message = header .. body message = header .. body
@ -1169,28 +1399,48 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
trigger.action.textToAll(coalition, id + 100, point, color, text_fill_color, font_size, read_only, text) trigger.action.textToAll(coalition, id + 100, point, color, text_fill_color, font_size, read_only, text)
end end
for index, cpz in pairs(ctld.pickupZones) do
for index, pickup_zone in pairs(RotorOps.ctld_pickup_zones) env.info("CTLD pickzone name: " .. cpz[1])
do pickup_zone = trigger.misc.getZone(cpz[1])
for c_index, c_zone in pairs(ctld.pickupZones) if pickup_zone then
do env.info("found a ctld pickup zone")
if pickup_zone == c_zone[1] then local ctld_zone_status = cpz[4]
--debugMsg("found our zone in ctld zones, status: "..c_zone[4]) local point = pickup_zone.point
local ctld_zone_status = c_zone[4] local radius = pickup_zone.radius
local point = trigger.misc.getZone(pickup_zone).point
local radius = trigger.misc.getZone(pickup_zone).radius
local coalition = -1 local coalition = -1
local id = index + 150 --this must be UNIQUE! local id = index + 150 --this must be UNIQUE!
local color = {1, 1, 1, 0.5} local color = {1, 1, 1, 0.5}
local fill_color = {0, 0.8, 0, 0.1} 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 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 if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
--debugMsg("draw the pickup zone") 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(coalition, id, point, radius, color, fill_color, line_type)
end end
end end
end end
end
-- for index, pickup_zone in pairs(RotorOps.ctld_pickup_zones)
-- do
-- for c_index, c_zone in pairs(ctld.pickupZones)
-- do
-- if pickup_zone == c_zone[1] then
-- --debugMsg("found our zone in ctld zones, status: "..c_zone[4])
-- 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 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)
-- end
-- end
-- end
-- end
end end
@ -1211,17 +1461,17 @@ function RotorOps.setActiveZone(new_index)
if new_index ~= old_index then --the active zone is changing if new_index ~= old_index then --the active zone is changing
if not RotorOps.defending then -- if not RotorOps.defending then
if old_index > 0 and RotorOps.apcs_spawn_infantry == false then -- if old_index > 0 and RotorOps.apcs_spawn_infantry == false then
ctld.activatePickupZone(RotorOps.zones[old_index].name) --make the captured zone a pickup zone -- ctld.activatePickupZone(RotorOps.farp_names[old_index]) --make the captured zone a pickup zone
end -- end
ctld.deactivatePickupZone(RotorOps.zones[new_index].name) -- ctld.deactivatePickupZone(RotorOps.farp_names[new_index])
end -- end
RotorOps.game_state = new_index RotorOps.game_state = new_index
trigger.action.setUserFlag(RotorOps.game_state_flag, new_index) trigger.action.setUserFlag(RotorOps.game_state_flag, new_index)
trigger.action.setUserFlag('ROPS_GAMESTATE', new_index)
if new_index > old_index then if new_index > old_index then
if RotorOps.defending == true then if RotorOps.defending == true then
RotorOps.gameMsg(RotorOps.gameMsgs.enemy_pushing, new_index) RotorOps.gameMsg(RotorOps.gameMsgs.enemy_pushing, new_index)
@ -1290,6 +1540,28 @@ function RotorOps.setupCTLD()
} }
--add to CTLD default pickzone names. This could be done in a loop but this should be more readable
--pickupZones = { "Zone name or Ship Unit Name", "smoke color", "limit (-1 unlimited)", "ACTIVE (yes/no)", "side (0 = Both sides / 1 = Red / 2 = Blue )", flag number (optional) }
ctld.pickupZones[#ctld.pickupZones + 1] = { "STAGING", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "STAGING_BASE", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "ALPHA_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "BRAVO_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "CHARLIE_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "DELTA_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER", "none", -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER_1", "none", -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops1", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops2", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops3", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops4", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops5", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops6", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops7", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops8", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops9", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops10", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
end end
@ -1310,8 +1582,13 @@ function RotorOps.addZone(_name, _zone_defenders_flag)
end end
table.insert(RotorOps.zones, {name = _name, defenders_status_flag = _zone_defenders_flag}) table.insert(RotorOps.zones, {name = _name, defenders_status_flag = _zone_defenders_flag})
trigger.action.setUserFlag(_zone_defenders_flag, 101) trigger.action.setUserFlag(_zone_defenders_flag, 101)
trigger.action.setUserFlag(zone_defenders_flags[1], 101)
trigger.action.setUserFlag(zone_defenders_flags[2], 101)
trigger.action.setUserFlag(zone_defenders_flags[3], 101)
trigger.action.setUserFlag(zone_defenders_flags[4], 101)
RotorOps.drawZones() RotorOps.drawZones()
RotorOps.addPickupZone(_name, RotorOps.pickup_zone_smoke, -1, "no", 2) local farp_name = _name .. "_FARP"
RotorOps.farp_names[#RotorOps.farp_names + 1] = farp_name
end end
@ -1320,7 +1597,7 @@ function RotorOps.addStagingZone(_name)
trigger.action.outText(_name.." trigger zone missing! Check RotorOps setup!", 60) trigger.action.outText(_name.." trigger zone missing! Check RotorOps setup!", 60)
env.warning(_name.." trigger zone missing! Check RotorOps setup!") env.warning(_name.." trigger zone missing! Check RotorOps setup!")
end end
RotorOps.addPickupZone(_name, RotorOps.pickup_zone_smoke, -1, "no", 0)
RotorOps.staging_zones[#RotorOps.staging_zones + 1] = _name RotorOps.staging_zones[#RotorOps.staging_zones + 1] = _name
end end
@ -1348,6 +1625,7 @@ function RotorOps.setupConflict(_game_state_flag)
RotorOps.game_state = RotorOps.game_states.not_started RotorOps.game_state = RotorOps.game_states.not_started
processMsgBuffer() processMsgBuffer()
trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.not_started) trigger.action.setUserFlag(RotorOps.game_state_flag, RotorOps.game_states.not_started)
trigger.action.setUserFlag('ROPS_GAMESTATE', RotorOps.game_states.not_started)
trigger.action.outText("ALL TROOPS GET TO TRANSPORT AND PREPARE FOR DEPLOYMENT!" , 10, false) trigger.action.outText("ALL TROOPS GET TO TRANSPORT AND PREPARE FOR DEPLOYMENT!" , 10, false)
if RotorOps.CTLD_sound_effects == true then if RotorOps.CTLD_sound_effects == true then
local timer_id = timer.scheduleFunction(RotorOps.registerCtldCallbacks, 1, timer.getTime() + 5) local timer_id = timer.scheduleFunction(RotorOps.registerCtldCallbacks, 1, timer.getTime() + 5)
@ -1355,8 +1633,7 @@ function RotorOps.setupConflict(_game_state_flag)
end end
function RotorOps.addPickupZone(zone_name, smoke, limit, active, side) function RotorOps.addPickupZone(zone_name, smoke, limit, active, side) --depreciated, don't use
RotorOps.ctld_pickup_zones[#RotorOps.ctld_pickup_zones + 1] = zone_name
ctld.pickupZones[#ctld.pickupZones + 1] = {zone_name, smoke, limit, active, side} ctld.pickupZones[#ctld.pickupZones + 1] = {zone_name, smoke, limit, active, side}
end end
@ -1370,14 +1647,14 @@ function RotorOps.startConflict()
--missionCommands.removeItem(commandDB['start_conflict']) --missionCommands.removeItem(commandDB['start_conflict'])
--commandDB['clear_zone'] = missionCommands.addCommand( "[CHEAT] Force Clear Zone" , conflict_zones_menu , RotorOps.clearActiveZone) --commandDB['clear_zone'] = missionCommands.addCommand( "[CHEAT] Force Clear Zone" , conflict_zones_menu , RotorOps.clearActiveZone)
RotorOps.staged_units = mist.getUnitsInZones(mist.makeUnitTable({'[all][vehicle]'}), RotorOps.staging_zones) local units_found = mist.getUnitsInZones(mist.makeUnitTable({'[all][vehicle]'}), RotorOps.staging_zones)
--filter out 'static' units --filter out 'static' units
-- for index, unit in pairs(RotorOps.staged_units) do for index, unit in pairs(units_found) do
-- if string.find(Unit.getGroup(unit):getName():lower(), RotorOps.exclude_ai_group_name:lower()) then if not isStaticUnit(unit) then
-- RotorOps.staged_units[index] = nil --remove 'static' units RotorOps.staged_units[#RotorOps.staged_units + 1] = unit
-- end end
-- end end
if RotorOps.staged_units[1] == nil then if RotorOps.staged_units[1] == nil then
@ -1387,23 +1664,35 @@ function RotorOps.startConflict()
end end
if RotorOps.staged_units[1]:getCoalition() == 1 then --check the coalition in the staging zone to see if we're defending if RotorOps.staged_units[1]:getCoalition() == 1 then --check the coalition in the staging zone to see if we're defending
--DEFENSE
trigger.action.setUserFlag('ROPS_DEFENDING', 1)
RotorOps.defending = true RotorOps.defending = true
RotorOps.gameMsg(RotorOps.gameMsgs.start_defense) RotorOps.gameMsg(RotorOps.gameMsgs.start_defense)
ctld.activatePickupZone(RotorOps.zones[#RotorOps.zones].name) --make the last zone a pickup zone for defenders ctld.activatePickupZone(RotorOps.farp_names[#RotorOps.farp_names]) --make the last zone a pickup zone for defenders
for index, zone in pairs(RotorOps.staging_zones) do
ctld.deactivatePickupZone(zone)
end
else else
--OFFENSE
RotorOps.gameMsg(RotorOps.gameMsgs.start) RotorOps.gameMsg(RotorOps.gameMsgs.start)
for index, zone in pairs(RotorOps.staging_zones) do if RotorOps.enable_staging_pickzones then
ctld.activatePickupZone(zone) if trigger.misc.getZone("STAGING_BASE") then
ctld.activatePickupZone("STAGING_BASE")
else
ctld.activatePickupZone("STAGING")
end
end end
end end
RotorOps.setActiveZone(1) RotorOps.setActiveZone(1)
if RotorOps.ai_task_by_name then
RotorOps.taskByName()
end
local id = timer.scheduleFunction(RotorOps.assessUnitsInZone, 1, timer.getTime() + 5) local id = timer.scheduleFunction(RotorOps.assessUnitsInZone, 1, timer.getTime() + 5)
world.addEventHandler(RotorOps.eventHandler) world.addEventHandler(RotorOps.eventHandler)
end end
@ -1431,6 +1720,32 @@ function RotorOps.triggerSpawn(groupName, msg, resume_task)
end end
---Search for group names containing key strings to assign AI tasks
function RotorOps.taskByName()
env.info("RotorOps searching for groups to taskByName")
for group_name, data in pairs(mist.DBs.groupsByName) do
if string.find(group_name:lower(), RotorOps.patrol_task_string:lower()) then
RotorOps.aiTask(group_name, "patrol")
env.info("Tasking " .. group_name .. " as patrol.")
elseif string.find(group_name:lower(), RotorOps.aggressive_task_string:lower()) then
RotorOps.aiTask(group_name, "aggressive")
env.info("Tasking " .. group_name .. " as aggressive.")
elseif string.find(group_name:lower(), RotorOps.move_to_active_task_string:lower()) then
RotorOps.aiTask(group_name, "move_to_active_zone")
env.info("Tasking " .. group_name .. " to move to active zone.")
elseif string.find(group_name:lower(), RotorOps.shift_task_string:lower()) then
RotorOps.aiTask(group_name, "shift")
env.info("Tasking " .. group_name .. " to shift positions.")
elseif string.find(group_name:lower(), RotorOps.guard_task_string:lower()) then
RotorOps.aiTask(group_name, "guard")
env.info("Tasking " .. group_name .. " to guard positions.")
end
end
if RotorOps.ai_task_by_name_scheduler then
local timer_id = timer.scheduleFunction(RotorOps.taskByName, nil, timer.getTime() + 120)
end
end
function RotorOps.spawnAttackHelos() function RotorOps.spawnAttackHelos()
RotorOps.triggerSpawn("Enemy Attack Helicopters", RotorOps.gameMsgs.attack_helos_prep, true) RotorOps.triggerSpawn("Enemy Attack Helicopters", RotorOps.gameMsgs.attack_helos_prep, true)
@ -1443,8 +1758,16 @@ end
function RotorOps.farpEstablished(index) function RotorOps.farpEstablished(index, trigger_zone)
env.info("RotorOps FARP established at "..RotorOps.zones[index].name) env.info("RotorOps FARP established at "..RotorOps.zones[index].name)
if trigger_zone then
if RotorOps.farp_pickups then
ctld.activatePickupZone(trigger_zone)
end
if RotorOps.farp_smoke_color >= 0 and RotorOps.pickup_zone_smoke == 'none' then
trigger.action.smoke(trigger.misc.getZone(trigger_zone).point , RotorOps.farp_smoke_color)
end
end
timer.scheduleFunction(function()RotorOps.gameMsg(RotorOps.gameMsgs.farp_established, index) end, {}, timer.getTime() + 15) timer.scheduleFunction(function()RotorOps.gameMsg(RotorOps.gameMsgs.farp_established, index) end, {}, timer.getTime() + 15)
end end
@ -1562,6 +1885,7 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
end end
--- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls) --- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls)
--determine if any human players are above a defined ceiling above ground level. If 'above' parameter is false, function will return true if no players above ceiling --determine if any human players are above a defined ceiling above ground level. If 'above' parameter is false, function will return true if no players above ceiling

View File

@ -22,6 +22,18 @@ spencershepard (GRIMM):
-damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed -damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed
-added options table to allow easy adjustments before release -added options table to allow easy adjustments before release
-general refactoring and restructure -general refactoring and restructure
31 December 2021
spencershepard (GRIMM):
-added many new weapons
-added filter for weapons.shells events
-fixed mission weapon message option
-changed default for damage_model option
16 April 2022
spencershepard (GRIMM):
added new/missing weapons to explTable
added new option rocket_multiplier
--]] --]]
----[[ ##### SCRIPT CONFIGURATION ##### ]]---- ----[[ ##### SCRIPT CONFIGURATION ##### ]]----
@ -40,6 +52,7 @@ splash_damage_options = {
["infantry_cant_fire_health"] = 90, --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury ["infantry_cant_fire_health"] = 90, --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury
["debug"] = false, --enable debugging messages ["debug"] = false, --enable debugging messages
["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable ["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable
["rocket_multiplier"] = 1.3, --multiplied by the explTable value for rockets
} }
local script_enable = 1 local script_enable = 1
@ -156,8 +169,14 @@ explTable = {
["AB_250_2_SD_2"] = 100, --("AB 250-2 - 144 x SD-2, 250kg CBU with HE submunitions") ["AB_250_2_SD_2"] = 100, --("AB 250-2 - 144 x SD-2, 250kg CBU with HE submunitions")
["AB_250_2_SD_10A"] = 100, --("AB 250-2 - 17 x SD-10A, 250kg CBU with 10kg Frag/HE submunitions") ["AB_250_2_SD_10A"] = 100, --("AB 250-2 - 17 x SD-10A, 250kg CBU with 10kg Frag/HE submunitions")
["AB_500_1_SD_10A"] = 213, --("AB 500-1 - 34 x SD-10A, 500kg CBU with 10kg Frag/HE submunitions") ["AB_500_1_SD_10A"] = 213, --("AB 500-1 - 34 x SD-10A, 500kg CBU with 10kg Frag/HE submunitions")
--["LTF_5B"] = 100, --("LTF 5b Aerial Torpedo") ["AGM_114K"] = 10,
--agm-65?? ["HYDRA_70_M229"] = 8,
["AGM_65D"] = 130,
["AGM_65E"] = 300,
["AGM_65F"] = 300,
["HOT3"] = 15,
["AGR_20A"] = 8,
["GBU_54_V_1B"] = 118,
} }
@ -253,9 +272,11 @@ function track_wpns()
trigger.action.explosion(impactPoint, getWeaponExplosive(wpnData.name)) trigger.action.explosion(impactPoint, getWeaponExplosive(wpnData.name))
--trigger.action.smoke(impactPoint, 0) --trigger.action.smoke(impactPoint, 0)
end end
--if wpnData.cat == Weapon.Category.ROCKET then local explosive = getWeaponExplosive(wpnData.name)
blastWave(impactPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, getWeaponExplosive(wpnData.name)) if splash_damage_options.rocket_multiplier > 0 and wpnData.cat == Weapon.Category.ROCKET then
--end explosive = explosive * splash_damage_options.rocket_multiplier
end
blastWave(impactPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosive)
tracked_weapons[wpn_id_] = nil -- remove from tracked weapons first. tracked_weapons[wpn_id_] = nil -- remove from tracked weapons first.
end end
end end

Binary file not shown.

View File

@ -1,24 +0,0 @@
## Forces Templates
The friendly/enemy forces templates available in the generator are simply .miz files in the Generator/Forces folder.
A Forces template defines the groups of ground units available, AI aircraft, liveries, and loadouts.
To create your own Forces template:
1) Create an empty mission on Caucasus
2) Add ground unit groups.
3) Save the mission in this directory.
Optional:
4) Add helicopters with "CAS" main task for attack helicopters.
5) Add helicopters with "Transport" main task for transport helicopters.
6) Add planes with "CAS" main task for attack planes.
7) Add planes with "CAP" main task for fighters.
8) Configure loadouts, liveries, and skill for aircraft.
Tips:
- The mission generator will only extract blue ground units from the template when selected from the "Blue Forces" menu, and vice versa.
- Only unit types are used from ground units. Liveries or other attributes are able to be copied.
- For aircraft, group size is currently capped at 2 units per group to help prevent issues with parking. Only the first unit in the group is used as a source.

Binary file not shown.

View File

@ -1,20 +0,0 @@
## Imports
A breakthrough feature of mission design with RotorOps is the ability to import complex arrangements of statics and vehicles. This allows you to create reusable mission assets like bases, FARPs, objective sites, or just about anything you can imagine building in the mission editor. You can place these on any map, at your desired point, rotation and coalition!
A selection of FOBs, FARPs, and other objects are available in the Imports folder. Included are multiplayer FOBs with up to 16 helicopter spawns! A guide to the included templates is available here: [RotorOps IMPORT Assets](http://dcs-helicopters.com/wp-content/uploads/2022/03/RotorOps_IMPORT_TEMPLATES-1.pdf)
To use an import template:
1) Place a static mark flag in the scenario template mission.
2) Change the group name to 'IMPORT-filename', where the filename is a .miz file in the Generator/Imports folder.
3) Change the flag coalition to CJTF Blue/Red or UN Peacekeepers.
4) Change the flag heading and position as desired.
5) Change the flag UNIT NAME to something relevant (ie. 'North Base')
6) For multiple imports of the same template, the import object GROUP NAME should end with '-01' etc
To create a new import template:
1) Make an empty mission on Caucasus.
2) Place units/objects on the map.
3) Make one unit group name: 'ANCHOR' This will represent the point of insertion/rotation in the target mission.
4) Save the template .miz file in Generator/Imports

Binary file not shown.

Binary file not shown.