-uh-60L troop capacity set to 11
-bugfix: AI enemy planes/helicopters attacked invisible FARPS
-generator now produces error log
-syria scenario farp support units invulnerable
-all russia/usa objects now swap sides for defense mode, including carriers and farps
-forces templates can include air units with customization for loadout, livery, and skill
-carrier and farp parking for enemy helicopters
-parking now supports multiple airports per side
-improved AI flight orbits (now onside and perpendicular to closest enemy airport)
-enemy transport helicopters!
-apcs spawn infantry now disables conflict zones as infinite pickup zones (adds a bit of realism)
-bug fix: add zone triggers can be added in wrong order
This commit is contained in:
spencer-ki
2022-02-17 20:03:18 -08:00
parent 7356f90eab
commit ad11fd7937
20 changed files with 1006 additions and 364 deletions

View File

@@ -5,7 +5,7 @@ import dcs
import RotorOpsMission as ROps
import RotorOpsUtils
import RotorOpsUnits
import logging
from PyQt5.QtWidgets import (
QApplication, QDialog, QMainWindow, QMessageBox
@@ -13,12 +13,38 @@ from PyQt5.QtWidgets import (
from PyQt5 import QtGui
from MissionGeneratorUI import Ui_MainWindow
#Setup logfile and exception handler
logger = logging.getLogger(__name__)
logging.basicConfig(filename='generator.log', encoding='utf-8', level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt): #example of handling error subclasses
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
msg = QMessageBox()
msg.setWindowTitle("Uncaught exception")
msg.setText("Oops, there was a problem. Please check the log file or post it in the RotorOps discord where some helpful people will have a look.")
x = msg.exec_()
sys.excepthook = handle_exception
maj_version = 0
minor_version = 3
minor_version = 4
version_string = str(maj_version) + "." + str(minor_version)
scenarios = []
red_forces_files = []
blue_forces_files = []
defenders_text = "Defending Forces:"
attackers_text = "Attacking Forces:"
logger.info("RotorOps v" + version_string)
class Window(QMainWindow, Ui_MainWindow):
@@ -27,11 +53,11 @@ class Window(QMainWindow, Ui_MainWindow):
super().__init__(parent)
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
print('running in a PyInstaller bundle')
logger.info('running in a PyInstaller bundle')
home_dir = os.getcwd()
os.chdir(home_dir + "/Generator")
else:
print('running in a normal Python process')
logger.info('running in a normal Python process')
self.m = ROps.RotorOpsMission()
@@ -42,6 +68,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.populateForces("blue", self.blueforces_comboBox, blue_forces_files)
self.populateSlotSelection()
self.blue_forces_label.setText(attackers_text)
self.red_forces_label.setText(defenders_text)
self.background_label.setPixmap(QtGui.QPixmap(self.m.assets_dir + "/background.PNG"))
self.statusbar.setStyleSheet(
"QStatusBar{padding-left:5px;color:black;font-weight:bold;}")
@@ -50,15 +78,15 @@ class Window(QMainWindow, Ui_MainWindow):
def connectSignalsSlots(self):
# self.action_Exit.triggered.connect(self.close)
self.action_generateMission.triggered.connect(self.generateMissionAction)
self.action_scenarioSelected.triggered.connect(self.scenarioChanged)
self.action_defensiveModeChanged.triggered.connect(self.defensiveModeChanged)
def populateScenarios(self):
os.chdir(self.m.scenarios_dir)
path = os.getcwd()
dir_list = os.listdir(path)
print("Looking for mission files in '", path, "' :")
logger.info("Looking for mission files in " + path)
for filename in dir_list:
if filename.endswith(".miz"):
@@ -70,7 +98,7 @@ class Window(QMainWindow, Ui_MainWindow):
os.chdir(self.m.forces_dir + "/" + side)
path = os.getcwd()
dir_list = os.listdir(path)
print("Looking for " + side + " Forces files in '", os.getcwd(), "' :")
logger.info("Looking for " + side + " Forces files in '" + path)
for filename in dir_list:
if filename.endswith(".miz"):
@@ -82,65 +110,69 @@ class Window(QMainWindow, Ui_MainWindow):
for type in RotorOpsUnits.client_helos:
self.slot_template_comboBox.addItem(type.id)
def defensiveModeChanged(self):
if self.defense_checkBox.isChecked():
self.red_forces_label.setText(attackers_text)
self.blue_forces_label.setText(defenders_text)
else:
self.red_forces_label.setText(defenders_text)
self.blue_forces_label.setText(attackers_text)
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
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])
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
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.")
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())
)
#self.description_textBrowser.setText("File error occured.")
def generateMissionAction(self):
@@ -168,19 +200,21 @@ class Window(QMainWindow, Ui_MainWindow):
"zone_protect_sams": self.zone_sams_checkBox.isChecked(),
"zone_farps": self.farp_buttonGroup.checkedButton().objectName(),
"inf_spawn_msgs": self.inf_spawn_voiceovers_checkBox.isChecked(),
"e_transport_helos": self.e_transport_helos_spinBox.value(),
"transport_drop_qty": self.troop_drop_spinBox.value(),
}
os.chdir(self.m.home_dir + '/Generator')
n = ROps.RotorOpsMission()
result = n.generateMission(data)
print("Generating mission with options:")
print(str(data))
logger.info("Generating mission with options:")
logger.info(str(data))
# generate the mission
#result = self.m.generateMission(data)
#display results
if result["success"]:
print(result["filename"] + "' successfully generated in " + result["directory"])
logger.info(result["filename"] + "' successfully generated in " + result["directory"])
self.statusbar.showMessage(result["filename"] + "' successfully generated in " + result["directory"], 10000)
msg = QMessageBox()
msg.setWindowTitle("Mission Generated")
@@ -199,7 +233,7 @@ class Window(QMainWindow, Ui_MainWindow):
)
x = msg.exec_()
elif not result["success"]:
print(result["failure_msg"])
logger.warning(result["failure_msg"])
msg = QMessageBox()
msg.setWindowTitle("Error")
msg.setText(result["failure_msg"])