Feature/generator (#13)

* Create README.md

* Update README.md

* Update README.md

* Update README.md

* ..

* ..

* ..

* ..

* ..

* ..

* ..

* added many ui options

* stable

* release candidate
This commit is contained in:
spencershepard 2022-01-30 23:36:14 -08:00 committed by GitHub
parent 07c4afa947
commit 8cda007768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2321 additions and 8743 deletions

7
.gitignore vendored
View File

@ -1,3 +1,10 @@
.project
sound/desktop.ini
Generator/.idea
Generator/__pycache__
Generator/build
Generator/venv
Generator/Output
Generator/dist

View File

@ -0,0 +1,9 @@
You can add your own unit templates in this directory and they will appear in the mission generator.
1) Create an empty mission on Caucasus
2) Add ground unit groups.
3) Save the mission in this directory.
Tips:
-Drop your templates in the RotorOps Discord if you'd like to have them added in a release for everyone.
-The mission generator will only extract blue ground units from the template when selected from the "Blue Forces" menu, and vice versa.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,206 @@
import math
import sys
import os
import dcs
import RotorOpsMission as ROps
import RotorOpsUtils
import RotorOpsUnits
from PyQt5.QtWidgets import (
QApplication, QDialog, QMainWindow, QMessageBox
)
from PyQt5 import QtGui
from MissionGeneratorUI import Ui_MainWindow
scenarios = []
red_forces_files = []
blue_forces_files = []
class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
print('running in a PyInstaller bundle')
home_dir = os.getcwd()
os.chdir(home_dir + "/Generator")
else:
print('running in a normal Python process')
self.m = ROps.RotorOpsMission()
self.setupUi(self)
self.connectSignalsSlots()
self.populateScenarios()
self.populateForces("red", self.redforces_comboBox, red_forces_files)
self.populateForces("blue", self.blueforces_comboBox, blue_forces_files)
self.populateSlotSelection()
self.background_label.setPixmap(QtGui.QPixmap(self.m.assets_dir + "/background.PNG"))
self.statusbar.setStyleSheet(
"QStatusBar{padding-left:5px;color:black;font-weight:bold;}")
def connectSignalsSlots(self):
# self.action_Exit.triggered.connect(self.close)
self.action_generateMission.triggered.connect(self.generateMissionAction)
self.action_scenarioSelected.triggered.connect(self.scenarioChanged)
def populateScenarios(self):
os.chdir(self.m.scenarios_dir)
path = os.getcwd()
dir_list = os.listdir(path)
print("Looking for mission files in '", path, "' :")
for filename in dir_list:
if filename.endswith(".miz"):
scenarios.append(filename)
self.scenario_comboBox.addItem(filename.removesuffix('.miz'))
def populateForces(self, side, combobox, files_list):
os.chdir(self.m.home_dir)
os.chdir(self.m.forces_dir + "/" + side)
path = os.getcwd()
dir_list = os.listdir(path)
print("Looking for " + side + " Forces files in '", os.getcwd(), "' :")
for filename in dir_list:
if filename.endswith(".miz"):
files_list.append(filename)
combobox.addItem(filename.removesuffix('.miz'))
def populateSlotSelection(self):
self.slot_template_comboBox.addItem("Multiple Slots")
for type in RotorOpsUnits.client_helos:
self.slot_template_comboBox.addItem(type.id)
def scenarioChanged(self):
try:
os.chdir(self.m.scenarios_dir)
filename = scenarios[self.scenario_comboBox.currentIndex()]
source_mission = dcs.mission.Mission()
source_mission.load_file(filename)
zones = source_mission.triggers.zones()
conflict_zones = 0
staging_zones = 0
conflict_zone_size_sum = 0
conflict_zone_distance_sum = 0
spawn_zones = 0
conflict_zone_positions = []
#friendly_airports = source_mission.getCoalitionAirports("blue")
#enemy_airports = source_mission.getCoalitionAirports("red")
friendly_airports = True
enemy_airports = True
## TODO: we should be creating a new instance of RotorOpsMission each time scenario is changed so we can access all methods and vars
for zone in zones:
if zone.name == "STAGING":
staging_zones += 1
if zone.name == "ALPHA" or zone.name == "BRAVO" or zone.name == "CHARLIE" or zone.name == "DELTA":
conflict_zones += 1
conflict_zone_size_sum += zone.radius
conflict_zone_positions.append(zone.position)
if zone.name.rfind("_SPAWN") > 0:
spawn_zones += 1
if conflict_zones > 1:
for index, position in enumerate(conflict_zone_positions):
if index > 0:
conflict_zone_distance_sum += RotorOpsUtils.getDistance(conflict_zone_positions[index], conflict_zone_positions[index - 1])
def validateTemplate():
valid = True
if len(staging_zones) < 1:
valid = False
if len(conflict_zones) < 1:
valid = False
if not friendly_airports:
valid = False
if not enemy_airports:
valid = False
return valid
if conflict_zones and staging_zones :
average_zone_size = conflict_zone_size_sum / conflict_zones
self.description_textBrowser.setText(
"Map: " + source_mission.terrain.name + "\n" +
"Conflict Zones: " + str(conflict_zones) + "\n" +
"Average Zone Size " + str(math.floor(average_zone_size)) + "m \n" +
"Infantry Spawn Zones: " + str(spawn_zones) + "\n" +
"Approx Distance: " + str(math.floor(RotorOpsUtils.convertMeterToNM(conflict_zone_distance_sum))) + "nm \n"
#"Validity Check:" + str(validateTemplate())
)
except:
self.description_textBrowser.setText("File error occured.")
def generateMissionAction(self):
red_forces_filename = red_forces_files[self.redforces_comboBox.currentIndex()]
blue_forces_filename = blue_forces_files[self.blueforces_comboBox.currentIndex()]
scenario_filename = scenarios[self.scenario_comboBox.currentIndex()]
data = {
"scenario_filename": scenario_filename,
"red_forces_filename": red_forces_filename,
"blue_forces_filename": blue_forces_filename,
"red_quantity": self.redqty_spinBox.value(),
"blue_quantity": self.blueqty_spinBox.value(),
"inf_spawn_qty": self.inf_spawn_spinBox.value(),
"apc_spawns_inf": self.apcs_spawn_checkBox.isChecked(),
"e_transport": self.enemy_transport_checkBox.isChecked(),
"e_attack_helos": self.enemy_attack_helos_checkBox.isChecked(),
"e_fighters": self.enemy_fighters_checkBox.isChecked(),
"e_attack_planes": self.enemy_attack_planes_checkBox.isChecked(),
"crates": self.logistics_crates_checkBox.isChecked(),
"f_awacs": self.awacs_checkBox.isChecked(),
"f_tankers": self.tankers_checkBox.isChecked(),
"smoke_zone": self.smoke_checkBox.isChecked(),
"voiceovers": self.voiceovers_checkBox.isChecked(),
"force_offroad": self.force_offroad_checkBox.isChecked(),
"game_display": self.game_status_checkBox.isChecked(),
"defending": self.defense_checkBox.isChecked(),
"slots": self.slot_template_comboBox.currentText(),
}
os.chdir(self.m.home_dir + '/Generator')
n = ROps.RotorOpsMission()
result = n.generateMission(data)
print("Generating mission with options:")
print(str(data))
# generate the mission
#result = self.m.generateMission(data)
#display results
if result["success"]:
print(result["filename"] + "' successfully generated in " + result["directory"])
self.statusbar.showMessage(result["filename"] + "' successfully generated in " + result["directory"], 10000)
msg = QMessageBox()
msg.setWindowTitle("Mission Generated")
msg.setText("Awesome, your mission is ready! It's located in this directory: \n" +
self.m.output_dir + "\n" +
"\n" +
"Next, you should use the DCS Mission Editor to fine tune unit placements. Don't be afraid to edit the missions that this generator produces. \n" +
"\n" +
"There are no hidden script changes, everything is visible in the ME. Triggers have been created to help you to add your own actions based on active zone and game status. \n" +
"\n" +
"Units can be changed or moved without issue. Player slots can be changed or moved without issue. \n" +
"\n" +
"Don't forget, you can also create your own templates that can include any mission options, objects, or even scripts. \n" +
"\n" +
"Have fun! \n"
)
x = msg.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

View File

@ -0,0 +1,41 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['MissionGenerator.py'],
pathex=['../'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='MissionGenerator',
icon='assets\\icon.ico',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )

View File

@ -0,0 +1,365 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MissionGeneratorUI.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1140, 826)
font = QtGui.QFont()
font.setPointSize(10)
MainWindow.setFont(font)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("assets/icon.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
MainWindow.setWindowIcon(icon)
MainWindow.setWindowOpacity(4.0)
MainWindow.setAutoFillBackground(False)
MainWindow.setStyleSheet("background-color: white;")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.scenario_comboBox = QtWidgets.QComboBox(self.centralwidget)
self.scenario_comboBox.setGeometry(QtCore.QRect(270, 40, 361, 31))
self.scenario_comboBox.setToolTip("")
self.scenario_comboBox.setToolTipDuration(-1)
self.scenario_comboBox.setWhatsThis("")
self.scenario_comboBox.setObjectName("scenario_comboBox")
self.scenario_label = QtWidgets.QLabel(self.centralwidget)
self.scenario_label.setGeometry(QtCore.QRect(60, 30, 181, 41))
font = QtGui.QFont()
font.setPointSize(12)
self.scenario_label.setFont(font)
self.scenario_label.setObjectName("scenario_label")
self.generateButton = QtWidgets.QPushButton(self.centralwidget)
self.generateButton.setGeometry(QtCore.QRect(940, 720, 141, 41))
self.generateButton.setStyleSheet("background-color: white;\n"
"border-style: outset;\n"
"border-width: 2px;\n"
"border-radius: 15px;\n"
"border-color: black;\n"
"padding: 4px;")
self.generateButton.setObjectName("generateButton")
self.description_textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.description_textBrowser.setGeometry(QtCore.QRect(710, 20, 331, 131))
font = QtGui.QFont()
font.setPointSize(9)
self.description_textBrowser.setFont(font)
self.description_textBrowser.setStyleSheet("border-radius: 5px; color: gray")
self.description_textBrowser.setObjectName("description_textBrowser")
self.blueforces_comboBox = QtWidgets.QComboBox(self.centralwidget)
self.blueforces_comboBox.setGeometry(QtCore.QRect(790, 230, 291, 31))
self.blueforces_comboBox.setObjectName("blueforces_comboBox")
self.scenario_label_2 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_2.setGeometry(QtCore.QRect(690, 180, 141, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.scenario_label_2.setFont(font)
self.scenario_label_2.setObjectName("scenario_label_2")
self.scenario_label_3 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_3.setGeometry(QtCore.QRect(60, 180, 141, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.scenario_label_3.setFont(font)
self.scenario_label_3.setObjectName("scenario_label_3")
self.redforces_comboBox = QtWidgets.QComboBox(self.centralwidget)
self.redforces_comboBox.setGeometry(QtCore.QRect(170, 230, 291, 31))
self.redforces_comboBox.setObjectName("redforces_comboBox")
self.background_label = QtWidgets.QLabel(self.centralwidget)
self.background_label.setGeometry(QtCore.QRect(10, 430, 801, 371))
self.background_label.setAutoFillBackground(False)
self.background_label.setStyleSheet("")
self.background_label.setText("")
self.background_label.setPixmap(QtGui.QPixmap("assets/background.PNG"))
self.background_label.setObjectName("background_label")
self.scenario_hint_label = QtWidgets.QLabel(self.centralwidget)
self.scenario_hint_label.setGeometry(QtCore.QRect(250, 80, 381, 16))
self.scenario_hint_label.setAlignment(QtCore.Qt.AlignCenter)
self.scenario_hint_label.setObjectName("scenario_hint_label")
self.forces_hint_label = QtWidgets.QLabel(self.centralwidget)
self.forces_hint_label.setGeometry(QtCore.QRect(130, 270, 381, 16))
self.forces_hint_label.setAlignment(QtCore.Qt.AlignCenter)
self.forces_hint_label.setObjectName("forces_hint_label")
self.blueqty_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.blueqty_spinBox.setGeometry(QtCore.QRect(690, 230, 71, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.blueqty_spinBox.setFont(font)
self.blueqty_spinBox.setMinimum(0)
self.blueqty_spinBox.setMaximum(50)
self.blueqty_spinBox.setProperty("value", 3)
self.blueqty_spinBox.setObjectName("blueqty_spinBox")
self.redqty_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.redqty_spinBox.setGeometry(QtCore.QRect(70, 230, 71, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.redqty_spinBox.setFont(font)
self.redqty_spinBox.setMinimum(0)
self.redqty_spinBox.setMaximum(50)
self.redqty_spinBox.setProperty("value", 2)
self.redqty_spinBox.setObjectName("redqty_spinBox")
self.scenario_label_4 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_4.setGeometry(QtCore.QRect(670, 260, 101, 31))
font = QtGui.QFont()
font.setPointSize(8)
self.scenario_label_4.setFont(font)
self.scenario_label_4.setAlignment(QtCore.Qt.AlignCenter)
self.scenario_label_4.setObjectName("scenario_label_4")
self.game_status_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.game_status_checkBox.setGeometry(QtCore.QRect(910, 510, 191, 16))
font = QtGui.QFont()
font.setPointSize(9)
self.game_status_checkBox.setFont(font)
self.game_status_checkBox.setChecked(True)
self.game_status_checkBox.setTristate(False)
self.game_status_checkBox.setObjectName("game_status_checkBox")
self.voiceovers_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.voiceovers_checkBox.setGeometry(QtCore.QRect(910, 570, 191, 16))
font = QtGui.QFont()
font.setPointSize(9)
self.voiceovers_checkBox.setFont(font)
self.voiceovers_checkBox.setChecked(True)
self.voiceovers_checkBox.setObjectName("voiceovers_checkBox")
self.logistics_crates_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.logistics_crates_checkBox.setGeometry(QtCore.QRect(910, 320, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.logistics_crates_checkBox.setFont(font)
self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox")
self.awacs_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.awacs_checkBox.setGeometry(QtCore.QRect(910, 350, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.awacs_checkBox.setFont(font)
self.awacs_checkBox.setStatusTip("")
self.awacs_checkBox.setObjectName("awacs_checkBox")
self.tankers_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.tankers_checkBox.setGeometry(QtCore.QRect(910, 380, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.tankers_checkBox.setFont(font)
self.tankers_checkBox.setObjectName("tankers_checkBox")
self.apcs_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(470, 400, 251, 31))
font = QtGui.QFont()
font.setPointSize(10)
self.apcs_spawn_checkBox.setFont(font)
self.apcs_spawn_checkBox.setObjectName("apcs_spawn_checkBox")
self.enemy_transport_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.enemy_transport_checkBox.setEnabled(False)
self.enemy_transport_checkBox.setGeometry(QtCore.QRect(70, 320, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.enemy_transport_checkBox.setFont(font)
self.enemy_transport_checkBox.setObjectName("enemy_transport_checkBox")
self.enemy_attack_helos_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.enemy_attack_helos_checkBox.setEnabled(True)
self.enemy_attack_helos_checkBox.setGeometry(QtCore.QRect(70, 350, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.enemy_attack_helos_checkBox.setFont(font)
self.enemy_attack_helos_checkBox.setObjectName("enemy_attack_helos_checkBox")
self.enemy_fighters_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.enemy_fighters_checkBox.setEnabled(False)
self.enemy_fighters_checkBox.setGeometry(QtCore.QRect(70, 380, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.enemy_fighters_checkBox.setFont(font)
self.enemy_fighters_checkBox.setObjectName("enemy_fighters_checkBox")
self.enemy_attack_planes_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.enemy_attack_planes_checkBox.setEnabled(True)
self.enemy_attack_planes_checkBox.setGeometry(QtCore.QRect(70, 410, 251, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.enemy_attack_planes_checkBox.setFont(font)
self.enemy_attack_planes_checkBox.setObjectName("enemy_attack_planes_checkBox")
self.inf_spawn_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.inf_spawn_spinBox.setGeometry(QtCore.QRect(680, 360, 71, 31))
font = QtGui.QFont()
font.setPointSize(12)
self.inf_spawn_spinBox.setFont(font)
self.inf_spawn_spinBox.setMinimum(0)
self.inf_spawn_spinBox.setMaximum(50)
self.inf_spawn_spinBox.setProperty("value", 2)
self.inf_spawn_spinBox.setObjectName("inf_spawn_spinBox")
self.smoke_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.smoke_checkBox.setGeometry(QtCore.QRect(910, 540, 191, 16))
font = QtGui.QFont()
font.setPointSize(9)
self.smoke_checkBox.setFont(font)
self.smoke_checkBox.setChecked(True)
self.smoke_checkBox.setObjectName("smoke_checkBox")
self.scenario_label_5 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_5.setGeometry(QtCore.QRect(50, 260, 101, 31))
font = QtGui.QFont()
font.setPointSize(8)
self.scenario_label_5.setFont(font)
self.scenario_label_5.setAlignment(QtCore.Qt.AlignCenter)
self.scenario_label_5.setObjectName("scenario_label_5")
self.forces_hint_label_2 = QtWidgets.QLabel(self.centralwidget)
self.forces_hint_label_2.setGeometry(QtCore.QRect(790, 270, 311, 20))
self.forces_hint_label_2.setAlignment(QtCore.Qt.AlignCenter)
self.forces_hint_label_2.setObjectName("forces_hint_label_2")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(470, 360, 191, 31))
font = QtGui.QFont()
font.setPointSize(10)
self.label.setFont(font)
self.label.setObjectName("label")
self.slot_template_comboBox = QtWidgets.QComboBox(self.centralwidget)
self.slot_template_comboBox.setGeometry(QtCore.QRect(790, 630, 291, 31))
self.slot_template_comboBox.setObjectName("slot_template_comboBox")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(650, 630, 111, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
self.scenario_label_6 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_6.setGeometry(QtCore.QRect(470, 320, 141, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.scenario_label_6.setFont(font)
self.scenario_label_6.setObjectName("scenario_label_6")
self.force_offroad_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.force_offroad_checkBox.setGeometry(QtCore.QRect(910, 480, 191, 16))
font = QtGui.QFont()
font.setPointSize(9)
self.force_offroad_checkBox.setFont(font)
self.force_offroad_checkBox.setChecked(False)
self.force_offroad_checkBox.setTristate(False)
self.force_offroad_checkBox.setObjectName("force_offroad_checkBox")
self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.defense_checkBox.setGeometry(QtCore.QRect(60, 90, 181, 31))
font = QtGui.QFont()
font.setPointSize(11)
self.defense_checkBox.setFont(font)
self.defense_checkBox.setObjectName("defense_checkBox")
self.background_label.raise_()
self.scenario_comboBox.raise_()
self.scenario_label.raise_()
self.generateButton.raise_()
self.description_textBrowser.raise_()
self.blueforces_comboBox.raise_()
self.scenario_label_2.raise_()
self.scenario_label_3.raise_()
self.redforces_comboBox.raise_()
self.scenario_hint_label.raise_()
self.forces_hint_label.raise_()
self.blueqty_spinBox.raise_()
self.redqty_spinBox.raise_()
self.scenario_label_4.raise_()
self.game_status_checkBox.raise_()
self.voiceovers_checkBox.raise_()
self.logistics_crates_checkBox.raise_()
self.awacs_checkBox.raise_()
self.tankers_checkBox.raise_()
self.apcs_spawn_checkBox.raise_()
self.enemy_transport_checkBox.raise_()
self.enemy_attack_helos_checkBox.raise_()
self.enemy_fighters_checkBox.raise_()
self.enemy_attack_planes_checkBox.raise_()
self.inf_spawn_spinBox.raise_()
self.smoke_checkBox.raise_()
self.scenario_label_5.raise_()
self.forces_hint_label_2.raise_()
self.label.raise_()
self.slot_template_comboBox.raise_()
self.label_2.raise_()
self.scenario_label_6.raise_()
self.force_offroad_checkBox.raise_()
self.defense_checkBox.raise_()
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1140, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setAcceptDrops(False)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.action_generateMission = QtWidgets.QAction(MainWindow)
self.action_generateMission.setObjectName("action_generateMission")
self.action_scenarioSelected = QtWidgets.QAction(MainWindow)
self.action_scenarioSelected.setObjectName("action_scenarioSelected")
self.action_blueforcesSelected = QtWidgets.QAction(MainWindow)
self.action_blueforcesSelected.setObjectName("action_blueforcesSelected")
self.action_redforcesSelected = QtWidgets.QAction(MainWindow)
self.action_redforcesSelected.setObjectName("action_redforcesSelected")
self.retranslateUi(MainWindow)
self.generateButton.clicked.connect(self.action_generateMission.trigger)
self.scenario_comboBox.currentIndexChanged['int'].connect(self.action_scenarioSelected.trigger)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator"))
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_label.setText(_translate("MainWindow", "Scenario Template:"))
self.generateButton.setText(_translate("MainWindow", "Generate Mission"))
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"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'MS Shell Dlg 2\'; 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-size:10pt;\">Provide close air support for our convoys as we take back Las Vegas from the enemy!</span></p></body></html>"))
self.blueforces_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own custom ground forces groups to be automatically generated."))
self.scenario_label_2.setText(_translate("MainWindow", "Friendly Forces:"))
self.scenario_label_3.setText(_translate("MainWindow", "Enemy Forces:"))
self.redforces_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own custom ground forces groups to be automatically generated."))
self.scenario_hint_label.setText(_translate("MainWindow", "Scenario templates are .miz files in \'Generator/Scenarios\'"))
self.forces_hint_label.setText(_translate("MainWindow", "Forces templates are .miz files in \'Generator/Forces\'"))
self.blueqty_spinBox.setStatusTip(_translate("MainWindow", "How many groups should we generate?"))
self.redqty_spinBox.setStatusTip(_translate("MainWindow", "How many groups should we generate?"))
self.scenario_label_4.setText(_translate("MainWindow", "Groups Per 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.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.logistics_crates_checkBox.setStatusTip(_translate("MainWindow", "Enable CTLD logistics crates for building ground units and air defenses."))
self.logistics_crates_checkBox.setText(_translate("MainWindow", "Logistics Crates"))
self.awacs_checkBox.setText(_translate("MainWindow", "Friendly AWACS"))
self.tankers_checkBox.setText(_translate("MainWindow", "Friendly Tankers"))
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", "APCs Spawn Infantry"))
self.enemy_transport_checkBox.setStatusTip(_translate("MainWindow", "Not yet implemented."))
self.enemy_transport_checkBox.setText(_translate("MainWindow", "Enemy Transport Helicopters"))
self.enemy_attack_helos_checkBox.setStatusTip(_translate("MainWindow", "Not yet implemented."))
self.enemy_attack_helos_checkBox.setText(_translate("MainWindow", "Enemy Attack Helicopters"))
self.enemy_fighters_checkBox.setStatusTip(_translate("MainWindow", "Not yet implemented."))
self.enemy_fighters_checkBox.setText(_translate("MainWindow", "Enemy Fighter Planes"))
self.enemy_attack_planes_checkBox.setStatusTip(_translate("MainWindow", "Not yet implemented."))
self.enemy_attack_planes_checkBox.setText(_translate("MainWindow", "Enemy Ground Attack Planes"))
self.inf_spawn_spinBox.setStatusTip(_translate("MainWindow", "This value is multiplied by the number of spawn zones in the mission template."))
self.smoke_checkBox.setStatusTip(_translate("MainWindow", "Not yet implemented."))
self.smoke_checkBox.setText(_translate("MainWindow", "Smoke Active Zone"))
self.scenario_label_5.setText(_translate("MainWindow", "Groups Per Zone"))
self.forces_hint_label_2.setText(_translate("MainWindow", "Forces templates are .miz files in \'Generator/Forces\'"))
self.label.setText(_translate("MainWindow", "Infantry Groups per zone:"))
self.slot_template_comboBox.setStatusTip(_translate("MainWindow", "Default player/client spawn locations at a friendly airport."))
self.label_2.setText(_translate("MainWindow", "Player Slots"))
self.scenario_label_6.setText(_translate("MainWindow", "Infantry Spawns:"))
self.force_offroad_checkBox.setStatusTip(_translate("MainWindow", "May help prevent long travel times or pathfinding issues. Tip: You can change this dynamically from mission triggers."))
self.force_offroad_checkBox.setText(_translate("MainWindow", "Force Offroad"))
self.defense_checkBox.setText(_translate("MainWindow", "Defensive Mode"))
self.action_generateMission.setText(_translate("MainWindow", "_generateMission"))
self.action_scenarioSelected.setText(_translate("MainWindow", "_scenarioSelected"))
self.action_blueforcesSelected.setText(_translate("MainWindow", "_blueforcesSelected"))
self.action_redforcesSelected.setText(_translate("MainWindow", "_redforcesSelected"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,846 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1140</width>
<height>826</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="windowTitle">
<string>RotorOps Mission Generator</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>assets/icon.ico</normaloff>assets/icon.ico</iconset>
</property>
<property name="windowOpacity">
<double>4.000000000000000</double>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: white;</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QComboBox" name="scenario_comboBox">
<property name="geometry">
<rect>
<x>270</x>
<y>40</y>
<width>361</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
<string/>
</property>
<property name="toolTipDuration">
<number>-1</number>
</property>
<property name="statusTip">
<string>Tip: You can create your own templates that include mission options like kneeboards, briefings, weather, static units, triggers, scripts, etc.</string>
</property>
<property name="whatsThis">
<string/>
</property>
</widget>
<widget class="QLabel" name="scenario_label">
<property name="geometry">
<rect>
<x>60</x>
<y>30</y>
<width>181</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Scenario Template:</string>
</property>
</widget>
<widget class="QPushButton" name="generateButton">
<property name="geometry">
<rect>
<x>940</x>
<y>720</y>
<width>141</width>
<height>41</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background-color: white;
border-style: outset;
border-width: 2px;
border-radius: 15px;
border-color: black;
padding: 4px;</string>
</property>
<property name="text">
<string>Generate Mission</string>
</property>
</widget>
<widget class="QTextBrowser" name="description_textBrowser">
<property name="geometry">
<rect>
<x>710</x>
<y>20</y>
<width>331</width>
<height>131</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">border-radius: 5px; color: gray</string>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Provide close air support for our convoys as we take back Las Vegas from the enemy!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QComboBox" name="blueforces_comboBox">
<property name="geometry">
<rect>
<x>790</x>
<y>230</y>
<width>291</width>
<height>31</height>
</rect>
</property>
<property name="statusTip">
<string>Tip: You can create your own custom ground forces groups to be automatically generated.</string>
</property>
</widget>
<widget class="QLabel" name="scenario_label_2">
<property name="geometry">
<rect>
<x>690</x>
<y>180</y>
<width>141</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Friendly Forces:</string>
</property>
</widget>
<widget class="QLabel" name="scenario_label_3">
<property name="geometry">
<rect>
<x>60</x>
<y>180</y>
<width>141</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Enemy Forces:</string>
</property>
</widget>
<widget class="QComboBox" name="redforces_comboBox">
<property name="geometry">
<rect>
<x>170</x>
<y>230</y>
<width>291</width>
<height>31</height>
</rect>
</property>
<property name="statusTip">
<string>Tip: You can create your own custom ground forces groups to be automatically generated.</string>
</property>
</widget>
<widget class="QLabel" name="background_label">
<property name="geometry">
<rect>
<x>10</x>
<y>430</y>
<width>801</width>
<height>371</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>assets/background.PNG</pixmap>
</property>
</widget>
<widget class="QLabel" name="scenario_hint_label">
<property name="geometry">
<rect>
<x>250</x>
<y>80</y>
<width>381</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Scenario templates are .miz files in 'Generator/Scenarios'</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="forces_hint_label">
<property name="geometry">
<rect>
<x>130</x>
<y>270</y>
<width>381</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Forces templates are .miz files in 'Generator/Forces'</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QSpinBox" name="blueqty_spinBox">
<property name="geometry">
<rect>
<x>690</x>
<y>230</y>
<width>71</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="statusTip">
<string>How many groups should we generate?</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
<widget class="QSpinBox" name="redqty_spinBox">
<property name="geometry">
<rect>
<x>70</x>
<y>230</y>
<width>71</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="statusTip">
<string>How many groups should we generate?</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
<widget class="QLabel" name="scenario_label_4">
<property name="geometry">
<rect>
<x>670</x>
<y>260</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Groups Per Zone</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QCheckBox" name="game_status_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>510</y>
<width>191</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>Enable an onscreen zone status display. This helps keep focus on the active conflict zone.</string>
</property>
<property name="text">
<string>Game Status Display</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
<widget class="QCheckBox" name="voiceovers_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>570</y>
<width>191</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>Voiceovers from the ground commander. Helps keep focus on the active zone.</string>
</property>
<property name="text">
<string>Voiceovers</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="logistics_crates_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>320</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string>Enable CTLD logistics crates for building ground units and air defenses.</string>
</property>
<property name="text">
<string>Logistics Crates</string>
</property>
</widget>
<widget class="QCheckBox" name="awacs_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>350</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string/>
</property>
<property name="text">
<string>Friendly AWACS</string>
</property>
</widget>
<widget class="QCheckBox" name="tankers_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>380</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Friendly Tankers</string>
</property>
</widget>
<widget class="QCheckBox" name="apcs_spawn_checkBox">
<property name="geometry">
<rect>
<x>470</x>
<y>400</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="statusTip">
<string>Friendly/enemy APCs will drop infantry when reaching a new conflict zone.</string>
</property>
<property name="text">
<string>APCs Spawn Infantry</string>
</property>
</widget>
<widget class="QCheckBox" name="enemy_transport_checkBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>70</x>
<y>320</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string>Not yet implemented.</string>
</property>
<property name="text">
<string>Enemy Transport Helicopters</string>
</property>
</widget>
<widget class="QCheckBox" name="enemy_attack_helos_checkBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>70</x>
<y>350</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string>Not yet implemented.</string>
</property>
<property name="text">
<string>Enemy Attack Helicopters</string>
</property>
</widget>
<widget class="QCheckBox" name="enemy_fighters_checkBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>70</x>
<y>380</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string>Not yet implemented.</string>
</property>
<property name="text">
<string>Enemy Fighter Planes</string>
</property>
</widget>
<widget class="QCheckBox" name="enemy_attack_planes_checkBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>70</x>
<y>410</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="statusTip">
<string>Not yet implemented.</string>
</property>
<property name="text">
<string>Enemy Ground Attack Planes</string>
</property>
</widget>
<widget class="QSpinBox" name="inf_spawn_spinBox">
<property name="geometry">
<rect>
<x>680</x>
<y>360</y>
<width>71</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="statusTip">
<string>This value is multiplied by the number of spawn zones in the mission template.</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
<widget class="QCheckBox" name="smoke_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>540</y>
<width>191</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>Not yet implemented.</string>
</property>
<property name="text">
<string>Smoke Active Zone</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="scenario_label_5">
<property name="geometry">
<rect>
<x>50</x>
<y>260</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Groups Per Zone</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="forces_hint_label_2">
<property name="geometry">
<rect>
<x>790</x>
<y>270</y>
<width>311</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Forces templates are .miz files in 'Generator/Forces'</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>470</x>
<y>360</y>
<width>191</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Infantry Groups per zone:</string>
</property>
</widget>
<widget class="QComboBox" name="slot_template_comboBox">
<property name="geometry">
<rect>
<x>790</x>
<y>630</y>
<width>291</width>
<height>31</height>
</rect>
</property>
<property name="statusTip">
<string>Default player/client spawn locations at a friendly airport.</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>650</x>
<y>630</y>
<width>111</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Player Slots</string>
</property>
</widget>
<widget class="QLabel" name="scenario_label_6">
<property name="geometry">
<rect>
<x>470</x>
<y>320</y>
<width>141</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Infantry Spawns:</string>
</property>
</widget>
<widget class="QCheckBox" name="force_offroad_checkBox">
<property name="geometry">
<rect>
<x>910</x>
<y>480</y>
<width>191</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>May help prevent long travel times or pathfinding issues. Tip: You can change this dynamically from mission triggers.</string>
</property>
<property name="text">
<string>Force Offroad</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
<widget class="QCheckBox" name="defense_checkBox">
<property name="geometry">
<rect>
<x>60</x>
<y>90</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Defensive Mode</string>
</property>
</widget>
<zorder>background_label</zorder>
<zorder>scenario_comboBox</zorder>
<zorder>scenario_label</zorder>
<zorder>generateButton</zorder>
<zorder>description_textBrowser</zorder>
<zorder>blueforces_comboBox</zorder>
<zorder>scenario_label_2</zorder>
<zorder>scenario_label_3</zorder>
<zorder>redforces_comboBox</zorder>
<zorder>scenario_hint_label</zorder>
<zorder>forces_hint_label</zorder>
<zorder>blueqty_spinBox</zorder>
<zorder>redqty_spinBox</zorder>
<zorder>scenario_label_4</zorder>
<zorder>game_status_checkBox</zorder>
<zorder>voiceovers_checkBox</zorder>
<zorder>logistics_crates_checkBox</zorder>
<zorder>awacs_checkBox</zorder>
<zorder>tankers_checkBox</zorder>
<zorder>apcs_spawn_checkBox</zorder>
<zorder>enemy_transport_checkBox</zorder>
<zorder>enemy_attack_helos_checkBox</zorder>
<zorder>enemy_fighters_checkBox</zorder>
<zorder>enemy_attack_planes_checkBox</zorder>
<zorder>inf_spawn_spinBox</zorder>
<zorder>smoke_checkBox</zorder>
<zorder>scenario_label_5</zorder>
<zorder>forces_hint_label_2</zorder>
<zorder>label</zorder>
<zorder>slot_template_comboBox</zorder>
<zorder>label_2</zorder>
<zorder>scenario_label_6</zorder>
<zorder>force_offroad_checkBox</zorder>
<zorder>defense_checkBox</zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1140</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="acceptDrops">
<bool>false</bool>
</property>
</widget>
<action name="action_generateMission">
<property name="text">
<string>_generateMission</string>
</property>
</action>
<action name="action_scenarioSelected">
<property name="text">
<string>_scenarioSelected</string>
</property>
</action>
<action name="action_blueforcesSelected">
<property name="text">
<string>_blueforcesSelected</string>
</property>
</action>
<action name="action_redforcesSelected">
<property name="text">
<string>_redforcesSelected</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>generateButton</sender>
<signal>clicked()</signal>
<receiver>action_generateMission</receiver>
<slot>trigger()</slot>
<hints>
<hint type="sourcelabel">
<x>993</x>
<y>591</y>
</hint>
<hint type="destinationlabel">
<x>589</x>
<y>409</y>
</hint>
</hints>
</connection>
<connection>
<sender>scenario_comboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>action_scenarioSelected</receiver>
<slot>trigger()</slot>
<hints>
<hint type="sourcelabel">
<x>285</x>
<y>71</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,406 @@
from tokenize import String
import dcs
import os
import random
import RotorOpsUnits
import time
class RotorOpsMission:
conflict_zones = {}
staging_zones = {}
spawn_zones = {}
scripts = {}
def __init__(self):
self.m = dcs.mission.Mission()
self.old_blue = self.m.coalition.get("blue").dict()
self.old_red = self.m.coalition.get("red").dict()
os.chdir("../")
self.home_dir = os.getcwd()
self.scenarios_dir = self.home_dir + "\Generator\Scenarios"
self.forces_dir = self.home_dir + "\Generator\Forces"
self.script_directory = self.home_dir
self.sound_directory = self.home_dir + "\sound\embedded"
self.output_dir = self.home_dir + "\Generator\Output"
self.assets_dir = self.home_dir + "\Generator/assets"
class RotorOpsZone:
def __init__(self, name: str, flag: int, position: dcs.point, size: int):
self.name = name
self.flag = flag
self.position = position
self.size = size
def getMission(self):
return self.m
def addZone(self, zone_dict, zone: RotorOpsZone):
zone_dict[zone.name] = zone
def addResources(self, sound_directory, script_directory):
# add all of our required sounds
os.chdir(sound_directory)
path = os.getcwd()
dir_list = os.listdir(path)
# print("Files and directories in '", path, "' :")
# print(dir_list)
for filename in dir_list:
if filename.endswith(".ogg"):
#print(filename)
self.m.map_resource.add_resource_file(filename)
#add all of our lua scripts
os.chdir(script_directory)
path = os.getcwd()
dir_list = os.listdir(path)
# print("Files and directories in '", path, "' :")
# print(dir_list)
for filename in dir_list:
if filename.endswith(".lua"):
print("Adding script to mission: " + filename)
self.scripts[filename] = self.m.map_resource.add_resource_file(filename)
def getUnitsFromMiz(self, filename, side):
forces = []
os.chdir(self.home_dir)
os.chdir(self.forces_dir + "/" + side)
print("Looking for " + side + " Forces files in '", os.getcwd(), "' :")
source_mission = dcs.mission.Mission()
try:
source_mission.load_file(filename)
for country_name in source_mission.coalition.get(side).countries:
country_obj = source_mission.coalition.get(side).countries[country_name]
for vehicle_group in country_obj.vehicle_group:
forces.append(vehicle_group)
return forces
except:
print("Failed to load units from " + filename)
def generateMission(self, options):
#get the template mission file
os.chdir(self.scenarios_dir)
print("Looking for mission files in '", os.getcwd(), "' :")
self.m.load_file(options["scenario_filename"])
#Load the default coalitions for simplicity
self.m.coalition.get("blue").load_from_dict(self.m, self.old_blue)
self.m.coalition.get("red").load_from_dict(self.m, self.old_red)
red_forces = self.getUnitsFromMiz(options["red_forces_filename"], "red")
blue_forces = self.getUnitsFromMiz(options["blue_forces_filename"], "blue")
# add zones to target mission
for zone in self.m.triggers.zones():
if zone.name == "ALPHA":
self.addZone(self.conflict_zones, self.RotorOpsZone("ALPHA", 101, zone.position, zone.radius))
elif zone.name == "BRAVO":
self.addZone(self.conflict_zones, self.RotorOpsZone("BRAVO", 102, zone.position, zone.radius))
elif zone.name == "CHARLIE":
self.addZone(self.conflict_zones, self.RotorOpsZone("CHARLIE", 103, zone.position, zone.radius))
elif zone.name == "DELTA":
self.addZone(self.conflict_zones, self.RotorOpsZone("DELTA", 104, zone.position, zone.radius))
elif zone.name.rfind("STAGING") >= 0:
self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
elif zone.name.rfind("SPAWN") >= 0:
self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
#add files and triggers necessary for RotorOps.lua script
self.addResources(self.sound_directory, self.script_directory)
self.scriptTriggerSetup(options)
blue_zones = self.staging_zones
red_zones = self.conflict_zones
if options["defending"]:
blue_zones = self.conflict_zones
red_zones = self.staging_zones
#swap airport sides
blue_airports = self.getCoalitionAirports("blue")
red_airports = self.getCoalitionAirports("red")
for airport_name in blue_airports:
self.m.terrain.airports[airport_name].set_red()
for airport_name in red_airports:
self.m.terrain.airports[airport_name].set_blue()
#Add red ground units
for zone_name in red_zones:
if red_forces:
self.addGroundGroups(red_zones[zone_name], self.m.country('Russia'), red_forces, options["red_quantity"])
#Add blue ground units
for zone_name in blue_zones:
if blue_forces:
self.addGroundGroups(blue_zones[zone_name], self.m.country('USA'), blue_forces,
options["blue_quantity"])
#Add player slots
if options["slots"] == "Multiple Slots":
self.addMultiplayerHelos()
else:
for helicopter in dcs.helicopters.helicopter_map:
if helicopter == options["slots"]:
self.addSinglePlayerHelos(dcs.helicopters.helicopter_map[helicopter])
self.addFlights(options)
#Set the Editor Map View
self.m.map.position = self.m.terrain.airports[self.getCoalitionAirports("blue")[0]].position
self.m.map.zoom = 100000
#Save the mission file
print(self.m.triggers.zones())
os.chdir(self.output_dir)
output_filename = options["scenario_filename"].removesuffix('.miz') + " " + time.strftime('%a%H%M%S') + '.miz'
success = self.m.save(output_filename)
return {"success": success, "filename": output_filename, "directory": self.output_dir} #let the UI know the result
def addGroundGroups(self, zone, _country, groups, quantity):
for a in range(0, quantity):
group = random.choice(groups)
unit_types = []
for unit in group.units:
if dcs.vehicles.vehicle_map[unit.type]:
unit_types.append(dcs.vehicles.vehicle_map[unit.type])
country = self.m.country(_country.name)
pos1 = zone.position.point_from_heading(5, 500)
#for i in range(0, quantity):
self.m.vehicle_group_platoon(
country,
zone.name + '-GND ' + str(a+1),
unit_types,
pos1.random_point_within(zone.size / 2, 500),
heading=random.randint(0, 359),
formation=dcs.unitgroup.VehicleGroup.Formation.Scattered,
)
def getCoalitionAirports(self, side: str):
coalition_airports = []
for airport_name in self.m.terrain.airports:
airportobj = self.m.terrain.airports[airport_name]
if airportobj.coalition == str.upper(side):
coalition_airports.append(airport_name)
return coalition_airports
def addSinglePlayerHelos(self, helotype):
friendly_airports = self.getCoalitionAirports("blue")
for airport_name in friendly_airports:
fg = self.m.flight_group_from_airport(self.m.country('USA'), "Player Helos", helotype,
self.m.terrain.airports[airport_name], group_size=2)
fg.units[0].set_player()
def addMultiplayerHelos(self):
friendly_airports = self.getCoalitionAirports("blue")
for airport_name in friendly_airports:
for helotype in RotorOpsUnits.client_helos:
fg = self.m.flight_group_from_airport(self.m.country('USA'), airport_name + " " + helotype.id, helotype,
self.m.terrain.airports[airport_name], group_size=1)
fg.units[0].set_client()
class TrainingScenario():
@staticmethod
def random_orbit(rect: dcs.mapping.Rectangle):
x1 = random.randrange(int(rect.bottom), int(rect.top))
sy = rect.left
y1 = random.randrange(int(sy), int(rect.right))
heading = 90 if y1 < (sy + (rect.right - sy) / 2) else 270
heading = random.randrange(heading - 20, heading + 20)
race_dist = random.randrange(80 * 1000, 120 * 1000)
return dcs.mapping.Point(x1, y1), heading, race_dist
def addFlights(self, options):
usa = self.m.country(dcs.countries.USA.name)
russia = self.m.country(dcs.countries.Russia.name)
friendly_airport = self.m.terrain.airports[self.getCoalitionAirports("blue")[0]]
enemy_airport = self.m.terrain.airports[self.getCoalitionAirports("red")[0]]
orbit_rect = dcs.mapping.Rectangle(
int(friendly_airport.position.x), int(friendly_airport.position.y - 100 * 1000), int(friendly_airport.position.x - 100 * 1000),
int(friendly_airport.position.y))
if options["f_awacs"]:
awacs_name = "AWACS"
awacs_freq = 266
pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
awacs = self.m.awacs_flight(
usa,
awacs_name,
plane_type=dcs.planes.E_3A,
airport=None,
position=pos,
race_distance=race_dist, heading=heading,
altitude=random.randrange(4000, 5500, 100), frequency=awacs_freq)
awacs_escort = self.m.escort_flight(usa, "AWACS Escort", dcs.countries.USA.Plane.F_15C, None, awacs, group_size=2)
awacs_escort.load_loadout("Combat Air Patrol") #not working for f-15
briefing = self.m.description_text() + "\n\n" + awacs_name + " " + str(awacs_freq) + ".00 " + "\n"
self.m.set_description_text(briefing)
if options["f_tankers"]:
t1_name = "Tanker KC_130 Basket"
t1_freq = 253
t1_tac = "61Y"
t2_name = "Tanker KC_135 Boom"
t2_freq = 256
t2_tac = "101Y"
pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
refuel_net = self.m.refuel_flight(
usa,
t1_name,
dcs.planes.KC130,
airport=None,
position=pos,
race_distance=race_dist, heading=heading,
altitude=random.randrange(4000, 5500, 100), speed=750, frequency=t1_freq, tacanchannel=t1_tac)
pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
refuel_rod = self.m.refuel_flight(
usa,
t2_name,
dcs.planes.KC_135,
airport=None,
position=pos,
race_distance=race_dist, heading=heading,
altitude=random.randrange(4000, 5500, 100), frequency=t2_freq, tacanchannel=t2_tac)
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"
self.m.set_description_text(briefing)
def zone_attack(fg, unit_type):
fg.set_skill(dcs.unit.Skill.Random)
fg.late_activation = True
#fg.load_loadout(unit_type["loadout"])
#task = dcs.task.CAS
#loadout = dcs.planes.Su_25.loadout(task)
#loadout = dcs.planes.Su_25.loadout_by_name("Ground Attack")
#fg.load_task_default_loadout(task)
#fg.load_loadout("Ground Attack")
#fg.load_task_default_loadout(dcs.task.GroundAttack)
#fg.load_loadout("2xB-13L+4xATGM 9M114")
if options["defending"]:
for zone_name in self.conflict_zones:
fg.add_waypoint(self.conflict_zones[zone_name].position, 1000)
else:
for zone_name in reversed(self.conflict_zones):
fg.add_waypoint(self.conflict_zones[zone_name].position, 1000)
fg.add_runway_waypoint(enemy_airport)
fg.land_at(enemy_airport)
if options["e_attack_helos"]:
helo = random.choice(RotorOpsUnits.e_attack_helos)
afg = self.m.flight_group_from_airport(
russia,
"Enemy Attack Helicopters",
helo,
airport=enemy_airport,
maintask=dcs.task.GroundAttack,
start_type=dcs.mission.StartType.Warm,
group_size=2)
zone_attack(afg, helo)
if options["e_attack_planes"]:
plane = random.choice(RotorOpsUnits.e_attack_planes)
afg = self.m.flight_group_from_airport(
russia, "Enemy Attack Planes", plane["type"],
airport=enemy_airport,
maintask=dcs.task.GroundAttack,
start_type=dcs.mission.StartType.Warm,
group_size=2)
zone_attack(afg, plane)
def scriptTriggerSetup(self, options):
#get the boolean value from ui option and convert to lua string
def lb(var):
return str(options[var]).lower()
game_flag = 100
#Add the first trigger
mytrig = dcs.triggers.TriggerOnce(comment="RotorOps Setup Scripts")
mytrig.rules.append(dcs.condition.TimeAfter(1))
mytrig.actions.append(dcs.action.DoScriptFile(self.scripts["mist_4_4_90.lua"]))
mytrig.actions.append(dcs.action.DoScriptFile(self.scripts["Splash_Damage_2_0.lua"]))
mytrig.actions.append(dcs.action.DoScriptFile(self.scripts["CTLD.lua"]))
mytrig.actions.append(dcs.action.DoScriptFile(self.scripts["RotorOps.lua"]))
mytrig.actions.append(dcs.action.DoScript(dcs.action.String((
"--OPTIONS HERE!\n\n" +
"RotorOps.CTLD_crates = " + lb("crates") + "\n\n" +
"RotorOps.CTLD_sound_effects = true\n\n" +
"RotorOps.force_offroad = " + lb("force_offroad") + "\n\n" +
"RotorOps.voice_overs = " + lb("voiceovers") + "\n\n" +
"RotorOps.zone_status_display = " + lb("game_display") + "\n\n" +
"RotorOps.inf_spawns_per_zone = " + lb("inf_spawn_qty") + "\n\n" +
"RotorOps.apcs_spawn_infantry = " + lb("apc_spawns_inf") + " \n\n"))))
self.m.triggerrules.triggers.append(mytrig)
#Add the second trigger
mytrig = dcs.triggers.TriggerOnce(comment="RotorOps Setup Zones")
mytrig.rules.append(dcs.condition.TimeAfter(2))
for s_zone in self.staging_zones:
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.stagingZone('" + s_zone + "')")))
for c_zone in self.conflict_zones:
zone_flag = self.conflict_zones[c_zone].flag
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.addZone('" + c_zone + "'," + str(zone_flag) + ")")))
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.setupConflict('" + str(game_flag) + "')")))
self.m.triggerrules.triggers.append(mytrig)
#Add the third trigger
mytrig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict Start")
mytrig.rules.append(dcs.condition.TimeAfter(10))
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)")))
self.m.triggerrules.triggers.append(mytrig)
#Add all zone-based triggers
for index, c_zone in enumerate(self.conflict_zones):
z_active_trig = dcs.triggers.TriggerOnce(comment= c_zone + " Active")
z_active_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
z_active_trig.actions.append(dcs.action.DoScript(dcs.action.String("--Add any action you want here!")))
self.m.triggerrules.triggers.append(z_active_trig)
zone_flag = self.conflict_zones[c_zone].flag
z_weak_trig = dcs.triggers.TriggerOnce(comment= c_zone + " Weak")
z_weak_trig.rules.append(dcs.condition.FlagIsMore(zone_flag, 10))
z_weak_trig.rules.append(dcs.condition.FlagIsLess(zone_flag, random.randrange(20, 80)))
z_weak_trig.actions.append(dcs.action.DoScript(dcs.action.String("--Add any action you want here!\n\n--Flag value represents the percentage of defending ground units remaining. ")))
if options["e_attack_helos"]:
z_weak_trig.actions.append(dcs.action.DoScript(dcs.action.String("mist.respawnGroup('Enemy Attack Helicopters', true)")))
self.m.triggerrules.triggers.append(z_weak_trig)
#Add game won/lost triggers
mytrig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON")
mytrig.rules.append(dcs.condition.FlagEquals(game_flag, 99))
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is WON")))
self.m.triggerrules.triggers.append(mytrig)
mytrig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict LOST")
mytrig.rules.append(dcs.condition.FlagEquals(game_flag, 98))
mytrig.actions.append(dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST")))
self.m.triggerrules.triggers.append(mytrig)

View File

@ -0,0 +1,26 @@
import dcs
client_helos = [
dcs.helicopters.UH_1H,
dcs.helicopters.Mi_8MT,
dcs.helicopters.Mi_24P,
dcs.helicopters.Ka_50,
]
e_attack_helos = [
dcs.helicopters.Mi_24P,
dcs.helicopters.Ka_50,
dcs.helicopters.Mi_28N,
]
e_transport_helos = [
dcs.helicopters.Mi_26,
dcs.helicopters.Mi_24P,
dcs.helicopters.Mi_8MT,
]
e_attack_planes = [
{'type': dcs.planes.Su_34, 'loadout': "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410"},
{'type': dcs.planes.Su_25, 'loadout': "RKB-250*8,R-60M*2"},
]

View File

@ -0,0 +1,17 @@
import math
import dcs
def getDistance(point1=dcs.Point, point2=dcs.Point):
x1 = point1.x
y1 = point1.y
x2 = point2.x
y2 = point2.y
dX = abs(x1-x2)
dY = abs(y1-y2)
distance = math.sqrt(dX*dX + dY*dY)
return distance
def convertMeterToNM(meters=int):
nm = meters / 1852
return nm

View File

@ -0,0 +1,20 @@
You can add your own scenarios in this directory and they will appear in the mission generator.
A scenario .miz file MUST have:
1) Between 1-4 trigger zones called "ALPHA", "BRAVO", "CHARLIE", "DELTA"
2) At least one trigger zone with a name that starts with "STAGING".
3) A blue airport (recommend somewhere near your staging zone).
4) A red airport (recommend somewhere near your last conflict zone).
Optional:
You can add smaller infantry spawning zones inside conflict zones. Add near buildings to simulate infantry hiding within. Name them like "ALPHA_SPAWN", "ALPHA_SPAWN_2, etc.
Tips:
-The conflict game type can be played with blue forces on defense. In this mode the last conflict zone is the only troop pickup zone.
-Design your template so that it can be played in normal 'attacking' mode or 'defending' the conflict zone from enemy ground units starting from the staging zone.
-Keep the zones fairly close together, both for helicopter and ground unit travel times.
-You can place static objects and units in a scenario template.
-You can change mission briefing and other mission options in the template.
-Drop your templates in the RotorOps Discord if you'd like to have them added in a release for everyone. Maintain a similar naming convention and be sure to credit yourself in the .miz name.
-Airfields can be captured with ground units. You might consider placing conflict zones over neutral airfields and adding unarmed client slots.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
Generator/assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
Generator/dist/MissionGenerator.exe vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
1) Place your .miz file in this directory.
2) Run embed_sounds.exe
3) A new file will be created in the same directory; a copy of your mission file with RotorOps sound files already embedded so you don't need to add manually.
Tip: You can use this tool to update your existing RotorOps missions with the current sound files.

View File

@ -0,0 +1,37 @@
import dcs
import os
from tkinter import messagebox as mbox
mizfound = False
path = os.getcwd()
dir_list = os.listdir(path)
print("Looking for mission files in '", path, "' :")
for filename in dir_list:
if filename.endswith(".miz") and not filename == "template_source.miz" and not filename.startswith("SoundsAdded"):
mizfound = True
print("Attempting to add sound files to: " + filename)
m = dcs.mission.Mission()
m.load_file(filename)
# add all of our required sounds
os.chdir("../sound/embedded")
path = os.getcwd()
sound_file_list = os.listdir(path)
print("Attempting to add sound files from '", path, "' :")
for soundfilename in sound_file_list:
if soundfilename.endswith(".ogg"):
print("Adding " + soundfilename)
m.map_resource.add_resource_file(soundfilename)
continue
else:
continue
os.chdir("../../Generator")
m.save("SoundsAdded_" + filename)
if not mizfound:
print("No valid miz files found!")
mbox.showerror('No Source Files Found', 'Error: Place your .miz files in this directory before running the application.')

View File

@ -0,0 +1,40 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['embed_sounds.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='embed_sounds',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
MissionGenerator.exe Normal file

Binary file not shown.

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# What is RotorOps?
RotorOps brings the ground war in DCS to life. Infantry becomes useful, and the helicopter operations that support them will directly contribute to the success of the mission.
RotorOps is a DCS script that makes it easy to create fun and engaging missions on the fly, directly in the mission editor and without ever opening a script file.
## Demo Missions
RotorOps: Aleppo Under Siege https://www.digitalcombatsimulator.com/en/files/3320079/
# RotorOps: Conflict
At the heart of this first release is a game type called Conflict, where attacking forces must clear Conflict Zones of defending ground forces. Once a zone is cleared, the next zone is activated and ground vehicles will move to the next Conflict Zone automatically. It's up to the rotorheads to pickup troops from the cleared zones and transport them to the active Conflict Zone.
![alt text](https://raw.githubusercontent.com/spencershepard/RotorOps/develop/documentation/images/rotorops%20conflict%20zones.png?raw=true)
### Do I have to transport troops?
This is really up to the mission designer. Transporting troops is not required for mission success in Conflict. However, friendly troops can be a very valuable asset, especially for clearing enemies in dense urban areas. If you're in a fixed wing or attack helicopter role, troop transport could be provided by other players or AI.
### What about attack helicopters?
The constantly moving infantry is easier to see than the statues we are used to seeing. Destroying defending enemy vehicles so that our troops and vehicles can survive, and intercepting enemy reinforcements may be crucial to mission success.
### How do I create a Conflict mission?
Just open a demo mission in the DCS mission editor and drop units into the Conflict Zones. These are trigger areas drawn in the mission editor that will automatically control the ground forces that enter them. This means that you do not need to worry about creating waypoints; enemy vehicles and infantry will seek each other out automatically. Move the Conflict Zones or change their size, add friendly or enemy units (remember, no waypoints needed).
Optional USER FLAGS are available to trigger events based on the status of individual zones and the game as a whole. Simple DO SCRIPT waypoint actions are available to drop troops from friendly or enemy AI helicopters or ground vehicles.
## RotorOps Mission Creator Guide: https://github.com/spencershepard/RotorOps/wiki/RotorOps:-Mission-Creator-Guide
***
## Get in touch!
![alt text](https://discord.com/assets/cb48d2a8d4991281d7a6a95d2f58195e.svg?raw=true)
https://discord.gg/HFqjrZV9xD
### Developers
We welcome contributors to this new project! Please get in touch on Discord with new ideas or pickup/create an issue in this repo.
***
RotorOps uses MIST and integrates CTLD:
https://github.com/mrSkortch/MissionScriptingTools
https://github.com/ciribob/DCS-CTLD

View File

@ -1,5 +1,6 @@
RotorOps = {}
RotorOps.version = "1.2.1"
RotorOps.version = "1.2.3"
local debug = true
---[[ROTOROPS OPTIONS]]---
@ -8,15 +9,21 @@ RotorOps.version = "1.2.1"
--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.voice_overs = true
RotorOps.ground_speed = 60 --max speed for ground vehicles moving between zones
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.max_units_left = 0 --allow clearing the zone when a few units are left to prevent frustration with units getting stuck in buildings etc
RotorOps.force_offroad = false --affects "move_to_zone" tasks only
RotorOps.apcs_spawn_infantry = false --apcs will unload troops when arriving to a new zone
RotorOps.auto_push = true --should attacking ground units move to the next zone after clearing?
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_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 settings that are safe to change only before calling setupConflict()
RotorOps.transports = {'UH-1H', 'Mi-8MT', 'Mi-24P', 'SA342M', 'SA342L', 'SA342Mistral'} --players flying these will have ctld transport access
RotorOps.auto_push = true --should attacking ground units move to the next zone after clearing?
RotorOps.CTLD_crates = false
RotorOps.CTLD_sound_effects = true --sound effects for troop pickup/dropoffs
RotorOps.exclude_ai_group_name = "noai" --include this somewhere in a group name to exclude the group from being tasked in the active zone
@ -53,6 +60,7 @@ local game_message_buffer = {}
local active_zone_initial_defenders
local apcs = {} --table to keep track of infantry vehicles
local low_units_message_fired = false
local inf_spawn_zones = {}
@ -177,8 +185,10 @@ end
---UTILITY FUNCTIONS---
local function debugMsg(text)
trigger.action.outText(text, 5)
env.info("ROTOROPS_DEBUG: "..text)
if(debug) then
--trigger.action.outText(text, 5)
env.info("ROTOROPS_DEBUG: "..text)
end
end
@ -347,6 +357,45 @@ function RotorOps.spawnGroupOnGroup(grp, src_grp_name, ai_task) --allow to spawn
end
end
--Spawn infantry in a trigger zone. Uses CTLD but may use another method in the future. Side is "red" or "blue"
--function RotorOps.spawnInfantryInZone(vars)
-- --local group = {mg=1,at=0,aa=0,inf=4,mortar=0}
--
-- local _triggerName = vars.zone
-- local _groupSide = vars.side
-- local _number = vars.qty
-- local _searchRadius = 500
--
-- local _spawnTrigger = trigger.misc.getZone(_triggerName) -- trigger to use as reference position
--
-- if _spawnTrigger == nil then
-- env.warning("ERROR: Cant find zone called " .. _triggerName)
-- return
-- end
--
-- local _country
-- if _groupSide == "red" then
-- _groupSide = 1
-- _country = 0
-- else
-- _groupSide = 2
-- _country = 2
-- end
--
-- if _searchRadius < 0 then
-- _searchRadius = 0
-- end
--
-- local _pos2 = { x = _spawnTrigger.point.x, y = _spawnTrigger.point.z }
-- local _alt = land.getHeight(_pos2)
-- local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
--
-- local _groupDetails = ctld.generateTroopTypes(_groupSide, _number, _country)
--
-- local _droppedTroops = ctld.spawnDroppedGroup(_pos3, _groupDetails, false, _searchRadius);
-- --debugMsg(_groupDetails.groupName)
-- return _groupDetails.groupName --_ { units = _troops, groupId = _groupId, groupName = string.format("%s %i", _groupName, _groupId), side = _side, country = _country, weight = _weight, jtac = _hasJTAC }
--end
--Easy way to deploy troops from a vehicle with waypoint action. Spawns from the first valid unit found in a group
function RotorOps.deployTroops(quantity, target_group, announce)
@ -410,6 +459,7 @@ end
---AI CORE BEHAVIOR--
--
function RotorOps.chargeEnemy(vars)
@ -427,61 +477,129 @@ function RotorOps.chargeEnemy(vars)
if grp:getCoalition() == 1 then enemy_coal = 2 end
if grp:getCoalition() == 2 then enemy_coal = 1 end
local volS
if vars.zone then
--debugMsg("CHARGE ENEMY at zone: "..vars.zone)
local sphere = trigger.misc.getZone(vars.zone)
volS = {
id = world.VolumeType.SPHERE,
params = {
point = sphere.point,
radius = sphere.radius
}
}
else
--debugMsg("CHARGE ENEMY in radius: "..search_radius)
volS = {
local ifFound = function(foundItem, val) ---dcs world.searchObjects method
local enemy_unit
local path = {}
--trigger.action.outText("found item: "..foundItem:getTypeName(), 5)
-- if foundItem:hasAttribute("Infantry") == true and foundItem:getCoalition() == enemy_coal then
if foundItem:getCoalition() == enemy_coal and foundItem:isActive() then
enemy_unit = foundItem
--debugMsg("found enemy! "..foundItem:getTypeName())
path[1] = mist.ground.buildWP(start_point, '', 5)
path[2] = mist.ground.buildWP(enemy_unit:getPoint(), '', 5)
mist.goRoute(grp, path)
else
--trigger.action.outText("object found is not enemy inf in "..search_radius, 5)
end
return true
end
if vars.zone then ---mist getUnitsInZones method
local units_in_zone = mist.getUnitsInZones(mist.makeUnitTable({'[red][vehicle]'}), {vars.zone}, "spherical")
local closest_dist = 10000
local closest_unit
for index, unit in pairs(units_in_zone) do
if unit:getCoalition() == enemy_coal then
local dist = mist.utils.get2DDist(start_point, unit:getPoint())
if dist < closest_dist then
closest_unit = unit
closest_dist = dist
end
end
end
if closest_unit ~= nil then
local path = {}
path[1] = mist.ground.buildWP(start_point, '', 5)
path[2] = mist.ground.buildWP(closest_unit:getPoint(), '', 5)
mist.goRoute(grp, path)
end
else ---dcs world.searchObjects method
--debugMsg("CHARGE ENEMY in radius: "..search_radius)
local volS = {
id = world.VolumeType.SPHERE,
params = {
point = first_valid_unit:getPoint(),
radius = search_radius
}
}
world.searchObjects(Object.Category.UNIT, volS, ifFound)
end
local enemy_unit
local path = {}
local ifFound = function(foundItem, val)
--trigger.action.outText("found item: "..foundItem:getTypeName(), 5)
-- if foundItem:hasAttribute("Infantry") == true and foundItem:getCoalition() == enemy_coal then
if foundItem:getCoalition() == enemy_coal and foundItem:isActive() then
enemy_unit = foundItem
--debugMsg("found enemy! "..foundItem:getTypeName())
path[1] = mist.ground.buildWP(start_point, '', 5)
path[2] = mist.ground.buildWP(enemy_unit:getPoint(), '', 5)
--path[3] = mist.ground.buildWP(vars.spawn_point, '', 5)
else
--trigger.action.outText("object found is not enemy inf in "..search_radius, 5)
end
return true
end
--default path if no units found
if false then
--debugMsg("group going back to origin")
path[1] = mist.ground.buildWP(start_point, '', 5)
path[2] = mist.ground.buildWP(vars.spawn_point, '', 5)
end
world.searchObjects(Object.Category.UNIT, volS, ifFound)
mist.goRoute(grp, path)
end
--function RotorOps.chargeEnemy(vars)
-- --trigger.action.outText("charge enemies: "..mist.utils.tableShow(vars), 5)
-- local grp = vars.grp
-- local search_radius = vars.radius or 5000
-- ----
-- local first_valid_unit = RotorOps.getValidUnitFromGroup(grp)
--
-- if first_valid_unit == nil then return end
-- local start_point = first_valid_unit:getPoint()
-- if not vars.spawn_point then vars.spawn_point = start_point end
--
-- local enemy_coal
-- if grp:getCoalition() == 1 then enemy_coal = 2 end
-- if grp:getCoalition() == 2 then enemy_coal = 1 end
--
-- local volS
-- if vars.zone then
-- --debugMsg("CHARGE ENEMY at zone: "..vars.zone)
-- local sphere = trigger.misc.getZone(vars.zone)
-- volS = {
-- id = world.VolumeType.SPHERE,
-- params = {
-- point = sphere.point,
-- radius = sphere.radius
-- }
-- }
-- else
-- --debugMsg("CHARGE ENEMY in radius: "..search_radius)
-- volS = {
-- id = world.VolumeType.SPHERE,
-- params = {
-- point = first_valid_unit:getPoint(),
-- radius = search_radius
-- }
-- }
-- end
--
--
-- local enemy_unit
-- local path = {}
-- local ifFound = function(foundItem, val)
-- --trigger.action.outText("found item: "..foundItem:getTypeName(), 5)
-- -- if foundItem:hasAttribute("Infantry") == true and foundItem:getCoalition() == enemy_coal then
-- if foundItem:getCoalition() == enemy_coal and foundItem:isActive() then
-- enemy_unit = foundItem
-- --debugMsg("found enemy! "..foundItem:getTypeName())
--
-- path[1] = mist.ground.buildWP(start_point, '', 5)
-- path[2] = mist.ground.buildWP(enemy_unit:getPoint(), '', 5)
-- --path[3] = mist.ground.buildWP(vars.spawn_point, '', 5)
-- mist.goRoute(grp, path)
-- else
--
-- --trigger.action.outText("object found is not enemy inf in "..search_radius, 5)
-- end
--
-- return true
-- end
--
-- world.searchObjects(Object.Category.UNIT, volS, ifFound)
--
--
--end
function RotorOps.patrolRadius(vars)
--debugMsg("patrol radius: "..mist.utils.tableShow(vars.grp))
local grp = vars.grp
@ -551,20 +669,45 @@ end
function RotorOps.aiExecute(vars)
local update_interval = 60
local last_task = vars.last_task
local last_zone = vars.last_zone
local group_name = vars.group_name
local task = RotorOps.ai_tasks[group_name].ai_task
local zone = RotorOps.ai_tasks[group_name].zone
-- if vars.zone then zone = vars.zone end
--debugMsg("tasking: "..group_name.." : "..task .." zone:"..zone)
if Group.isExist(Group.getByName(group_name)) ~= true or #Group.getByName(group_name):getUnits() < 1 then
--debugMsg("group no longer exists")
debugMsg(group_name.." no longer exists")
RotorOps.ai_tasks[group_name] = nil
return
end
--if Group.getByName(group_name):getController():hasTask() == false then --our implementation of hasTask does not seem to be working for vehicles
local same_zone = false
if zone ~= nil then
if zone ~= last_zone then
same_zone = true
end
end
local should_update = true
-- if task == last_task then
-- should_update = false
-- end
--
-- if same_zone then
-- should_update = false
-- end
--
-- if task == "patrol" then
-- should_update = true
-- end
if should_update then --check to make sure we don't have the same task
debugMsg("tasking: "..group_name.." : "..task)
if task == "patrol" then
local vars = {}
@ -600,7 +743,10 @@ function RotorOps.aiExecute(vars)
mist.groupToPoint(group_name, RotorOps.active_zone, formation, final_heading, speed, force_offroad)
end
end
vars.last_task = task
vars.last_zone = zone
local timer_id = timer.scheduleFunction(RotorOps.aiExecute, vars, timer.getTime() + update_interval)
end
@ -674,7 +820,18 @@ function RotorOps.assessUnitsInZone(var)
--debugMsg("taking stock of the active zone")
active_zone_initial_defenders = defending_ground_units
low_units_message_fired = false
env.info("ROTOR OPS: zone activated: "..RotorOps.active_zone)
--sort infantry spawn zones and spawn quantity
inf_spawn_zones = {}
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
inf_spawn_zones[#inf_spawn_zones + 1] = zone
env.info("ROTOR OPS: spawn zone found:"..zone)
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)
end
@ -766,6 +923,7 @@ function RotorOps.assessUnitsInZone(var)
if should_deploy then
local function timedDeploy()
if vehicle:isExist() then
env.info(vehicle:getName().." is deploying troops.")
RotorOps.deployTroops(4, vehicle:getGroup(), false)
end
end
@ -781,6 +939,26 @@ function RotorOps.assessUnitsInZone(var)
unloadAPCs() --this should really be an aitask
end
--spawn infantry in infantry spawn zones
local function spawnInfantry()
if math.random(0, 100) <= RotorOps.inf_spawn_chance then
local rand_index = math.random(1, #inf_spawn_zones)
local zone = inf_spawn_zones[rand_index]
if RotorOps.defending then
ctld.spawnGroupAtTrigger("blue", 5, zone, 1000)
else
ctld.spawnGroupAtTrigger("red", 5, zone, 1000)
end
RotorOps.inf_spawns_avail = RotorOps.inf_spawns_avail - 1
env.info("ROTOR OPS: Spawned infantry. "..RotorOps.inf_spawns_avail.." spawns remaining in "..zone)
end
end
if RotorOps.inf_spawns_avail > 0 and defenders_remaining_percent <= RotorOps.inf_spawn_trigger_percent then
spawnInfantry()
end
--voiceovers based on remaining defenders
if not low_units_message_fired then
@ -818,6 +996,7 @@ end
function RotorOps.drawZones() --this could use a lot of work, we should use trigger.action.removeMark and some way of managing ids created
local zones = RotorOps.zones
local previous_point
@ -975,6 +1154,10 @@ end
function RotorOps.addZone(_name, _zone_defenders_flag)
if trigger.misc.getZone(_name) == nil then
trigger.action.outText(_name.." trigger zone missing! Check RotorOps setup!", 60)
env.warning(_name.." trigger zone missing! Check RotorOps setup!")
end
table.insert(RotorOps.zones, {name = _name, defenders_status_flag = _zone_defenders_flag})
trigger.action.setUserFlag(_zone_defenders_flag, 101)
RotorOps.drawZones()
@ -982,11 +1165,16 @@ function RotorOps.addZone(_name, _zone_defenders_flag)
end
function RotorOps.stagingZone(_name)
if trigger.misc.getZone(_name) == nil then
trigger.action.outText(_name.." trigger zone missing! Check RotorOps setup!", 60)
env.warning(_name.." trigger zone missing! Check RotorOps setup!")
end
RotorOps.addPickupZone(_name, "blue", -1, "no", 0)
RotorOps.staging_zone = _name
end
--function to automatically add transport craft to ctld, rather than having to define each in the mission editor
function RotorOps.addPilots(var)
for uName, uData in pairs(mist.DBs.humansByName) do
@ -1032,6 +1220,12 @@ function RotorOps.startConflict()
RotorOps.staged_units = mist.getUnitsInZones(mist.makeUnitTable({'[all][vehicle]'}), {RotorOps.staging_zone})
if RotorOps.staged_units[1] == nil then
trigger.action.outText("RotorOps failed: You must place ground units in the staging and conflict zones!" , 60, false)
env.warning("No units in staging zone! Check RotorOps setup!")
return
end
if RotorOps.staged_units[1]:getCoalition() == 1 then --check the coalition in the staging zone to see if we're defending
RotorOps.defending = true
RotorOps.gameMsg(RotorOps.gameMsgs.start_defense)

View File

@ -3,3 +3,4 @@ assert(loadfile("C:\\RotorOps\\mist_4_4_90.lua"))()
assert(loadfile("C:\\RotorOps\\Splash_Damage_2_0.lua"))()
assert(loadfile("C:\\RotorOps\\CTLD.lua"))()
assert(loadfile("C:\\RotorOps\\RotorOps.lua"))()

8689
mist.lua

File diff suppressed because it is too large Load Diff